diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
index 6cdf4a6d7..7bbc903b2 100644
--- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
+++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
@@ -25,6 +25,14 @@ const ContentIndicator = () => {
);
};
+const ErrorIndicator = () => {
+ return (
+
+
+
+ );
+};
+
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
@@ -143,7 +151,11 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('script')}>
Script
- {(script.req || script.res) && }
+ {(script.req || script.res) && (
+ item.preScriptResponseErrorMessage || item.postResponseScriptErrorMessage ?
+ :
+
+ )}
selectTab('assert')}>
Assert
diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
index b6b8d751d..c1631a358 100644
--- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
@@ -6,11 +6,8 @@ import classnames from 'classnames';
import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common';
import { getCodeMirrorModeBasedOnContentType } from 'utils/common/codemirror';
import QueryResultPreview from './QueryResultPreview';
-
import StyledWrapper from './StyledWrapper';
-import { useState } from 'react';
-import { useMemo } from 'react';
-import { useEffect } from 'react';
+import { useState, useMemo, useEffect } from 'react';
import { useTheme } from 'providers/Theme/index';
import { uuid } from 'utils/common/index';
@@ -62,6 +59,19 @@ const formatResponse = (data, mode, filter) => {
return safeStringifyJSON(data, true);
};
+const formatErrorMessage = (error) => {
+ if (!error) return 'Something went wrong';
+
+ const remoteMethodError = "Error invoking remote method 'send-http-request':";
+
+ if (error.includes(remoteMethodError)) {
+ const parts = error.split(remoteMethodError);
+ return parts[1]?.trim() || error;
+ }
+
+ return error;
+};
+
const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => {
const contentType = getContentType(headers);
const mode = getCodeMirrorModeBasedOnContentType(contentType, data);
@@ -121,6 +131,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
}, [allowedPreviewModes, previewTab]);
const queryFilterEnabled = useMemo(() => mode.includes('json'), [mode]);
+ const hasScriptError = item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage;
return (
{error ? (
-
{error}
+ {hasScriptError ? null :
{formatErrorMessage(error)}
}
{error && typeof error === 'string' && error.toLowerCase().includes('self signed certificate') ? (
@@ -143,24 +154,26 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
) : null}
) : (
- <>
-
- {queryFilterEnabled && (
-
- )}
- >
+
+
+
+ {queryFilterEnabled && (
+
+ )}
+
+
)}
);
diff --git a/packages/bruno-app/src/components/ResponsePane/ScriptError/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ScriptError/StyledWrapper.js
new file mode 100644
index 000000000..c4a38e80f
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ScriptError/StyledWrapper.js
@@ -0,0 +1,55 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ border-left: 4px solid ${(props) => props.theme.colors.text.danger};
+ border-top: 1px solid transparent;
+ border-right: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ border-radius: 0.375rem;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+ max-height: 200px;
+ min-height: 70px;
+ overflow-y: auto;
+ background-color: ${(props) => props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.5)' : 'rgba(250, 250, 250, 0.9)'};
+
+ .error-icon-container {
+ margin-top: 0.125rem;
+ padding: 0.375rem;
+ border-radius: 9999px;
+ background-color: ${(props) => props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.8)' : 'rgba(240, 240, 240, 0.8)'};
+
+ svg {
+ color: ${(props) => props.theme.colors.text.danger};
+ }
+ }
+
+ .close-button {
+ opacity: 0.7;
+ transition: opacity 0.2s;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ svg {
+ color: ${(props) => props.theme.text};
+ }
+ }
+
+ .error-title {
+ font-weight: 600;
+ margin-bottom: 0.375rem;
+ color: ${(props) => props.theme.colors.text.danger};
+ }
+
+ .error-message {
+ font-family: monospace;
+ font-size: 0.6875rem;
+ line-height: 1.25rem;
+ white-space: pre-wrap;
+ word-break: break-all;
+ color: ${(props) => props.theme.text};
+ }
+`;
+
+export default StyledWrapper;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/ResponsePane/ScriptError/index.js b/packages/bruno-app/src/components/ResponsePane/ScriptError/index.js
new file mode 100644
index 000000000..4af07c587
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ScriptError/index.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import { IconX } from '@tabler/icons';
+import StyledWrapper from './StyledWrapper';
+
+
+const ScriptError = ({ item, onClose }) => {
+ const preRequestError = item?.preRequestScriptErrorMessage;
+ const postResponseError = item?.postResponseScriptErrorMessage;
+
+ if (!preRequestError && !postResponseError) return null;
+
+ const errorMessage = preRequestError || postResponseError;
+ const errorTitle = preRequestError ? 'Pre-Request Script Error' : 'Post-Response Script Error';
+
+ return (
+
+
+
+
+ {errorTitle}
+
+
+ {errorMessage}
+
+
+
+
+
+
+
+ );
+};
+
+export default ScriptError;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/ResponsePane/ScriptErrorIcon/index.js b/packages/bruno-app/src/components/ResponsePane/ScriptErrorIcon/index.js
new file mode 100644
index 000000000..208a49ffd
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ScriptErrorIcon/index.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { IconAlertCircle } from '@tabler/icons';
+import ToolHint from 'components/ToolHint';
+
+const ScriptErrorIcon = ({ itemUid, onClick }) => {
+ const toolhintId = `script-error-icon-${itemUid}`;
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default ScriptErrorIcon;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js
index f0df42e3e..dbb3b1a78 100644
--- a/packages/bruno-app/src/components/ResponsePane/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/index.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import find from 'lodash/find';
import classnames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
@@ -13,6 +13,8 @@ import ResponseSize from './ResponseSize';
import Timeline from './Timeline';
import TestResults from './TestResults';
import TestResultsLabel from './TestResultsLabel';
+import ScriptError from './ScriptError';
+import ScriptErrorIcon from './ScriptErrorIcon';
import StyledWrapper from './StyledWrapper';
import ResponseSave from 'src/components/ResponsePane/ResponseSave';
import ResponseClear from 'src/components/ResponsePane/ResponseClear';
@@ -22,6 +24,13 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isLoading = ['queued', 'sending'].includes(item.requestState);
+ const [showScriptErrorCard, setShowScriptErrorCard] = useState(false);
+
+ useEffect(() => {
+ if (item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage) {
+ setShowScriptErrorCard(true);
+ }
+ }, [item?.preRequestScriptErrorMessage, item?.postResponseScriptErrorMessage]);
const selectTab = (tab) => {
dispatch(
@@ -98,6 +107,8 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
};
const responseHeadersCount = typeof response.headers === 'object' ? Object.entries(response.headers).length : 0;
+
+ const hasScriptError = item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage;
return (
@@ -117,6 +128,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
{!isLoading ? (
+ {hasScriptError && !showScriptErrorCard && (
+ setShowScriptErrorCard(true)}
+ />
+ )}
@@ -126,9 +143,15 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
) : null}
{isLoading ? : null}
+ {hasScriptError && showScriptErrorCard && (
+ setShowScriptErrorCard(false)}
+ />
+ )}
{getTabPanel(focusedTab.responsePaneTab)}
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 905576a2f..58efc93e0 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -1853,12 +1853,22 @@ export const collectionsSlice = createSlice({
}
},
runRequestEvent: (state, action) => {
- const { itemUid, collectionUid, type, requestUid } = action.payload;
+ const { itemUid, collectionUid, type, requestUid, hasError } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (collection) {
const item = findItemInCollection(collection, itemUid);
if (item) {
+ if (type === 'pre-request-script-execution') {
+ item.requestUid = requestUid;
+ item.preRequestScriptErrorMessage = action.payload.errorMessage;
+ }
+
+ if(type === 'post-response-script-execution') {
+ item.requestUid = requestUid;
+ item.postResponseScriptErrorMessage = action.payload.errorMessage;
+ }
+
if (type === 'request-queued') {
const { cancelTokenUid } = action.payload;
item.requestUid = requestUid;
diff --git a/packages/bruno-app/src/utils/codemirror/javascript-lint.js b/packages/bruno-app/src/utils/codemirror/javascript-lint.js
index 475686f5d..ec612e5f9 100644
--- a/packages/bruno-app/src/utils/codemirror/javascript-lint.js
+++ b/packages/bruno-app/src/utils/codemirror/javascript-lint.js
@@ -19,6 +19,27 @@ if (!SERVER_RENDERED) {
}
return [];
}
+
+ // Set default options for Bruno
+ const defaultOptions = {
+ esversion: 11,
+ expr: true,
+ asi: true,
+ undef: true,
+ browser: true,
+ devel: true,
+ predef: {
+ 'bru': false,
+ 'req': false,
+ 'res': false,
+ 'test': false,
+ 'expect': false
+ }
+ };
+
+ // Merge provided options with defaults
+ options = Object.assign({}, defaultOptions, options);
+
if (!options.indent)
// JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
options.indent = 1; // JSHint default value is 4
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index 2271a3d33..50b234ca5 100644
--- a/packages/bruno-electron/src/ipc/network/index.js
+++ b/packages/bruno-electron/src/ipc/network/index.js
@@ -410,6 +410,7 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) =>
return { data, dataBuffer };
};
+
const registerNetworkIpc = (mainWindow) => {
const onConsoleLog = (type, args) => {
console[type](...args);
@@ -609,19 +610,39 @@ const registerNetworkIpc = (mainWindow) => {
request.signal = abortController.signal;
saveCancelToken(cancelTokenUid, abortController);
- await runPreRequest(
- request,
- requestUid,
- envVars,
- collectionPath,
- collection,
- collectionUid,
- runtimeVariables,
- processEnvVars,
- scriptingConfig,
- runRequestByItemPathname
- );
+
+ try {
+ await runPreRequest(
+ request,
+ requestUid,
+ envVars,
+ collectionPath,
+ collection,
+ collectionUid,
+ runtimeVariables,
+ processEnvVars,
+ scriptingConfig,
+ runRequestByItemPathname
+ );
+ !runInBackground && mainWindow.webContents.send('main:run-request-event', {
+ type: 'pre-request-script-execution',
+ requestUid,
+ collectionUid,
+ itemUid: item.uid,
+ errorMessage: null,
+ });
+
+ } catch (error) {
+ !runInBackground && mainWindow.webContents.send('main:run-request-event', {
+ type: 'pre-request-script-execution',
+ requestUid,
+ collectionUid,
+ itemUid: item.uid,
+ errorMessage: error?.message || 'An error occurred in pre-request script',
+ });
+ return Promise.reject(error);
+ }
const axiosInstance = await configureRequest(
collectionUid,
request,
@@ -693,19 +714,41 @@ const registerNetworkIpc = (mainWindow) => {
mainWindow.webContents.send('main:cookies-update', safeParseJSON(safeStringifyJSON(domainsWithCookies)));
- await runPostResponse(
- request,
- response,
- requestUid,
- envVars,
- collectionPath,
- collection,
- collectionUid,
- runtimeVariables,
- processEnvVars,
- scriptingConfig,
- runRequestByItemPathname
- );
+ try {
+ await runPostResponse(
+ request,
+ response,
+ requestUid,
+ envVars,
+ collectionPath,
+ collection,
+ collectionUid,
+ runtimeVariables,
+ processEnvVars,
+ scriptingConfig,
+ runRequestByItemPathname
+ );
+ !runInBackground && mainWindow.webContents.send('main:run-request-event', {
+ type: 'post-response-script-execution',
+ requestUid,
+ collectionUid,
+ errorMessage: null,
+ itemUid: item.uid,
+ });
+ } catch (error) {
+ console.error('Post-response script error:', error);
+
+ // Format a more readable error message
+ const errorMessage = error?.message || 'An error occurred in post-response script';
+
+ !runInBackground && mainWindow.webContents.send('main:run-request-event', {
+ type: 'post-response-script-execution',
+ requestUid,
+ errorMessage,
+ collectionUid,
+ itemUid: item.uid,
+ });
+ }
// run assertions
const assertions = get(request, 'assertions');