fix(proxy): refresh cached PAC content on demand (#8173)

This commit is contained in:
Pooja
2026-06-04 11:59:09 +05:30
committed by GitHub
parent 026dbfb108
commit 8f80230708
4 changed files with 72 additions and 4 deletions

View File

@@ -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}

View File

@@ -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;

View File

@@ -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;
});
};

View File

@@ -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']);
});
});