mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-02 08:58:32 +00:00
feat: add variable interpolation support for WebSocket requests (#6064)
* feat: add variable interpolation support for WebSocket requests
- Add WebSocket body interpolation in interpolateVars function
- Interpolate URL, headers, and all messages in request.body.ws array with full variable context
- Refactor sendWsRequest to use main process preparation (removes duplication)
- Add mode property to wsRequest object for proper request type detection
- Ensure consistent variable precedence matching HTTP/gRPC requests
- Centralize all interpolation logic in main process via prepareWsRequest
* Add Playwright tests for WebSocket variable interpolation
- Add tests for URL interpolation (wss://echo.{{url}}.org)
- Add tests for message content interpolation ({"test": "{{data}}"})
- Update test fixtures to use wss://echo.websocket.org echo server
- Add WEBSOCKET_FLOWS.md documentation
- Refactor queueWsMessage to handle variable interpolation in main process
* removed ws flow documentation
* chore: updated the network/index.js file to reduce merge conflicts by moving around code
* fix: added collection and item to WsQueryUrl Editor to fix available variable highlight
* chore: remove unnecessary whitespace in WebSocket event handlers
---------
Co-authored-by: Sid <siddharth@usebruno.com>
This commit is contained in:
committed by
GitHub
parent
2d2a17c90f
commit
8ec1925b9f
@@ -104,6 +104,8 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
|
||||
className="w-full"
|
||||
theme={displayedTheme}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
<div className="flex items-center h-full mr-2 cursor-pointer">
|
||||
<div
|
||||
|
||||
@@ -241,25 +241,45 @@ export const connectWS = async (item, collection, environment, runtimeVariables,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendWsRequest = (item, collection, environment, runtimeVariables) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const ensureConnection = async () => {
|
||||
const connectionStatus = await isWsConnectionActive(item.uid);
|
||||
if (!connectionStatus.isActive) {
|
||||
await connectWS(item, collection, environment, runtimeVariables, { connectOnly: true });
|
||||
}
|
||||
};
|
||||
const { request } = item.draft ? item.draft : item;
|
||||
queueWsMessage(item, collection.uid, request.body.ws[0].content)
|
||||
.then((initialState) => {
|
||||
// Return an initial state object to update the UI
|
||||
// The real response data will be handled by event listeners
|
||||
resolve({
|
||||
...initialState
|
||||
});
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
await ensureConnection();
|
||||
export const sendWsRequest = async (item, collection, environment, runtimeVariables) => {
|
||||
const ensureConnection = async () => {
|
||||
const connectionStatus = await isWsConnectionActive(item.uid);
|
||||
if (!connectionStatus.isActive) {
|
||||
await connectWS(item, collection, environment, runtimeVariables, { connectOnly: true });
|
||||
}
|
||||
};
|
||||
|
||||
await ensureConnection();
|
||||
|
||||
// Use queueWsMessage helper to queue all messages with proper variable interpolation
|
||||
const result = await queueWsMessage(item, collection, environment, runtimeVariables, null);
|
||||
|
||||
if (result.success) {
|
||||
return {};
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to queue messages');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Queues a message to an existing WebSocket connection with variable interpolation
|
||||
* @param {Object} item - The request item
|
||||
* @param {Object} collection - The collection object
|
||||
* @param {Object} environment - The environment variables
|
||||
* @param {Object} runtimeVariables - The runtime variables
|
||||
* @param {string} messageContent - The message content to queue (or null to queue all messages)
|
||||
* @returns {Promise<Object>} - The result of the queue operation
|
||||
*/
|
||||
export const queueWsMessage = async (item, collection, environment, runtimeVariables, messageContent) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:ws:queue-message', {
|
||||
item,
|
||||
collection,
|
||||
environment,
|
||||
runtimeVariables,
|
||||
messageContent
|
||||
}).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -287,20 +307,6 @@ export const startWsConnection = async (item, collection, environment, runtimeVa
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to an existing WebSocket connection
|
||||
* @param {string} requestId - The request ID to send a message to
|
||||
* @param {string} collectionUid - The collection ID the message is for
|
||||
* @param {*} message - The message
|
||||
* @returns {Promise<Object>} - The result of the send operation
|
||||
*/
|
||||
export const queueWsMessage = async (item, collectionUid, message) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:ws:queue-message', item.uid, collectionUid, message).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to an existing WebSocket connection
|
||||
* @param {string} requestId - The request ID to send a message to
|
||||
|
||||
@@ -81,6 +81,26 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
});
|
||||
request.body = JSON.parse(parsed);
|
||||
}
|
||||
// Interpolate WebSocket message body
|
||||
const isWsRequest = request.mode === 'ws';
|
||||
if (isWsRequest && request.body && request.body.ws && Array.isArray(request.body.ws)) {
|
||||
request.body.ws.forEach((message) => {
|
||||
if (message && message.content) {
|
||||
// Try to detect if content is JSON for proper escaping
|
||||
let isJson = false;
|
||||
try {
|
||||
JSON.parse(message.content);
|
||||
isJson = true;
|
||||
} catch (e) {
|
||||
// Not JSON, treat as regular string
|
||||
}
|
||||
|
||||
message.content = _interpolate(message.content, {
|
||||
escapeJSONStrings: isJson
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof contentType === 'string') {
|
||||
/*
|
||||
|
||||
@@ -38,6 +38,8 @@ const prepareWsRequest = async (item, collection, environment, runtimeVariables,
|
||||
mergeScripts(collection, request, requestTreePath, scriptFlow);
|
||||
mergeVars(collection, request, requestTreePath);
|
||||
mergeAuth(collection, request, requestTreePath);
|
||||
request.globalEnvironmentVariables = collection?.globalEnvironmentVariables;
|
||||
request.oauth2CredentialVariables = getFormattedCollectionOauth2Credentials({ oauth2Credentials: collection?.oauth2Credentials });
|
||||
}
|
||||
|
||||
each(get(collectionRoot, 'request.headers', []), (h) => {
|
||||
@@ -65,6 +67,7 @@ const prepareWsRequest = async (item, collection, environment, runtimeVariables,
|
||||
|
||||
let wsRequest = {
|
||||
uid: item.uid,
|
||||
mode: request.body.mode,
|
||||
url: request.url,
|
||||
headers,
|
||||
processEnvVars,
|
||||
@@ -276,15 +279,43 @@ const registerWsEventHandlers = (window) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:ws:queue-message', (event, requestId, collectionUid, message) => {
|
||||
try {
|
||||
wsClient.queueMessage(requestId, collectionUid, message);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error queuing WebSocket message:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
ipcMain.handle('renderer:ws:queue-message',
|
||||
async (event, { item, collection, environment, runtimeVariables, messageContent }) => {
|
||||
try {
|
||||
const itemCopy = cloneDeep(item);
|
||||
const preparedRequest = await prepareWsRequest(itemCopy, collection, environment, runtimeVariables, {});
|
||||
|
||||
// If messageContent is provided, find and queue that specific message (interpolated)
|
||||
// Otherwise, queue all messages
|
||||
if (messageContent !== undefined && messageContent !== null) {
|
||||
// Find the message index in the original request
|
||||
const originalMessages = itemCopy.draft?.request?.body?.ws || itemCopy.request?.body?.ws || [];
|
||||
const messageIndex = originalMessages.findIndex((msg) => msg.content === messageContent);
|
||||
|
||||
if (messageIndex >= 0 && preparedRequest.body?.ws?.[messageIndex]) {
|
||||
// Queue the interpolated version of the specific message
|
||||
wsClient.queueMessage(preparedRequest.uid, collection.uid, preparedRequest.body.ws[messageIndex].content);
|
||||
} else {
|
||||
// Message not found in request body, queue as-is (shouldn't happen in normal flow)
|
||||
wsClient.queueMessage(preparedRequest.uid, collection.uid, messageContent);
|
||||
}
|
||||
} else {
|
||||
// Queue all messages (they are already interpolated by prepareWsRequest -> interpolateVars)
|
||||
if (preparedRequest.body && preparedRequest.body.ws && Array.isArray(preparedRequest.body.ws)) {
|
||||
preparedRequest.body.ws
|
||||
.filter((message) => message && message.content)
|
||||
.forEach((message) => {
|
||||
wsClient.queueMessage(preparedRequest.uid, collection.uid, message.content);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error queuing WebSocket message:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Send a message to an existing WebSocket connection
|
||||
ipcMain.handle('renderer:ws:send-message', (event, requestId, collectionUid, message) => {
|
||||
|
||||
Reference in New Issue
Block a user