From 3b9f6bc809b9207ba8041ed4d1de306e758a96ad Mon Sep 17 00:00:00 2001 From: Siddharth Gelera Date: Wed, 17 Sep 2025 13:07:15 +0530 Subject: [PATCH] feat: enhance WebSocket handling with redirect and upgrade events - Added support for 'ws:redirect' and 'ws:upgrade' events in the WebSocket client. - Updated WSResponseHeaders to format headers correctly. - Modified WSResponsePane to display headers in the response. - Improved message handling in the Redux slice for WebSocket events. --- .../RequestTabs/RequestTab/index.js | 2 -- .../WsResponsePane/WSMessagesList/index.js | 17 ++++------ .../WsResponsePane/WSResponseHeaders/index.js | 11 +++++-- .../ResponsePane/WsResponsePane/index.js | 9 +++++ .../ReduxStore/slices/collections/index.js | 31 +++++++++-------- .../src/utils/network/ws-event-listeners.js | 26 +++++++++++++++ packages/bruno-lang/v1/src/index.js | 2 -- packages/bruno-requests/src/ws/ws-client.js | 33 +++++++++++++++---- 8 files changed, 94 insertions(+), 37 deletions(-) diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index 2da4ed79e..7a5cd204b 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -128,11 +128,9 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi ); } - const isGrpc = item.type === 'grpc-request'; const isWS = item.type === 'ws-request'; const method = getMethodText(item) - return ( {showConfirmClose && ( diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js index bd86fa148..912fc26e6 100644 --- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js +++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js @@ -34,7 +34,6 @@ const parseContent = (content) => { return { type: contentMeta.isJSON ? 'application/json' : 'text/plain', content: contentMeta.isJSON ? JSON.stringify(JSON.parse(contentMeta.content), null, 2) : contentMeta.content, - sliced: contentMeta.content.slice(0, 30) }; }; @@ -70,7 +69,6 @@ const WSMessageItem = ({ message, isLast }) => { const isIncoming = message.type === 'incoming'; const isInfo = message.type === 'info'; let parsedContent = parseContent(message.message); - const dataType = getDataTypeText(parsedContent.type); return ( @@ -88,17 +86,16 @@ const WSMessageItem = ({ message, isLast }) => {
{ - if(!isInfo){ - setIsOpen(!isOpen); - } + if(isInfo) return + setIsOpen(!isOpen); }} > -
+
{ > - {parsedContent.sliced} + {parsedContent.content}
-
+
{message.timestamp && ( {new Date(message.timestamp).toISOString()} )} diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseHeaders/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseHeaders/index.js index 0ef075410..3a4f8fd39 100644 --- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseHeaders/index.js +++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseHeaders/index.js @@ -1,9 +1,14 @@ import React from 'react'; import StyledWrapper from './StyledWrapper'; -const WSResponseHeaders = ({ metadata }) => { - // Ensure headers is an array - const metadataArray = Array.isArray(metadata) ? metadata : []; +const WSResponseHeaders = ({ response }) => { + const formatHeaders = (headers) => { + if (!headers) return []; + if (Array.isArray(headers)) return headers; + return Object.entries(headers).map(([key, value]) => ({ name: key, value })); + }; + + const metadataArray = formatHeaders(response.headers); return ( diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js index 0b414b530..931ab973c 100644 --- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js @@ -14,6 +14,7 @@ import ResponseLayoutToggle from '../ResponseLayoutToggle'; import Tab from 'components/Tab'; import WSMessagesList from './WSMessagesList'; import WSResponseSortOrder from './WSResponseSortOrder'; +import WSResponseHeaders from './WSResponseHeaders'; const WSResult = ({ response }) => { return response.isError ? ( @@ -51,6 +52,9 @@ const WSResponsePane = ({ item, collection }) => { case 'response': { return ; } + case 'headers': { + return ; + } case 'timeline': { return ; } @@ -91,6 +95,11 @@ const WSResponsePane = ({ item, collection }) => { label: 'Messages', count: Array.isArray(response.responses) ? response.responses.length : 0 }, + { + name: 'headers', + label: 'Metadata', + count: response.headers ? Object.keys(response.headers).length : 0 + }, { name: 'timeline', label: 'Timeline' diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 8579933e5..410ed97bf 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -2817,28 +2817,33 @@ export const collectionsSlice = createSlice({ // Process based on event type switch (eventType) { case 'message': - const { message, type } = eventData; - // Add message to responses list - updatedResponse.responses = [ - ...(currentResponse?.responses || []), - { - message, - type, - timestamp: Date.now() - } - ]; + updatedResponse.responses = (currentResponse?.responses||[]).concat(eventData) break; + case 'redirect': + updatedResponse.requestHeaders = eventData.headers + updatedResponse.responses ||= [] + updatedResponse.responses.push({ + message: eventData.message, + type: eventData.type, + timestamp: eventData.timestamp, + }) + break + + case 'upgrade': + updatedResponse.headers = eventData.headers + break + case 'open': updatedResponse.status = 'CONNECTED'; updatedResponse.statusText = 'CONNECTED'; updatedResponse.statusCode = 0; updatedResponse.responses ||= [] updatedResponse.responses.push({ - message: "Connected", - type: "info", - timestamp: Date.now() + message: `Connected to ${eventData.url}`, + type: 'info', + timestamp: eventData.timestamp }) break; diff --git a/packages/bruno-app/src/utils/network/ws-event-listeners.js b/packages/bruno-app/src/utils/network/ws-event-listeners.js index 10d70e1bd..16474a29a 100644 --- a/packages/bruno-app/src/utils/network/ws-event-listeners.js +++ b/packages/bruno-app/src/utils/network/ws-event-listeners.js @@ -28,6 +28,30 @@ const useWsEventListeners = () => { ); }); + + + const removeWsUpgradeListener = ipcRenderer.on('ws:upgrade', (requestId, collectionUid, eventData) => { + dispatch( + wsResponseReceived({ + itemUid: requestId, + collectionUid: collectionUid, + eventType: 'upgrade', + eventData: eventData + }) + ); + }); + + const removeWsRedirectListener = ipcRenderer.on('ws:redirect', (requestId, collectionUid, eventData) => { + dispatch( + wsResponseReceived({ + itemUid: requestId, + collectionUid: collectionUid, + eventType: 'redirect', + eventData: eventData + }) + ); + }); + // Handle WebSocket message event const removeWsMessageListener = ipcRenderer.on('ws:message', (requestId, collectionUid, eventData) => { dispatch( @@ -94,6 +118,8 @@ const useWsEventListeners = () => { return () => { removeWsRequestSentListener(); + removeWsUpgradeListener(); + removeWsRedirectListener(); removeWsMessageListener(); removeWsOpenListener(); removeWsCloseListener(); diff --git a/packages/bruno-lang/v1/src/index.js b/packages/bruno-lang/v1/src/index.js index 43dc96a32..07de838c1 100644 --- a/packages/bruno-lang/v1/src/index.js +++ b/packages/bruno-lang/v1/src/index.js @@ -40,8 +40,6 @@ const bruToJson = (fileContents) => { const parsed = parser.run(fileContents).result.reduce((acc, item) => _.merge(acc, item), {}); - console.log({ parsed }); - const json = { type: parsed.type || '', name: parsed.name || '', diff --git a/packages/bruno-requests/src/ws/ws-client.js b/packages/bruno-requests/src/ws/ws-client.js index 961dd0da4..497ddc2e9 100644 --- a/packages/bruno-requests/src/ws/ws-client.js +++ b/packages/bruno-requests/src/ws/ws-client.js @@ -94,17 +94,16 @@ class WsClient { }); // Set up event handlers - this.#setupWsEventHandlers(wsConnection, requestId, collectionUid); + this.#setupWsEventHandlers(wsConnection, requestId, collectionUid, { + url: parsedUrl.fullUrl, + headers + }); // Store the connection this.#addConnection(requestId, wsConnection); // Emit connecting event - this.eventCallback('ws:connecting', requestId, collectionUid, { - url: parsedUrl.fullUrl, - headers, - timestamp: Date.now() - }); + this.eventCallback('ws:connecting', requestId, collectionUid); wsConnection.addEventListener('open', () => { this.#flushQueue(requestId, collectionUid); @@ -265,10 +264,30 @@ class WsClient { #setupWsEventHandlers(ws, requestId, collectionUid) { ws.on('open', () => { this.eventCallback('ws:open', requestId, collectionUid, { - timestamp: Date.now() + timestamp: Date.now(), + url: ws.url }); }); + ws.on('redirect', (url, req) => { + const headerNames = req.getHeaderNames() + const headers = Object.fromEntries(headerNames.map(d=> [d,req.getHeader(d)])) + this.eventCallback('ws:redirect', requestId, collectionUid, { + message:`Redirected to ${url}`, + type: 'info', + timestamp: Date.now(), + headers: headers + }); + }) + + ws.on('upgrade', (response) => { + this.eventCallback('ws:upgrade', requestId, collectionUid, { + type: 'info', + timestamp: Date.now(), + headers: {...response.headers} + }); + }) + ws.on('message', (data) => { try { const message = JSON.parse(data.toString());