From a79ad218074c4ec6514640ebb00fe7db839d4b62 Mon Sep 17 00:00:00 2001 From: Siddharth Gelera Date: Tue, 23 Sep 2025 18:41:27 +0530 Subject: [PATCH] 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. --- .../components/RequestPane/WsBody/index.js | 95 +++++++++++++------ .../bruno-app/src/utils/collections/index.js | 3 +- packages/bruno-lang/v2/src/bruToJson.js | 21 +--- packages/bruno-lang/v2/src/jsonToBru.js | 17 ++-- .../bruno-schema/src/collections/index.js | 1 + 5 files changed, 86 insertions(+), 51 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/WsBody/index.js b/packages/bruno-app/src/components/RequestPane/WsBody/index.js index e99cc0576..47cc74cd7 100644 --- a/packages/bruno-app/src/components/RequestPane/WsBody/index.js +++ b/packages/bruno-app/src/components/RequestPane/WsBody/index.js @@ -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 (
e.stopPropagation()}> - +
@@ -223,7 +264,7 @@ const WSBody = ({ item, collection, handleRun }) => {
{ 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) })) }; diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 732bf77b9..bd70d18bd 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -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 } ] diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 7d9522aea..0dc456131 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -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'; }); } diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index f80bb5d15..92949bf31 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -389,6 +389,7 @@ const wsRequestSchema = Yup.object({ .of( Yup.object({ name: Yup.string().nullable(), + decoder: Yup.string().nullable(), content: Yup.string().nullable() }) )