fix: #1884 - Fixes infinite loading issue for text/event-stream requests (#4472)

* #1884 - Add support for text/event-stream content-type

* #1884 - Fix bugs with streaming

Fix bug when streaming response is not ok
Fix bug when clearing response of streaming request
Show text signaling that the response is being streamed in the reponse status
Update response size when new data is streamed in

* #1884 - Fix multiple requests when spamming send button

* #1884 - Add time counter for streamed response and fix final time

* #1884 - Run post script only at end of streamed request

* #1884 - add support for automatic "upgrade" to streaming data

* #1884 - adjustments for stopwatch in stream implementation and remove unused imports

* #1884 - fix imports indentation in useIpcEvents.js

* #1884 - remove stream data ended export function from collections

---------

Co-authored-by: Siddharth Gelera <ahoy@barelyhuman.dev>
This commit is contained in:
DaviXavier
2025-11-12 07:26:26 -03:00
committed by GitHub
parent 631b05330d
commit fc5093eab4
10 changed files with 472 additions and 327 deletions

View File

@@ -5,7 +5,7 @@ import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/sli
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import HttpMethodSelector from './HttpMethodSelector';
import { useTheme } from 'providers/Theme';
import { IconDeviceFloppy, IconArrowRight, IconCode } from '@tabler/icons';
import { IconDeviceFloppy, IconArrowRight, IconCode, IconX } from '@tabler/icons';
import SingleLineEditor from 'components/SingleLineEditor';
import { isMacOS } from 'utils/common/platform';
import { hasRequestChanges } from 'utils/collections';
@@ -87,7 +87,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
<div className="flex items-center justify-center h-full w-16">
<span className="text-xs text-indigo-500 font-bold">gRPC</span>
</div>
) : (
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
)}
@@ -149,7 +149,13 @@ const QueryUrl = ({ item, collection, handleRun }) => {
Save <span className="shortcut">({saveShortcut})</span>
</span>
</div>
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} data-testid="send-arrow-icon" />
{
item.response?.hasStreamRunning ? (
<IconX color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
) : (
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} data-testid="send-arrow-icon" />
)
}
</div>
</div>
{generateCodeItemModalOpen && (

View File

@@ -10,7 +10,7 @@ import GrpcResponsePane from 'components/ResponsePane/GrpcResponsePane';
import Welcome from 'components/Welcome';
import { findItemInCollection } from 'utils/collections';
import { updateRequestPaneTabWidth } from 'providers/ReduxStore/slices/tabs';
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import { cancelRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import RequestNotFound from './RequestNotFound';
import QueryUrl from 'components/RequestPane/QueryUrl/index';
import GrpcQueryUrl from 'components/RequestPane/GrpcQueryUrl/index';
@@ -74,8 +74,7 @@ const RequestTabPanel = () => {
const screenWidth = useSelector((state) => state.app.screenWidth);
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
const [leftPaneWidth, setLeftPaneWidth] = useState(
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
); // 2.2 is intentional to make both panes appear to be of equal width
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2); // 2.2 is intentional to make both panes appear to be of equal width
const [topPaneHeight, setTopPaneHeight] = useState(focusedTab?.requestPaneHeight || MIN_TOP_PANE_HEIGHT);
const [dragging, setDragging] = useState(false);
const dragOffset = useRef({ x: 0, y: 0 });
@@ -141,12 +140,10 @@ const RequestTabPanel = () => {
setDragging(false);
if (!isVerticalLayout) {
const mainRect = mainSectionRef.current.getBoundingClientRect();
dispatch(
updateRequestPaneTabWidth({
uid: activeTabUid,
requestPaneWidth: e.clientX - mainRect.left
})
);
dispatch(updateRequestPaneTabWidth({
uid: activeTabUid,
requestPaneWidth: e.clientX - mainRect.left
}));
}
}
};
@@ -263,11 +260,17 @@ const RequestTabPanel = () => {
return;
}
dispatch(sendRequest(item, collection.uid)).catch((err) =>
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
duration: 5000
})
);
if (item.response?.hasStreamRunning) {
dispatch(cancelRequest(item.cancelTokenUid, item, collection)).catch((err) =>
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
duration: 5000
}));
} else if (item.requestState !== 'sending' && item.requestState !== 'queued') {
dispatch(sendRequest(item, collection.uid)).catch((err) =>
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
duration: 5000
}));
}
};
// TODO: reaper, improve selection of panes

View File

@@ -0,0 +1,10 @@
import styled from 'styled-components';
const Wrapper = styled.div`
font-size: 0.75rem;
font-weight: 600;
color: ${(props) => props.theme.requestTabPanel.responseStatus};
text-align: center;
`;
export default Wrapper;

View File

@@ -0,0 +1,27 @@
import React, { useState, useEffect } from 'react';
import StyledWrapper from './StyledWrapper';
const ResponseStopWatch = ({ startMillis }) => {
const [milliseconds, setMilliseconds] = useState(startMillis);
const tickInterval = 100;
const tick = () => {
setMilliseconds(_milliseconds => _milliseconds + tickInterval);
};
useEffect(() => {
let timerID = setInterval(() => {
tick()
}, tickInterval);
return () => {
clearTimeout(timerID);
};
}, []);
let seconds = milliseconds / 1000;
let secondsFormatted = `${seconds.toFixed(1)}s`;
let width = secondsFormatted.length * 0.4; // Calculate width manually to stop parent layout from "flickering" by changing width too fast
return <StyledWrapper className="ml-4" style={{width: `${width}rem`}}>{secondsFormatted}</StyledWrapper>;
};
export default React.memo(ResponseStopWatch);

View File

@@ -4,7 +4,7 @@ import statusCodePhraseMap from './get-status-code-phrase';
import StyledWrapper from './StyledWrapper';
// Todo: text-error class is not getting pulled in for 500 errors
const StatusCode = ({ status, statusText }) => {
const StatusCode = ({ status, statusText, isStreaming }) => {
const getTabClassname = (status) => {
return classnames('ml-2', {
'text-ok': status >= 100 && status < 200,
@@ -17,7 +17,7 @@ const StatusCode = ({ status, statusText }) => {
return (
<StyledWrapper className={`response-status-code ${getTabClassname(status)}`} data-testid="response-status-code">
{status} {statusText || statusCodePhraseMap[status]}
{status} {statusText || statusCodePhraseMap[status]} {isStreaming ? ' - STREAMING' : null}
</StyledWrapper>
);
};

View File

@@ -21,6 +21,8 @@ import ResponseClear from 'src/components/ResponsePane/ResponseClear';
import ResponseBookmark from 'src/components/ResponsePane/ResponseBookmark';
import SkippedRequest from './SkippedRequest';
import ClearTimeline from './ClearTimeline/index';
import StopWatch from 'components/StopWatch';
import ResponseStopWatch from 'components/ResponsePane/ResponseStopWatch';
import ResponseLayoutToggle from './ResponseLayoutToggle';
import HeightBoundContainer from 'ui/HeightBoundContainer';
@@ -87,15 +89,17 @@ const ResponsePane = ({ item, collection }) => {
return <ResponseHeaders headers={response.headers} />;
}
case 'timeline': {
return <Timeline collection={collection} item={item} />;
return <Timeline collection={collection} item={item} />;
}
case 'tests': {
return <TestResults
results={item.testResults}
assertionResults={item.assertionResults}
preRequestTestResults={item.preRequestTestResults}
postResponseTestResults={item.postResponseTestResults}
/>;
return (
<TestResults
results={item.testResults}
assertionResults={item.assertionResults}
preRequestTestResults={item.preRequestTestResults}
postResponseTestResults={item.postResponseTestResults}
/>
);
}
default: {
@@ -184,7 +188,10 @@ const ResponsePane = ({ item, collection }) => {
<ResponseClear item={item} collection={collection} />
<ResponseSave item={item} />
<ResponseBookmark item={item} collection={collection} responseSize={responseSize} />
<StatusCode status={response.status} />
<StatusCode status={response.status} isStreaming={item.response?.hasStreamRunning} />
{item.response?.hasStreamRunning ? (
<ResponseStopWatch startMillis={response.duration} />
) : <ResponseTime duration={response.duration} />}
<ResponseTime duration={response.duration} />
<ResponseSize size={responseSize} />
</>
@@ -193,7 +200,7 @@ const ResponsePane = ({ item, collection }) => {
) : null}
</div>
<section
className={`flex flex-col min-h-0 relative px-4 auto overflow-auto`}
className="flex flex-col min-h-0 relative px-4 auto overflow-auto"
style={{
flex: '1 1 0',
height: hasScriptError && showScriptErrorCard ? 'auto' : '100%'
@@ -206,9 +213,9 @@ const ResponsePane = ({ item, collection }) => {
onClose={() => setShowScriptErrorCard(false)}
/>
)}
<div className='flex-1 overflow-y-auto'>
<div className="flex-1 overflow-y-auto">
{!item?.response ? (
focusedTab?.responsePaneTab === "timeline" && requestTimeline?.length ? (
focusedTab?.responsePaneTab === 'timeline' && requestTimeline?.length ? (
<Timeline
collection={collection}
item={item}

View File

@@ -15,9 +15,11 @@ import {
collectionUnlinkEnvFileEvent,
collectionUnlinkFileEvent,
processEnvUpdateEvent,
requestCancelled,
runFolderEvent,
runRequestEvent,
scriptEnvironmentUpdateEvent
scriptEnvironmentUpdateEvent,
streamDataReceived
} from 'providers/ReduxStore/slices/collections';
import { collectionAddEnvFileEvent, openCollectionEvent, hydrateCollectionWithUiStateSnapshot, mergeAndPersistEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
@@ -137,8 +139,8 @@ const useIpcEvents = () => {
dispatch(processEnvUpdateEvent(val));
});
const removeConsoleLogListener = ipcRenderer.on('main:console-log', (val) => {
console[val.type](...val.args);
const removeConsoleLogListener = ipcRenderer.on('main:console-log', (val) => {
console[val.type](...val.args);
dispatch(addLog({
type: val.type,
args: val.args,
@@ -188,6 +190,14 @@ const useIpcEvents = () => {
dispatch(collectionAddOauth2CredentialsByUrl(payload));
});
const removeHttpStreamNewDataListener = ipcRenderer.on('main:http-stream-new-data', (val) => {
dispatch(streamDataReceived(val));
});
const removeHttpStreamEndListener = ipcRenderer.on('main:http-stream-end', (val) => {
dispatch(requestCancelled(val));
});
const removeCollectionLoadingStateListener = ipcRenderer.on('main:collection-loading-state-updated', (val) => {
dispatch(updateCollectionLoadingState(val));
});
@@ -212,6 +222,8 @@ const useIpcEvents = () => {
removeGlobalEnvironmentsUpdatesListener();
removeSnapshotHydrationListener();
removeCollectionOauth2CredentialsUpdatesListener();
removeHttpStreamNewDataListener();
removeHttpStreamEndListener();
removeCollectionLoadingStateListener();
removePersistentEnvVariablesUpdateListener();
removeSystemResourcesListener();

View File

@@ -83,8 +83,8 @@ const initiatedGrpcResponse = {
isError: false,
duration: 0,
responses: [],
timestamp: Date.now(),
}
timestamp: Date.now()
};
const initiatedWsResponse = {
status: 'PENDING',
@@ -380,7 +380,15 @@ export const collectionsSlice = createSlice({
if (collection) {
const item = findItemInCollection(collection, itemUid);
if (item) {
item.response = null;
if (item.response?.hasStreamRunning) {
item.response.hasStreamRunning = null;
const startTimestamp = item.requestSent.timestamp;
item.response.duration = startTimestamp ? Date.now() - startTimestamp : item.response.duration;
} else {
item.response = null;
}
item.cancelTokenUid = null;
item.requestUid = null;
item.requestStartTime = null;
@@ -389,22 +397,22 @@ export const collectionsSlice = createSlice({
},
responseReceived: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item) {
item.requestState = 'received';
item.response = action.payload.response;
item.cancelTokenUid = null;
item.cancelTokenUid = item.response.hasStreamRunning ? item.cancelTokenUid : null;
item.requestStartTime = null;
if (!collection.timeline) {
collection.timeline = [];
}
// Ensure timestamp is a number (milliseconds since epoch)
const timestamp = item?.requestSent?.timestamp instanceof Date
? item.requestSent.timestamp.getTime()
const timestamp = item?.requestSent?.timestamp instanceof Date
? item.requestSent.timestamp.getTime()
: item?.requestSent?.timestamp || Date.now();
// Append the new timeline entry with numeric timestamp
@@ -427,7 +435,7 @@ export const collectionsSlice = createSlice({
const { itemUid, collectionUid, eventType, eventData } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (!collection) return;
const item = findItemInCollection(collection, itemUid);
if (!item) return;
const request = item.draft ? item.draft.request : item.request;
@@ -447,7 +455,7 @@ export const collectionsSlice = createSlice({
}
collection.timeline.push({
type: "request",
type: 'request',
eventType: eventType, // Add the specific gRPC event type
collectionUid: collection.uid,
folderUid: null,
@@ -456,36 +464,34 @@ export const collectionsSlice = createSlice({
data: {
request: eventData || item.requestSent || item.request,
timestamp: Date.now(),
eventData: eventData,
eventData: eventData
}
});
},
grpcResponseReceived: (state, action) => {
const { itemUid, collectionUid, eventType, eventData } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (!collection) return;
const item = findItemInCollection(collection, itemUid);
if (!item) return;
// Get current response state or create initial state
const currentResponse = item.response || initiatedGrpcResponse
const currentResponse = item.response || initiatedGrpcResponse;
const timestamp = item?.requestSent?.timestamp;
let updatedResponse = { ...currentResponse, duration: Date.now() - (timestamp || Date.now()) };
// Process based on event type
switch (eventType) {
case 'response':
const { error, res } = eventData;
// Handle error if present
if (error) {
const errorCode = error.code || 2; // Default to UNKNOWN if no code
updatedResponse.error = error.details || 'gRPC error occurred';
updatedResponse.statusCode = errorCode;
updatedResponse.statusText = grpcStatusCodes[errorCode] || 'UNKNOWN';
@@ -494,72 +500,72 @@ export const collectionsSlice = createSlice({
}
// Add response to list
updatedResponse.responses = res
? [...(currentResponse?.responses || []), res]
updatedResponse.responses = res
? [...(currentResponse?.responses || []), res]
: [...(currentResponse?.responses || [])];
break;
case 'metadata':
updatedResponse.headers = eventData.metadata;
updatedResponse.metadata = eventData.metadata;
break;
case 'status':
// Extract status info
const statusCode = eventData.status?.code;
const statusDetails = eventData.status?.details;
const statusMetadata = eventData.status?.metadata;
// Set status based on actual code and details
updatedResponse.statusCode = statusCode;
updatedResponse.statusText = grpcStatusCodes[statusCode] || 'UNKNOWN';
updatedResponse.statusDescription = statusDetails;
updatedResponse.statusDetails = eventData.status;
// Store trailers (status metadata)
if (statusMetadata) {
updatedResponse.trailers = statusMetadata;
}
// Handle error status (non-zero code)
if (statusCode !== 0) {
updatedResponse.isError = true;
updatedResponse.error = statusDetails || `gRPC error with code ${statusCode} (${updatedResponse.statusText})`;
}
break;
case 'error':
// Extract error details
const errorCode = eventData.error?.code || 2; // Default to UNKNOWN if no code
const errorDetails = eventData.error?.details || eventData.error?.message;
const errorMetadata = eventData.error?.metadata;
updatedResponse.isError = true;
updatedResponse.error = errorDetails || 'Unknown gRPC error';
updatedResponse.statusCode = errorCode;
updatedResponse.statusText = grpcStatusCodes[errorCode] || 'UNKNOWN';
updatedResponse.statusDescription = errorDetails;
// Store error metadata as trailers if present
if (errorMetadata) {
updatedResponse.trailers = errorMetadata;
}
break;
case 'end':
state.activeConnections = state.activeConnections.filter(id => id !== itemUid);
state.activeConnections = state.activeConnections.filter((id) => id !== itemUid);
break;
case 'cancel':
updatedResponse.statusCode = 1; // CANCELLED
updatedResponse.statusText = 'CANCELLED';
updatedResponse.statusDescription = 'Stream cancelled by client or server';
state.activeConnections = state.activeConnections.filter(id => id !== itemUid);
state.activeConnections = state.activeConnections.filter((id) => id !== itemUid);
break;
}
item.requestState = 'received';
item.response = updatedResponse;
@@ -570,7 +576,7 @@ export const collectionsSlice = createSlice({
// Append the new timeline entry with specific gRPC event type
collection.timeline.push({
type: "request",
type: 'request',
eventType: eventType, // Add the specific gRPC event type
collectionUid: collection.uid,
folderUid: null,
@@ -580,7 +586,7 @@ export const collectionsSlice = createSlice({
request: item.requestSent || item.request,
response: updatedResponse,
eventData: eventData, // Store the original event data
timestamp: Date.now(),
timestamp: Date.now()
}
});
},
@@ -590,6 +596,12 @@ export const collectionsSlice = createSlice({
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item) {
if (item.response && item.response.hasStreamRunning) {
item.response.data = '';
item.response.size = 0;
return;
}
item.response = null;
}
}
@@ -916,7 +928,7 @@ export const collectionsSlice = createSlice({
if (!item.draft) {
item.draft = cloneDeep(item);
}
const existingOtherParams = item.draft.request.params?.filter(p => p.type !== 'query') || [];
const existingOtherParams = item.draft.request.params?.filter((p) => p.type !== 'query') || [];
const newQueryParams = map(params, ({ name = '', value = '', enabled = true }) => ({
uid: uuid(),
name,
@@ -930,9 +942,7 @@ export const collectionsSlice = createSlice({
// Update the request URL to reflect the new query params
const parts = splitOnFirst(item.draft.request.url, '?');
const query = stringifyQueryParams(
filter(item.draft.request.params, (p) => p.enabled && p.type === 'query')
);
const query = stringifyQueryParams(filter(item.draft.request.params, (p) => p.enabled && p.type === 'query'));
// If there are enabled query params, append them to the URL
if (query && query.length) {
@@ -1163,7 +1173,7 @@ export const collectionsSlice = createSlice({
if (!item.draft) {
item.draft = cloneDeep(item);
}
item.draft.request.headers = map(action.payload.headers, ({name = '', value = '', enabled = true}) => ({
item.draft.request.headers = map(action.payload.headers, ({ name = '', value = '', enabled = true }) => ({
uid: uuid(),
name: name,
value: value,
@@ -1205,8 +1215,8 @@ export const collectionsSlice = createSlice({
if (!folder || !isItemAFolder(folder)) {
return;
}
folder.root.request.headers = map(headers, ({name = '', value = '', enabled = true}) => ({
folder.root.request.headers = map(headers, ({ name = '', value = '', enabled = true }) => ({
uid: uuid(),
name: name,
value: value,
@@ -1487,7 +1497,7 @@ export const collectionsSlice = createSlice({
if (!item.draft) {
item.draft = cloneDeep(item);
}
switch (item.draft.request.body.mode) {
case 'json': {
item.draft.request.body.json = action.payload.content;
@@ -1624,7 +1634,7 @@ export const collectionsSlice = createSlice({
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
@@ -1875,7 +1885,7 @@ export const collectionsSlice = createSlice({
break;
case 'ntlm':
set(collection, 'draft.root.request.auth.ntlm', action.payload.content);
break;
break;
case 'oauth2':
set(collection, 'draft.root.request.auth.oauth2', action.payload.content);
break;
@@ -2604,7 +2614,7 @@ export const collectionsSlice = createSlice({
const { requestUid, itemUid, collectionUid } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (!collection) return;
const item = findItemInCollection(collection, itemUid);
if (!item) return;
@@ -2637,7 +2647,7 @@ export const collectionsSlice = createSlice({
item.postResponseScriptErrorMessage = action.payload.errorMessage;
}
if(type === 'test-script-execution') {
if (type === 'test-script-execution') {
item.testScriptErrorMessage = action.payload.errorMessage;
}
@@ -2652,7 +2662,7 @@ export const collectionsSlice = createSlice({
if (type === 'request-sent') {
const { cancelTokenUid, requestSent } = action.payload;
item.requestSent = requestSent;
// sometimes the response is received before the request-sent event arrives
if (item.requestState === 'queued') {
item.requestState = 'sending';
@@ -2669,12 +2679,12 @@ export const collectionsSlice = createSlice({
const { results } = action.payload;
item.testResults = results;
}
if (type === 'test-results-pre-request') {
const { results } = action.payload;
item.preRequestTestResults = results;
}
if (type === 'test-results-post-response') {
const { results } = action.payload;
item.postResponseTestResults = results;
@@ -2788,7 +2798,7 @@ export const collectionsSlice = createSlice({
if (collection) {
collection.runnerResult = null;
collection.runnerTags = { include: [], exclude: [] }
collection.runnerTags = { include: [], exclude: [] };
collection.runnerTagsEnabled = false;
collection.runnerConfiguration = null;
}
@@ -2927,7 +2937,7 @@ export const collectionsSlice = createSlice({
updateFolderAuthMode: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null;
if (folder) {
if (!folder.draft) {
folder.draft = cloneDeep(folder.root);
@@ -2936,7 +2946,16 @@ export const collectionsSlice = createSlice({
set(folder, 'draft.request.auth.mode', action.payload.mode);
}
},
streamDataReceived: (state, action) => {
const { itemUid, collectionUid, data } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (collection) {
const item = findItemInCollection(collection, itemUid);
item.response.data = data.data + (item.response.data || '');
item.response.size = data.data?.length + (item.response.size || 0);
}
},
addRequestTag: (state, action) => {
const { tag, collectionUid, itemUid } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
@@ -2978,7 +2997,7 @@ export const collectionsSlice = createSlice({
updateCollectionTagsList: (state, action) => {
const { collectionUid } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (collection) {
collection.allTags = getUniqueTagsFromItems(collection.items);
}
@@ -3298,6 +3317,7 @@ export const {
updateRequestDocs,
updateFolderDocs,
moveCollection,
streamDataReceived,
collectionAddOauth2CredentialsByUrl,
collectionClearOauth2CredentialsByUrl,
collectionGetOauth2CredentialsByUrl,

View File

@@ -20,7 +20,8 @@ export const sendNetworkRequest = async (item, collection, environment, runtimeV
status: response.status,
statusText: response.statusText,
duration: response.duration,
timeline: response.timeline
timeline: response.timeline,
hasStreamRunning: response.hasStreamRunning
});
})
.catch((err) => reject(err));
@@ -31,19 +32,17 @@ export const sendNetworkRequest = async (item, collection, environment, runtimeV
export const sendGrpcRequest = async (item, collection, environment, runtimeVariables) => {
return new Promise((resolve, reject) => {
startGrpcRequest(item, collection, environment, runtimeVariables)
.then((initialState) => {
// Return an initial state object to update the UI
// The real response data will be handled by event listeners
resolve({
...initialState,
timeline: []
});
})
.catch((err) => reject(err));
.then((initialState) => {
// Return an initial state object to update the UI
// The real response data will be handled by event listeners
resolve({
...initialState,
timeline: []
});
})
.catch((err) => reject(err));
});
}
};
const sendHttpRequest = async (item, collection, environment, runtimeVariables) => {
return new Promise((resolve, reject) => {
@@ -83,19 +82,19 @@ export const startGrpcRequest = async (item, collection, environment, runtimeVar
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
const request = item.draft ? item.draft : item;
ipcRenderer.invoke('grpc:start-connection', {
request,
collection,
environment,
request,
collection,
environment,
runtimeVariables
})
.then(() => {
resolve();
})
.catch(err => {
reject(err);
});
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
});
};
@@ -188,7 +187,7 @@ export const isGrpcConnectionActive = async (connectionId) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('grpc:is-connection-active', connectionId)
.then(response => {
.then((response) => {
if (response.success) {
resolve(response.isActive);
} else {
@@ -197,7 +196,7 @@ export const isGrpcConnectionActive = async (connectionId) => {
resolve(false);
}
})
.catch(err => {
.catch((err) => {
console.error('Failed to check connection status:', err);
// On error, assume the connection is not active
resolve(false);
@@ -215,14 +214,14 @@ export const isGrpcConnectionActive = async (connectionId) => {
export const generateGrpcSampleMessage = async (methodPath, existingMessage = null, options = {}) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('grpc:generate-sample-message', {
methodPath,
existingMessage,
options
ipcRenderer.invoke('grpc:generate-sample-message', {
methodPath,
existingMessage,
options
})
.then(resolve)
.catch(reject);
.then(resolve)
.catch(reject);
});
};