mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix(proxy): refresh cached PAC content on demand (#8173)
This commit is contained in:
@@ -3,11 +3,11 @@ import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import debounce from 'lodash/debounce';
|
||||
import toast from 'react-hot-toast';
|
||||
import { savePreferences } from 'providers/ReduxStore/slices/app';
|
||||
import { savePreferences, refreshPacCache } from 'providers/ReduxStore/slices/app';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { IconEye, IconEyeOff } from '@tabler/icons';
|
||||
import { IconEye, IconEyeOff, IconRefresh } from '@tabler/icons';
|
||||
import { useState } from 'react';
|
||||
import SystemProxy from './SystemProxy';
|
||||
|
||||
@@ -103,6 +103,12 @@ const ProxySettings = ({ close }) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleRefreshPac = () => {
|
||||
dispatch(refreshPacCache())
|
||||
.then(() => toast.success('PAC cache refreshed'))
|
||||
.catch(() => toast.error('Failed to refresh PAC cache'));
|
||||
};
|
||||
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const [proxyMode, setProxyMode] = useState(() => {
|
||||
if (preferences.proxy.disabled) return 'off';
|
||||
@@ -451,6 +457,15 @@ const ProxySettings = ({ close }) => {
|
||||
? 'Enter the URL to your PAC file'
|
||||
: 'Supports .pac files for automatic proxy configuration'}
|
||||
</p>
|
||||
{formik.values.pac.source ? (
|
||||
<span
|
||||
className="text-link cursor-pointer hover:underline flex flex-row items-center w-fit mt-2"
|
||||
onClick={handleRefreshPac}
|
||||
>
|
||||
<IconRefresh size={14} strokeWidth={1.5} className="mr-1" />
|
||||
Refetch
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -378,4 +378,11 @@ export const clearHttpHttpsAgentCache = () => () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const refreshPacCache = () => () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:refresh-pac-cache').then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export default appSlice.reducer;
|
||||
|
||||
@@ -8,7 +8,7 @@ const { resolveDefaultLocation } = require('../utils/default-location');
|
||||
const onboardUser = require('../app/onboarding');
|
||||
const LastOpenedCollections = require('../store/last-opened-collections');
|
||||
const WindowStateStore = require('../store/window-state');
|
||||
const { clearAgentCache } = require('@usebruno/requests');
|
||||
const { clearAgentCache, clearPacCache } = require('@usebruno/requests');
|
||||
|
||||
const registerPreferencesIpc = (mainWindow) => {
|
||||
const lastOpenedCollections = new LastOpenedCollections();
|
||||
@@ -67,6 +67,15 @@ const registerPreferencesIpc = (mainWindow) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:refresh-pac-cache', async () => {
|
||||
try {
|
||||
clearPacCache();
|
||||
clearAgentCache();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('renderer:theme-change', (event, theme, themeBg) => {
|
||||
nativeTheme.themeSource = theme;
|
||||
const windowStateStore = new WindowStateStore();
|
||||
@@ -81,7 +90,10 @@ const registerPreferencesIpc = (mainWindow) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:refresh-system-proxy', async () => {
|
||||
return await fetchSystemProxy({ refresh: true });
|
||||
const variables = await fetchSystemProxy({ refresh: true });
|
||||
clearPacCache();
|
||||
clearAgentCache();
|
||||
return variables;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -278,4 +278,38 @@ describe('pac-resolver (shared)', () => {
|
||||
clearPacCache();
|
||||
expect(_CACHE.size).toBe(0);
|
||||
});
|
||||
|
||||
test('clearPacCache forces a re-read of updated PAC file content on next resolve', async () => {
|
||||
const scriptV1 = 'function FindProxyForURL() { return "PROXY a.example:8080"; }';
|
||||
const scriptV2 = 'function FindProxyForURL() { return "PROXY b.example:9090"; }';
|
||||
const readFileMock = jest.fn().mockResolvedValueOnce(scriptV1).mockResolvedValueOnce(scriptV2);
|
||||
jest.doMock('fs/promises', () => ({ readFile: readFileMock }));
|
||||
jest.doMock('url', () => ({ fileURLToPath: jest.fn(() => '/Users/test/proxy.pac') }));
|
||||
// resolver returns directives based on the exact script it was compiled from
|
||||
jest.doMock('pac-resolver', () => ({
|
||||
createPacResolver: jest.fn((_qjs: any, script: string) =>
|
||||
async () => (script === scriptV1 ? 'PROXY a.example:8080' : 'PROXY b.example:9090')
|
||||
)
|
||||
}));
|
||||
jest.doMock('quickjs-emscripten', () => ({ getQuickJS: jest.fn(async () => ({})) }));
|
||||
|
||||
const { getPacResolver, clearPacCache } = require('./pac-resolver');
|
||||
const pacSource = 'file:///Users/test/proxy.pac';
|
||||
|
||||
const w1 = await getPacResolver({ pacSource });
|
||||
expect(await w1.resolve('http://foo.example/')).toEqual(['PROXY a.example:8080']);
|
||||
expect(readFileMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Without refresh, the cached (stale) content is reused — the file is NOT re-read.
|
||||
const wCached = await getPacResolver({ pacSource });
|
||||
expect(wCached).toBe(w1);
|
||||
expect(readFileMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Refresh clears the cache, so the edited file is re-read and new directives take effect.
|
||||
clearPacCache();
|
||||
const w2 = await getPacResolver({ pacSource });
|
||||
expect(w2).not.toBe(w1);
|
||||
expect(readFileMock).toHaveBeenCalledTimes(2);
|
||||
expect(await w2.resolve('http://foo.example/')).toEqual(['PROXY b.example:9090']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user