diff --git a/packages/bruno-app/src/components/Preferences/ProxySettings/SystemProxy/index.js b/packages/bruno-app/src/components/Preferences/ProxySettings/SystemProxy/index.js index 0d21aebbd..6c6a39e88 100644 --- a/packages/bruno-app/src/components/Preferences/ProxySettings/SystemProxy/index.js +++ b/packages/bruno-app/src/components/Preferences/ProxySettings/SystemProxy/index.js @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { IconLoader2 } from '@tabler/icons'; -import { getSystemProxyVariables } from 'providers/ReduxStore/slices/app'; +import { IconLoader2, IconRefresh } from '@tabler/icons'; +import { getSystemProxyVariables, refreshSystemProxy } from 'providers/ReduxStore/slices/app'; import StyledWrapper from '../StyledWrapper'; const SystemProxy = () => { @@ -11,21 +11,32 @@ const SystemProxy = () => { const [isFetching, setIsFetching] = useState(true); const [error, setError] = useState(null); - useEffect(() => { - dispatch(getSystemProxyVariables()) + const fetchProxy = (forceRefresh = false) => { + setIsFetching(true); + setError(null); + const action = forceRefresh ? refreshSystemProxy : getSystemProxyVariables; + dispatch(action()) .then(() => setError(null)) .catch((err) => setError(err.message || String(err))) .finally(() => setIsFetching(false)); + }; + + useEffect(() => { + fetchProxy(false); }, [dispatch]); + const handleRefresh = () => { + fetchProxy(true); + }; + return (
-

- System Proxy {isFetching ? : null} +

+ System Proxy {isFetching ? : null}

Below values are sourced from your system proxy settings. @@ -75,6 +86,13 @@ const SystemProxy = () => {
{no_proxy || '-'}

+ + + Refresh +
); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index d70f6c0a1..8bae1ab6d 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -248,4 +248,16 @@ export const getSystemProxyVariables = () => (dispatch, getState) => { }); }; +export const refreshSystemProxy = () => (dispatch, getState) => { + return new Promise((resolve, reject) => { + const { ipcRenderer } = window; + ipcRenderer.invoke('renderer:refresh-system-proxy') + .then((variables) => { + dispatch(updateSystemProxyVariables(variables)); + return variables; + }) + .then(resolve).catch(reject); + }); +}; + export default appSlice.reducer; diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 9691df431..adbdf5777 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -18,6 +18,7 @@ const constants = require('../constants'); const { findItemInCollection, createCollectionJsonFromPathname, getCallStack, FORMAT_CONFIG } = require('../utils/collection'); const { hasExecutableTestInScript } = require('../utils/request'); const { createSkippedFileResults } = require('../utils/run'); +const { getSystemProxy } = require('@usebruno/requests'); const command = 'run [paths...]'; const desc = 'Run one or more requests/folders'; @@ -608,6 +609,15 @@ const handler = async function (argv) { const runtime = getJsSandboxRuntime(sandbox); + // Fetch system proxy once for all requests (skip if --noproxy flag is set) + if (!noproxy) { + try { + options['cachedSystemProxy'] = await getSystemProxy(); + } catch (error) { + console.warn(chalk.yellow('Failed to detect system proxy, continuing without system proxy')); + } + } + const runSingleRequestByPathname = async (relativeItemPathname) => { const ext = FORMAT_CONFIG[collection.format].ext; return new Promise(async (resolve, reject) => { diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index da813c623..701eec689 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -15,7 +15,6 @@ const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('../utils/axios-instance'); const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper'); 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'); @@ -241,6 +240,7 @@ const runSingleRequest = async function ( const options = getOptions(); const insecure = get(options, 'insecure', false); const noproxy = get(options, 'noproxy', false); + const cachedSystemProxy = get(options, 'cachedSystemProxy', null); const httpsAgentRequestFields = {}; if (insecure) { @@ -310,10 +310,11 @@ const runSingleRequest = async function ( proxyMode = 'on'; } else if (!collectionProxyDisabled && collectionProxyInherit) { // Inherit from system proxy - const systemProxy = await getSystemProxy(); - const { http_proxy, https_proxy } = systemProxy; - if (http_proxy?.length || https_proxy?.length) { - proxyMode = 'system'; + if (cachedSystemProxy) { + const { http_proxy, https_proxy } = cachedSystemProxy; + if (http_proxy?.length || https_proxy?.length) { + proxyMode = 'system'; + } } // else: no system proxy available, proxyMode stays 'off' } @@ -357,8 +358,7 @@ const runSingleRequest = async function ( } } else if (proxyMode === 'system') { try { - const systemProxy = await getSystemProxy(); - const { http_proxy, https_proxy, no_proxy } = systemProxy; + const { http_proxy, https_proxy, no_proxy } = cachedSystemProxy || {}; const shouldUseSystemProxy = shouldUseProxy(request.url, no_proxy || ''); const parsedUrl = new URL(request.url); const isHttpsRequest = parsedUrl.protocol === 'https:'; @@ -505,7 +505,7 @@ const runSingleRequest = async function ( const proxyConfig = get(brunoConfig, 'proxy'); const interpolatedClientCertificates = clientCertificates ? interpolateObject(clientCertificates, oauth2InterpolationOptions) : undefined; const interpolatedProxyConfig = proxyConfig ? interpolateObject(proxyConfig, oauth2InterpolationOptions) : undefined; - const systemProxyConfig = await getSystemProxy(); + const systemProxyConfig = cachedSystemProxy; const { httpAgent: oauth2HttpAgent, httpsAgent: oauth2HttpsAgent } = await getHttpHttpsAgents({ requestUrl: oauth2RequestUrl, diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index c567f48c1..6e5f05e3e 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -171,6 +171,12 @@ app.on('ready', async () => { } } + // Initialize system proxy cache early (non-blocking) + const { initializeSystemProxy } = require('./store/system-proxy'); + initializeSystemProxy().catch((err) => { + console.warn('Failed to initialize system proxy cache:', err); + }); + Menu.setApplicationMenu(menu); const { maximized, x, y, width, height } = loadWindowState(); diff --git a/packages/bruno-electron/src/ipc/network/cert-utils.js b/packages/bruno-electron/src/ipc/network/cert-utils.js index def250f14..9fb752792 100644 --- a/packages/bruno-electron/src/ipc/network/cert-utils.js +++ b/packages/bruno-electron/src/ipc/network/cert-utils.js @@ -1,9 +1,10 @@ const fs = require('node:fs'); const path = require('path'); const { get } = require('lodash'); -const { getCACertificates, getSystemProxy } = require('@usebruno/requests'); +const { getCACertificates } = require('@usebruno/requests'); const { preferencesUtil } = require('../../store/preferences'); const { getBrunoConfig } = require('../../store/bruno-config'); +const { getCachedSystemProxy } = require('../../store/system-proxy'); const { interpolateString } = require('./interpolate-string'); /** @@ -142,10 +143,10 @@ const getCertsAndProxyConfig = async ({ proxyConfig = globalProxyConfigData; proxyMode = 'on'; } else if (!globalDisabled && globalInherit) { - // Use system proxy + // Use system proxy (cached at app startup) proxyMode = 'system'; - const systemProxyConfig = await getSystemProxy(); - proxyConfig = systemProxyConfig; + const systemProxyConfig = getCachedSystemProxy(); + proxyConfig = systemProxyConfig || { http_proxy: null, https_proxy: null, no_proxy: null, source: 'cache-miss' }; } // else: global proxy is disabled, proxyMode stays 'off' } diff --git a/packages/bruno-electron/src/ipc/preferences.js b/packages/bruno-electron/src/ipc/preferences.js index 607eec6ad..07deec517 100644 --- a/packages/bruno-electron/src/ipc/preferences.js +++ b/packages/bruno-electron/src/ipc/preferences.js @@ -1,7 +1,7 @@ const { ipcMain, nativeTheme } = require('electron'); const { getPreferences, savePreferences } = require('../store/preferences'); const { globalEnvironmentsStore } = require('../store/global-environments'); -const { getSystemProxy } = require('@usebruno/requests'); +const { getCachedSystemProxy, refreshSystemProxy } = require('../store/system-proxy'); const registerPreferencesIpc = (mainWindow) => { ipcMain.handle('renderer:ready', async (event) => { @@ -40,8 +40,17 @@ const registerPreferencesIpc = (mainWindow) => { }); ipcMain.handle('renderer:get-system-proxy-variables', async () => { - const systemProxyConfig = await getSystemProxy(); - return systemProxyConfig; + // Return cached value (initialized at app startup) + const cachedProxy = getCachedSystemProxy(); + if (cachedProxy) { + return cachedProxy; + } + // Fallback: refresh if cache is empty (shouldn't happen normally) + return await refreshSystemProxy(); + }); + + ipcMain.handle('renderer:refresh-system-proxy', async () => { + return await refreshSystemProxy(); }); }; diff --git a/packages/bruno-electron/src/store/system-proxy.js b/packages/bruno-electron/src/store/system-proxy.js new file mode 100644 index 000000000..ee1ccde4c --- /dev/null +++ b/packages/bruno-electron/src/store/system-proxy.js @@ -0,0 +1,39 @@ +const { getSystemProxy } = require('@usebruno/requests'); + +let cachedSystemProxy = null; + +const initializeSystemProxy = async () => { + try { + cachedSystemProxy = await getSystemProxy(); + return cachedSystemProxy; + } catch (error) { + console.error('Failed to initialize system proxy:', error); + cachedSystemProxy = { + http_proxy: null, + https_proxy: null, + no_proxy: null, + source: 'error' + }; + return cachedSystemProxy; + } +}; + +const refreshSystemProxy = async () => { + try { + cachedSystemProxy = await getSystemProxy(); + return cachedSystemProxy; + } catch (error) { + console.error('Failed to refresh system proxy:', error); + throw error; + } +}; + +const getCachedSystemProxy = () => { + return cachedSystemProxy; +}; + +module.exports = { + initializeSystemProxy, + refreshSystemProxy, + getCachedSystemProxy +};