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.
This commit is contained in:
Siddharth Gelera
2025-09-17 13:07:15 +05:30
parent 1a1bfdce4c
commit 3b9f6bc809
8 changed files with 94 additions and 37 deletions

View File

@@ -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 (
<StyledWrapper className="flex items-center justify-between tab-container px-1">
{showConfirmClose && (

View File

@@ -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 }) => {
<div
className={
classnames("flex items-center justify-between",{
'cursor-not-allowed': isInfo,
'cursor-pointer': !isInfo
"cursor-pointer": !isInfo,
"cursor-not-allowed": isInfo
})
}
onClick={(e) => {
if(!isInfo){
setIsOpen(!isOpen);
}
if(isInfo) return
setIsOpen(!isOpen);
}}
>
<div className="flex">
<div className="flex min-w-0 shrink">
<span
className={classnames(
'font-semibold flex items-center gap-1',
@@ -107,9 +104,9 @@ const WSMessageItem = ({ message, isLast }) => {
>
<TypeIcon type={message.type} />
</span>
<span className="ml-3">{parsedContent.sliced}</span>
<span className="ml-3 text-ellipsis max-w-full overflow-hidden text-nowrap">{parsedContent.content}</span>
</div>
<div className="flex gap-2">
<div className="flex shrink-0 gap-2">
{message.timestamp && (
<span className="text-xs text-gray-400">{new Date(message.timestamp).toISOString()}</span>
)}

View File

@@ -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 (
<StyledWrapper className="pb-4 w-full">

View File

@@ -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 <WSResult response={response} />;
}
case 'headers': {
return <WSResponseHeaders response={response} />;
}
case 'timeline': {
return <Timeline collection={collection} item={item} />;
}
@@ -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'

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 || '',

View File

@@ -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());