Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import CodeEditor from 'components/CodeEditor/index';
import { useTheme } from 'providers/Theme';
import { useState } from 'react';
import { useSelector } from 'react-redux';
import _ from 'lodash';
import { useRef } from 'react';
import { useEffect } from 'react';

Expand Down Expand Up @@ -183,12 +182,16 @@ const WSMessagesList = ({ order = -1, messages = [] }) => {
if (!messages.length) {
return <StyledWrapper><div className="empty-state">No messages yet.</div></StyledWrapper>;
}
const ordered = order === -1 ? messages : messages.slice().reverse();

// sort based on order, seq was newly added and might be missing in some cases and when missing,
// the timestamp will be used instead
const ordered = messages.toSorted((x, y) => ((x.seq ?? x.timestamp) - (y.seq ?? y.timestamp)) * order);

return (
<StyledWrapper className="ws-messages-list mt-2 flex flex-col">
{ordered.map((msg, idx, src) => {
const inFocus = order === -1 ? src.length - 1 === idx : idx === 0;
return <WSMessageItem key={msg.timestamp} inFocus={inFocus} id={idx} message={msg} />;
return <WSMessageItem key={msg.seq ? msg.seq : msg.timestamp} inFocus={inFocus} id={idx} message={msg} />;
})}
</StyledWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export const collectionsSlice = createSlice({
}
},
requestCancelled: (state, action) => {
const { itemUid, collectionUid } = action.payload;
const { itemUid, collectionUid, seq, timestamp } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);

if (collection) {
Expand All @@ -394,7 +394,7 @@ export const collectionsSlice = createSlice({

const startTimestamp = item.requestSent.timestamp;
item.response.duration = startTimestamp ? Date.now() - startTimestamp : item.response.duration;
item.response.data = [{ type: 'info', timestamp: Date.now(), message: 'Connection Closed' }].concat(item.response.data);
item.response.data = [{ type: 'info', timestamp: Date.now(), seq: seq, message: 'Connection Closed' }].concat(item.response.data);
} else {
item.response = null;
item.requestUid = null;
Expand Down Expand Up @@ -3118,7 +3118,7 @@ export const collectionsSlice = createSlice({
}
},
streamDataReceived: (state, action) => {
const { itemUid, collectionUid, data } = action.payload;
const { itemUid, collectionUid, seq, timestamp, data } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);

if (collection) {
Expand All @@ -3127,12 +3127,16 @@ export const collectionsSlice = createSlice({
item.response.data ||= [];
item.response.data = [{
type: 'incoming',
seq,
message: data.data,
messageHexdump: hexdump(data.data),
timestamp: Date.now()
timestamp: timestamp || Date.now()
}].concat(item.response.data);
}
item.response.dataBuffer = Buffer.concat([Buffer.from(item.response.dataBuffer), Buffer.from(data.dataBuffer)]);
if (item.response.dataBuffer && item.response.dataBuffer.length && data.dataBuffer) {
item.response.dataBuffer = Buffer.concat([Buffer.from(item.response.dataBuffer), Buffer.from(data.dataBuffer)]);
}

item.response.size = data.data?.length + (item.response.size || 0);
}
},
Expand Down Expand Up @@ -3255,7 +3259,8 @@ export const collectionsSlice = createSlice({
updatedResponse.responses.push({
message: eventData.message,
type: eventData.type,
timestamp: eventData.timestamp
timestamp: eventData.timestamp,
seq: eventData.seq
});
break;

Expand All @@ -3271,7 +3276,8 @@ export const collectionsSlice = createSlice({
updatedResponse.responses.push({
message: `Connected to ${eventData.url}`,
type: 'info',
timestamp: eventData.timestamp
timestamp: eventData.timestamp,
seq: eventData.seq
});
break;

Expand All @@ -3287,7 +3293,8 @@ export const collectionsSlice = createSlice({
updatedResponse.responses.push({
type: code !== 1000 ? 'info' : 'error',
message: reason.trim().length ? ['Closed:', reason.trim()].join(' ') : 'Closed',
timestamp
timestamp: eventData.timestamp,
seq: eventData.seq
});
break;

Expand All @@ -3302,7 +3309,8 @@ export const collectionsSlice = createSlice({
updatedResponse.responses.push({
type: 'error',
message: errorDetails || 'WebSocket error occurred',
timestamp
timestamp: eventData.timestamp,
seq: eventData.seq
});

break;
Expand Down
24 changes: 19 additions & 5 deletions packages/bruno-electron/src/ipc/network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,7 @@ const registerNetworkIpc = (mainWindow) => {

// handler for sending http request
ipcMain.handle('send-http-request', async (event, item, collection, environment, runtimeVariables) => {
let seq = 0;
const collectionUid = collection.uid;
const envVars = getEnvVars(environment);
const processEnvVars = getProcessEnvVars(collectionUid);
Expand All @@ -1012,16 +1013,29 @@ const registerNetworkIpc = (mainWindow) => {
response.stream = { running: response.status >= 200 && response.status < 300 };

stream.on('data', (newData) => {
seq += 1;

const parsed = parseDataFromResponse({ data: newData, headers: {} });
mainWindow.webContents.send('main:http-stream-new-data', { collectionUid, itemUid: item.uid, data: parsed });

mainWindow.webContents.send('main:http-stream-new-data', {
collectionUid,
itemUid: item.uid,
seq,
timestamp: Date.now(),
data: parsed
});
});

stream.on('close', () => {
if (!cancelTokens[response.cancelTokenUid]) {
return;
}
if (!cancelTokens[response.cancelTokenUid]) return;

mainWindow.webContents.send('main:http-stream-end', {
collectionUid,
itemUid: item.uid,
seq: seq + 1,
timestamp: Date.now()
});

mainWindow.webContents.send('main:http-stream-end', { collectionUid, itemUid: item.uid });
deleteCancelToken(response.cancelTokenUid);
});
}
Expand Down
45 changes: 43 additions & 2 deletions packages/bruno-requests/src/ws/ws-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,36 @@ const normalizeMessageByFormat = (message, format) => {
}
};

const createSequencer = () => {
const seq = {};

const nextSeq = (requestId, collectionId) => {
seq[requestId] ||= {};
seq[requestId][collectionId] ||= 0;
return ++seq[requestId][collectionId];
};

/**
* @param {string} requestId
* @param {string} [collectionId]
*/
const clean = (requestId, collectionId = undefined) => {
if (collectionId) {
delete seq[requestId][collectionId];
}
if (!Object.keys(seq[requestId]).length) {
delete seq[requestId];
}
};

return {
next: nextSeq,
clean
};
};
Comment on lines +52 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add guard checks to prevent runtime errors in clean().

The clean method will throw if seq[requestId] is undefined. This can happen if close() is called on a connection that failed before emitting any events (since seq[requestId] is only lazily initialized in nextSeq).

🔎 Proposed fix
 const clean = (requestId, collectionId = undefined) => {
+  if (!seq[requestId]) {
+    return;
+  }
+
   if (collectionId) {
     delete seq[requestId][collectionId];
   }
+
   if (!Object.keys(seq[requestId]).length) {
     delete seq[requestId];
   }
 };
🤖 Prompt for AI Agents
In packages/bruno-requests/src/ws/ws-client.js around lines 52 to 78, the
clean() function can throw when seq[requestId] is undefined; add guard checks to
exit early if seq[requestId] is missing, only attempt to delete a collection key
if the request entry exists, and only run Object.keys(...) when seq[requestId]
is defined — i.e. at the top of clean return immediately if !seq[requestId],
then if collectionId delete seq[requestId][collectionId] (or check existence
first), and finally if Object.keys(seq[requestId]).length === 0 delete
seq[requestId].


const seq = createSequencer();

class WsClient {
messageQueues = {};
activeConnections = new Map();
Expand Down Expand Up @@ -181,6 +211,7 @@ class WsClient {
message: payload,
messageHexdump: hexdump(payload),
type: 'outgoing',
seq: seq.next(requestId, collectionUid),
timestamp: Date.now()
});
}
Expand All @@ -204,6 +235,7 @@ class WsClient {
if (connectionMeta?.connection) {
connectionMeta.connection.close(code, reason);
this.#removeConnection(requestId);
seq.clean(requestId);
}
}

Expand Down Expand Up @@ -283,7 +315,8 @@ class WsClient {

this.eventCallback('main:ws:open', requestId, collectionUid, {
timestamp: Date.now(),
url: ws.url
url: ws.url,
seq: seq.next(requestId, collectionUid)
});
});

Expand All @@ -294,14 +327,16 @@ class WsClient {
message: `Redirected to ${url}`,
type: 'info',
timestamp: Date.now(),
headers: headers
headers: headers,
seq: seq.next(requestId, collectionUid)
});
});

ws.on('upgrade', (response) => {
this.eventCallback('main:ws:upgrade', requestId, collectionUid, {
type: 'info',
timestamp: Date.now(),
seq: seq.next(requestId, collectionUid),
headers: { ...response.headers }
});
});
Expand All @@ -313,6 +348,7 @@ class WsClient {
message,
messageHexdump: hexdump(Buffer.from(data)),
type: 'incoming',
seq: seq.next(requestId, collectionUid),
timestamp: Date.now()
});
} catch (error) {
Expand All @@ -321,6 +357,7 @@ class WsClient {
message: data.toString(),
messageHexdump: hexdump(data),
type: 'incoming',
seq: seq.next(requestId, collectionUid),
timestamp: Date.now()
});
}
Expand All @@ -330,14 +367,17 @@ class WsClient {
this.eventCallback('main:ws:close', requestId, collectionUid, {
code,
reason: Buffer.from(reason).toString(),
seq: seq.next(requestId, collectionUid),
timestamp: Date.now()
});
seq.clean(requestId, collectionUid);
this.#removeConnection(requestId);
});

ws.on('error', (error) => {
this.eventCallback('main:ws:error', requestId, collectionUid, {
error: error.message,
seq: seq.next(requestId, collectionUid),
timestamp: Date.now()
});
});
Expand All @@ -356,6 +396,7 @@ class WsClient {
this.eventCallback('main:ws:connections-changed', {
type: 'added',
requestId,
seq: seq.next(requestId, collectionUid),
activeConnectionIds: this.getActiveConnectionIds()
});
}
Expand Down