mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-23 20:55:41 +00:00
feat: enhance WebSocket message handling with decoder support
- Added decoder field to WebSocket messages in various components. - Updated prettify functionality to handle XML and JSON formats. - Modified Redux state to include decoder information. - Adjusted schema validation to accommodate decoder field.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { get } from 'lodash';
|
||||
import { get, invert } from 'lodash';
|
||||
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
@@ -8,11 +8,20 @@ import { IconChevronDown, IconChevronUp, IconPlus, IconTrash, IconWand } from '@
|
||||
import CodeEditor from 'components/CodeEditor/index';
|
||||
import ToolHint from 'components/ToolHint/index';
|
||||
import { applyEdits, format } from 'jsonc-parser';
|
||||
import xmlFormat from 'xml-formatter';
|
||||
import { toastError } from 'utils/common/error';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import WSRequestBodyMode from './BodyMode/index';
|
||||
import { autoDetectLang } from 'utils/codemirror/lang-detect';
|
||||
|
||||
const TYPE_BY_DECODER = {
|
||||
base64: 'binary',
|
||||
json: 'json',
|
||||
xml: 'xml'
|
||||
};
|
||||
|
||||
const DECODER_BY_TYPE = invert(TYPE_BY_DECODER)
|
||||
|
||||
const SingleWSMessage = ({
|
||||
message,
|
||||
item,
|
||||
@@ -29,7 +38,7 @@ const SingleWSMessage = ({
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
|
||||
|
||||
const { name, content } = message;
|
||||
const { name, content, decoder } = message;
|
||||
const [messageFormat, setMessageFormat] = useState(autoDetectLang(content));
|
||||
|
||||
const onEdit = (value) => {
|
||||
@@ -37,6 +46,7 @@ const SingleWSMessage = ({
|
||||
|
||||
currentMessages[index] = {
|
||||
name: name ? name : `message ${index + 1}`,
|
||||
decoder: DECODER_BY_TYPE[messageFormat],
|
||||
content: value
|
||||
};
|
||||
|
||||
@@ -65,30 +75,13 @@ const SingleWSMessage = ({
|
||||
);
|
||||
};
|
||||
|
||||
const onPrettify = () => {
|
||||
try {
|
||||
const edits = format(content, undefined, { tabSize: 2, insertSpaces: true });
|
||||
const prettyBodyJson = applyEdits(content, edits);
|
||||
|
||||
const currentMessages = [...(body.ws || [])];
|
||||
currentMessages[index] = {
|
||||
name: name ? name : `message ${index + 1}`,
|
||||
content: prettyBodyJson
|
||||
};
|
||||
dispatch(
|
||||
updateRequestBody({
|
||||
content: currentMessages,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
toastError(new Error('Unable to prettify. Invalid JSON format.'));
|
||||
}
|
||||
};
|
||||
|
||||
const getContainerHeight =
|
||||
canClientSendMultipleMessages && body.ws.length > 1 ? `${isCollapsed ? '' : 'h-80'}` : 'h-full';
|
||||
|
||||
let codeType = messageFormat;
|
||||
if (TYPE_BY_DECODER[decoder]){
|
||||
codeType = TYPE_BY_DECODER[decoder];
|
||||
}
|
||||
|
||||
const codemirrorMode = {
|
||||
text: 'application/text',
|
||||
@@ -96,6 +89,54 @@ const SingleWSMessage = ({
|
||||
json: 'application/ld+json'
|
||||
};
|
||||
|
||||
|
||||
const onPrettify = () => {
|
||||
if(codeType === "json"){
|
||||
try {
|
||||
const edits = format(content, undefined, { tabSize: 2, insertSpaces: true });
|
||||
const prettyBodyJson = applyEdits(content, edits);
|
||||
|
||||
const currentMessages = [...(body.ws || [])];
|
||||
currentMessages[index] = {
|
||||
name: name ? name : `message ${index + 1}`,
|
||||
content: prettyBodyJson
|
||||
};
|
||||
dispatch(
|
||||
updateRequestBody({
|
||||
content: currentMessages,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
toastError(new Error('Unable to prettify. Invalid JSON format.'));
|
||||
}
|
||||
}
|
||||
|
||||
if (codeType === "xml") {
|
||||
try {
|
||||
const prettyBodyXML = xmlFormat(content, { collapseContent: true });
|
||||
|
||||
const currentMessages = [...(body.ws || [])];
|
||||
currentMessages[index] = {
|
||||
name: name ? name : `message ${index + 1}`,
|
||||
content: prettyBodyXML
|
||||
};
|
||||
|
||||
dispatch(
|
||||
updateRequestBody({
|
||||
content: currentMessages,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
toastError(new Error('Unable to prettify. Invalid XML format.'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col mb-3 border border-neutral-200 dark:border-neutral-800 rounded-md overflow-hidden ${getContainerHeight} relative`}
|
||||
@@ -113,7 +154,7 @@ const SingleWSMessage = ({
|
||||
</div>
|
||||
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<WSRequestBodyMode mode={messageFormat} onModeChange={setMessageFormat} />
|
||||
<ToolHint text="Format JSON with proper indentation and spacing" toolhintId={`prettify-msg-${index}`}>
|
||||
<ToolHint text="Prettify" toolhintId={`prettify-msg-${index}`}>
|
||||
<button
|
||||
onClick={onPrettify}
|
||||
className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
|
||||
@@ -145,7 +186,7 @@ const SingleWSMessage = ({
|
||||
onEdit={onEdit}
|
||||
onRun={handleRun}
|
||||
onSave={onSave}
|
||||
mode={codemirrorMode[messageFormat] ?? 'text/plain'}
|
||||
mode={codemirrorMode[codeType] ?? 'text/plain'}
|
||||
enableVariableHighlighting={true}
|
||||
/>
|
||||
</div>
|
||||
@@ -223,7 +264,7 @@ const WSBody = ({ item, collection, handleRun }) => {
|
||||
<StyledWrapper isVerticalLayout={isVerticalLayout}>
|
||||
<div
|
||||
ref={messagesContainerRef}
|
||||
id="grpc-messages-container"
|
||||
id="ws-messages-container"
|
||||
className={`flex-1 ${body.ws.length === 1 || !canClientSendMultipleMessages ? 'h-full' : 'overflow-y-auto'} ${
|
||||
canClientSendMultipleMessages && 'pb-16'
|
||||
}`}
|
||||
|
||||
@@ -666,8 +666,9 @@ export const transformRequestToSaveToFilesystem = (item) => {
|
||||
if (itemToSave.request.body.mode === 'ws') {
|
||||
itemToSave.request.body = {
|
||||
...itemToSave.request.body,
|
||||
ws: itemToSave.request.body.ws.map(({ name, content }, index) => ({
|
||||
ws: itemToSave.request.body.ws.map(({ name, content, decoder }, index) => ({
|
||||
name: name ? name : `message ${index + 1}`,
|
||||
decoder,
|
||||
content: replaceTabsWithSpaces(content)
|
||||
}))
|
||||
};
|
||||
|
||||
@@ -1005,29 +1005,17 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
}
|
||||
};
|
||||
},
|
||||
// TODO: reaper remove `:ws`
|
||||
// TODO: reaper add `type`
|
||||
bodyws(_1, dictionary) {
|
||||
const pairs = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||
const namePair = _.find(pairs, { name: 'name' });
|
||||
const contentPair = _.find(pairs, { name: 'content' });
|
||||
const decoderPair = _.find(pairs, { name: 'decoder' });
|
||||
|
||||
const messageName = namePair ? namePair.value : '';
|
||||
const messageContent = contentPair ? contentPair.value : '';
|
||||
|
||||
try {
|
||||
JSON.parse(messageContent);
|
||||
} catch (error) {
|
||||
return {
|
||||
body: {
|
||||
mode: 'ws',
|
||||
ws: [
|
||||
{
|
||||
name: messageName,
|
||||
content: messageContent
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
const decoderContent = decoderPair ? decoderPair.value : '';
|
||||
|
||||
return {
|
||||
body: {
|
||||
@@ -1035,6 +1023,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
ws: [
|
||||
{
|
||||
name: messageName,
|
||||
decoder: decoderContent,
|
||||
content: messageContent
|
||||
}
|
||||
]
|
||||
|
||||
@@ -103,22 +103,22 @@ const jsonToBru = (json) => {
|
||||
|
||||
if (ws.method && ws.method.length) {
|
||||
bru += `
|
||||
method: ${ws.method}`;
|
||||
method: ${ws.method}`;
|
||||
}
|
||||
|
||||
if (ws.body && ws.body.length) {
|
||||
bru += `
|
||||
body: ${ws.body}`;
|
||||
body: ${ws.body}`;
|
||||
}
|
||||
|
||||
if (ws.auth && ws.auth.length) {
|
||||
bru += `
|
||||
auth: ${ws.auth}`;
|
||||
auth: ${ws.auth}`;
|
||||
}
|
||||
|
||||
if (ws.methodType && ws.methodType.length) {
|
||||
bru += `
|
||||
methodType: ${ws.methodType}`;
|
||||
methodType: ${ws.methodType}`;
|
||||
}
|
||||
|
||||
bru += `
|
||||
@@ -635,17 +635,20 @@ ${indentString(body.sparql)}
|
||||
// Convert each ws message to a separate body:ws block
|
||||
if (Array.isArray(body.ws)) {
|
||||
body.ws.forEach((m) => {
|
||||
const { name, content } = m;
|
||||
const { name, content, decoder = "" } = m;
|
||||
|
||||
bru += `body:ws {\n`;
|
||||
|
||||
bru += `${indentString(`name: ${getValueString(name)}`)}\n`;
|
||||
if(decoder.length){
|
||||
bru += `${indentString(`decoder: ${getValueString(decoder)}`)}\n`;
|
||||
}
|
||||
|
||||
// Convert content to JSON string if it's an object
|
||||
let jsonValue = typeof content === 'object' ? JSON.stringify(content, null, 2) : content || '{}';
|
||||
let contentValue = typeof content === 'object' ? JSON.stringify(content, null, 2) : content || '{}';
|
||||
|
||||
// Wrap content with triple quotes for multiline support, without extra indentation
|
||||
bru += `${indentString(`content: '''\n${indentString(jsonValue)}\n'''`)}\n`;
|
||||
bru += `${indentString(`content: '''\n${indentString(contentValue)}\n'''`)}\n`;
|
||||
bru += '}\n\n';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -389,6 +389,7 @@ const wsRequestSchema = Yup.object({
|
||||
.of(
|
||||
Yup.object({
|
||||
name: Yup.string().nullable(),
|
||||
decoder: Yup.string().nullable(),
|
||||
content: Yup.string().nullable()
|
||||
})
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user