Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,89 @@
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IconLoader2 } from '@tabler/icons';
import { getSystemProxyVariables } from 'providers/ReduxStore/slices/app';

const SystemProxy = () => {
const dispatch = useDispatch();
const systemProxyVariables = useSelector((state) => state.app.systemProxyVariables);
const { source, http_proxy, https_proxy, no_proxy } = systemProxyVariables || {};
const [isFetching, setIsFetching] = useState(true);
const [error, setError] = useState(null);

const SOURCE_LABELS = {
'environment': 'Environment Variables',
'windows-system': 'Windows System Settings',
'macos-system': 'macOS System Settings',
'linux-system': 'Linux System Settings'
};

useEffect(() => {
dispatch(getSystemProxyVariables())
.then(() => setError(null))
.catch((err) => setError(err.message || String(err)))
.finally(() => setIsFetching(false));
}, [dispatch]);

return (
<>
<div className="mb-3 text-muted system-proxy-settings space-y-4">
<div className="flex items-start justify-start flex-col gap-2 mt-2">
<div className="flex flex-row items-center gap-2">
<div>
<h2 className="text-xs text-gray-900 dark:text-gray-100">
System Proxy {isFetching ? <IconLoader2 className="animate-spin ml-1" size={18} strokeWidth={1.5} /> : null}
</h2>
<small className="text-gray-500 dark:text-gray-400">
Below values are sourced from your system proxy settings.
</small>
</div>
</div>
</div>
{error && (
<div className="mb-2 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded">
<small className="text-red-600 dark:text-red-400">
Error loading system proxy settings: {error}
</small>
</div>
)}
{source && (
<div className="mb-2">
<small className="font-medium flex flex-row gap-2">
<div className="opacity-70 text-xs">
Proxy source:
</div>
<div>
{SOURCE_LABELS[source] || source}
</div>
</small>
</div>
)}
<small>
These values cannot be directly updated in Bruno. Please refer to your OS documentation to update these.
</small>
<div className="flex flex-col justify-start items-start pt-2">
<div className="mb-1 flex items-center">
<label className="settings-label">
http_proxy
</label>
<div className="opacity-80 text-indigo-600 dark:text-indigo-400">{http_proxy || '-'}</div>
</div>
<div className="mb-1 flex items-center">
<label className="settings-label">
https_proxy
</label>
<div className="opacity-80 text-indigo-600 dark:text-indigo-400">{https_proxy || '-'}</div>
</div>
<div className="mb-1 flex items-center">
<label className="settings-label">
no_proxy
</label>
<div className="opacity-80 text-indigo-600 dark:text-indigo-400">{no_proxy || '-'}</div>
</div>
</div>
</div>
</>
);
};

export default SystemProxy;
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import StyledWrapper from './StyledWrapper';
import { useDispatch, useSelector } from 'react-redux';
import { IconEye, IconEyeOff } from '@tabler/icons';
import { useState } from 'react';
import SystemProxy from './SystemProxy';

const ProxySettings = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences);
const systemProxyEnvVariables = useSelector((state) => state.app.systemProxyEnvVariables);
const { http_proxy, https_proxy, no_proxy } = systemProxyEnvVariables || {};
const dispatch = useDispatch();
console.log(preferences);

const proxySchema = Yup.object({
mode: Yup.string().oneOf(['off', 'on', 'system']),
Expand Down Expand Up @@ -162,30 +160,7 @@ const ProxySettings = ({ close }) => {
</div>
{formik?.values?.mode === 'system' ? (
<div className="mb-3 pt-1 text-muted system-proxy-settings">
<small>
Below values are sourced from your system environment variables and cannot be directly updated in Bruno.<br/>
Please refer to your OS documentation to change these values.
</small>
<div className="flex flex-col justify-start items-start pt-2">
<div className="mb-1 flex items-center">
<label className="settings-label" htmlFor="http_proxy">
http_proxy
</label>
<div className="opacity-80">{http_proxy || '-'}</div>
</div>
<div className="mb-1 flex items-center">
<label className="settings-label" htmlFor="https_proxy">
https_proxy
</label>
<div className="opacity-80">{https_proxy || '-'}</div>
</div>
<div className="mb-1 flex items-center">
<label className="settings-label" htmlFor="no_proxy">
no_proxy
</label>
<div className="opacity-80">{no_proxy || '-'}</div>
</div>
</div>
<SystemProxy />
</div>
) : null}
{formik?.values?.mode === 'on' ? (
Expand Down
2 changes: 1 addition & 1 deletion packages/bruno-app/src/components/ResponsePane/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ const ResponsePane = ({ item, collection }) => {
<div className={getTabClassname('response')} role="tab" onClick={() => selectTab('response')}>
Response
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')} data-testid="response-headers-tab">
Headers
{responseHeadersCount > 0 && <sup className="ml-1 font-medium">{responseHeadersCount}</sup>}
</div>
Expand Down
8 changes: 1 addition & 7 deletions packages/bruno-app/src/providers/App/useIpcEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { useEffect } from 'react';
import {
showPreferences,
updateCookies,
updatePreferences,
updateSystemProxyEnvVariables
updatePreferences
} from 'providers/ReduxStore/slices/app';
import {
brunoConfigUpdateEvent,
Expand Down Expand Up @@ -164,10 +163,6 @@ const useIpcEvents = () => {
dispatch(updatePreferences(val));
});

const removeSystemProxyEnvUpdatesListener = ipcRenderer.on('main:load-system-proxy-env', (val) => {
dispatch(updateSystemProxyEnvVariables(val));
});

const removeCookieUpdateListener = ipcRenderer.on('main:cookies-update', (val) => {
dispatch(updateCookies(val));
});
Expand Down Expand Up @@ -218,7 +213,6 @@ const useIpcEvents = () => {
removeShowPreferencesListener();
removePreferencesUpdatesListener();
removeCookieUpdateListener();
removeSystemProxyEnvUpdatesListener();
removeGlobalEnvironmentsUpdatesListener();
removeSnapshotHydrationListener();
removeCollectionOauth2CredentialsUpdatesListener();
Expand Down
22 changes: 17 additions & 5 deletions packages/bruno-app/src/providers/ReduxStore/slices/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const initialState = {
},
cookies: [],
taskQueue: [],
systemProxyEnvVariables: {},
clipboard: {
hasCopiedItems: false // Whether clipboard has Bruno data (for UI)
}
},
systemProxyVariables: {}
};

export const appSlice = createSlice({
Expand Down Expand Up @@ -90,8 +90,8 @@ export const appSlice = createSlice({
removeAllTasksFromQueue: (state) => {
state.taskQueue = [];
},
updateSystemProxyEnvVariables: (state, action) => {
state.systemProxyEnvVariables = action.payload;
updateSystemProxyVariables: (state, action) => {
state.systemProxyVariables = action.payload;
},
updateGenerateCode: (state, action) => {
state.generateCode = {
Expand Down Expand Up @@ -123,7 +123,7 @@ export const {
insertTaskIntoQueue,
removeTaskFromQueue,
removeAllTasksFromQueue,
updateSystemProxyEnvVariables,
updateSystemProxyVariables,
updateGenerateCode,
toggleSidebarCollapse,
setClipboard
Expand Down Expand Up @@ -198,4 +198,16 @@ export const copyRequest = (item) => (dispatch, getState) => {
return Promise.resolve();
};

export const getSystemProxyVariables = () => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('renderer:get-system-proxy-variables')
.then((variables) => {
dispatch(updateSystemProxyVariables(variables));
return variables;
})
.then(resolve).catch(reject);
});
};

export default appSlice.reducer;
59 changes: 34 additions & 25 deletions packages/bruno-cli/src/runner/run-single-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const { HttpProxyAgent } = require('http-proxy-agent');
const { SocksProxyAgent } = require('socks-proxy-agent');
const { makeAxiosInstance } = require('../utils/axios-instance');
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
const { shouldUseProxy, PatchedHttpsProxyAgent, getSystemProxyEnvVariables } = require('../utils/proxy-util');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
const { getSystemProxy } = require('@usebruno/requests');
const path = require('path');
const { parseDataFromResponse } = require('../utils/common');
const { getCookieStringForUrl, saveCookies } = require('../utils/cookies');
Expand Down Expand Up @@ -301,7 +302,8 @@ const runSingleRequest = async function (
proxyMode = 'on';
} else if (collectionProxyEnabled === 'global') {
// If collection proxy is set to 'global', use system proxy
const { http_proxy, https_proxy } = getSystemProxyEnvVariables();
const systemProxy = await getSystemProxy();
const { http_proxy, https_proxy } = systemProxy;
if (http_proxy?.length || https_proxy?.length) {
proxyMode = 'system';
}
Expand Down Expand Up @@ -346,33 +348,40 @@ const runSingleRequest = async function (
});
}
} else if (proxyMode === 'system') {
const { http_proxy, https_proxy, no_proxy } = getSystemProxyEnvVariables();
const shouldUseSystemProxy = shouldUseProxy(request.url, no_proxy || '');
if (shouldUseSystemProxy) {
try {
if (http_proxy?.length) {
new URL(http_proxy);
request.httpAgent = new HttpProxyAgent(http_proxy);
try {
const systemProxy = await getSystemProxy();
const { http_proxy, https_proxy, no_proxy } = systemProxy;
const shouldUseSystemProxy = shouldUseProxy(request.url, no_proxy || '');
const parsedUrl = new URL(request.url);
const isHttpsRequest = parsedUrl.protocol === 'https:';
if (shouldUseSystemProxy) {
try {
if (http_proxy?.length && !isHttpsRequest) {
new URL(http_proxy);
request.httpAgent = new HttpProxyAgent(http_proxy);
}
} catch (error) {
throw new Error('Invalid system http_proxy');
}
} catch (error) {
throw new Error('Invalid system http_proxy');
}
try {
if (https_proxy?.length) {
new URL(https_proxy);
request.httpsAgent = new PatchedHttpsProxyAgent(
https_proxy,
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
);
} else {
request.httpsAgent = new https.Agent({
...httpsAgentRequestFields
try {
if (https_proxy?.length && isHttpsRequest) {
new URL(https_proxy);
request.httpsAgent = new PatchedHttpsProxyAgent(https_proxy,
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined);
} else {
request.httpsAgent = new https.Agent({
...httpsAgentRequestFields
});
}
} catch (error) {
throw new Error('Invalid system https_proxy');
} catch (error) {
throw new Error('Invalid system https_proxy');
}
} else {
request.httpsAgent = new https.Agent({
...httpsAgentRequestFields
});
}
} else {
} catch (error) {
request.httpsAgent = new https.Agent({
...httpsAgentRequestFields
});
Expand Down
13 changes: 1 addition & 12 deletions packages/bruno-cli/src/utils/proxy-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,7 @@ class PatchedHttpsProxyAgent extends HttpsProxyAgent {
}
}


const getSystemProxyEnvVariables = () => {
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
return {
http_proxy: http_proxy || HTTP_PROXY,
https_proxy: https_proxy || HTTPS_PROXY,
no_proxy: no_proxy || NO_PROXY
};
}

module.exports = {
shouldUseProxy,
PatchedHttpsProxyAgent,
getSystemProxyEnvVariables
PatchedHttpsProxyAgent
};
6 changes: 5 additions & 1 deletion packages/bruno-electron/src/ipc/network/cert-utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const fs = require('node:fs');
const path = require('path');
const { get } = require('lodash');
const { getCACertificates } = require('@usebruno/requests');
const { getCACertificates, getSystemProxy } = require('@usebruno/requests');
const { preferencesUtil } = require('../../store/preferences');
const { getBrunoConfig } = require('../../store/bruno-config');
const { interpolateString } = require('./interpolate-string');
Expand Down Expand Up @@ -113,6 +113,10 @@ const getCertsAndProxyConfig = async ({
} else if (collectionProxyEnabled === 'global') {
proxyConfig = preferencesUtil.getGlobalProxyConfig();
proxyMode = get(proxyConfig, 'mode', 'off');
if (proxyMode === 'system') {
const systemProxyConfig = await getSystemProxy();
proxyConfig = systemProxyConfig;
}
}

return { proxyMode, proxyConfig, httpsAgentRequestFields, interpolationOptions };
Expand Down
13 changes: 7 additions & 6 deletions packages/bruno-electron/src/ipc/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@ const { getPreferences, savePreferences, preferencesUtil } = require('../store/p
const { isDirectory } = require('../utils/filesystem');
const { openCollection } = require('../app/collections');
const { globalEnvironmentsStore } = require('../store/global-environments');
``;
const { getSystemProxy } = require('@usebruno/requests');

const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
ipcMain.handle('renderer:ready', async (event) => {
// load preferences
const preferences = getPreferences();
mainWindow.webContents.send('main:load-preferences', preferences);

// load system proxy vars
const systemProxyVars = preferencesUtil.getSystemProxyEnvVariables();
const { http_proxy, https_proxy, no_proxy } = systemProxyVars || {};
mainWindow.webContents.send('main:load-system-proxy-env', { http_proxy, https_proxy, no_proxy });

try {
// load global environments
const globalEnvironments = globalEnvironmentsStore.getGlobalEnvironments();
Expand Down Expand Up @@ -52,6 +48,11 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
return Promise.reject(error);
}
});

ipcMain.handle('renderer:get-system-proxy-variables', async () => {
const systemProxyConfig = await getSystemProxy();
return systemProxyConfig;
});
Comment on lines +51 to +55
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling to prevent unhandled rejection.

If getSystemProxy() throws (e.g., detection fails on all platforms), the renderer receives an unhandled promise rejection. Wrap in try/catch to return a graceful fallback or propagate a meaningful error.

   ipcMain.handle('renderer:get-system-proxy-variables', async () => {
-    const systemProxyConfig = await getSystemProxy();
-    return systemProxyConfig;
+    try {
+      const systemProxyConfig = await getSystemProxy();
+      return systemProxyConfig;
+    } catch (error) {
+      console.error('Failed to get system proxy:', error);
+      return {
+        http_proxy: null,
+        https_proxy: null,
+        no_proxy: null,
+        source: 'environment'
+      };
+    }
   });
🤖 Prompt for AI Agents
In packages/bruno-electron/src/ipc/preferences.js around lines 51 to 55, the
ipcMain handler calls getSystemProxy() without error handling which can produce
an unhandled rejection; wrap the await call in a try/catch, log or process the
error inside the catch, and then either return a safe fallback (e.g., null or an
empty config) or throw a meaningful Error that the renderer can handle so the
promise always resolves or rejects with a controlled value and no unhandled
rejection occurs.

};

module.exports = registerPreferencesIpc;
Loading
Loading