From 42d93cc4c9a416bf8aaafd318ec816cd8fc75562 Mon Sep 17 00:00:00 2001 From: Harald Fielker Date: Tue, 20 Jan 2026 19:41:59 +0100 Subject: [PATCH 1/2] timeout fix, enable/disable reloads browser --- browser-plugin/src/host/background.ts | 53 +++++++++++++++++----- browser-plugin/src/host/ui/options.tsx | 62 +++++++++++++++++++------- 2 files changed, 89 insertions(+), 26 deletions(-) diff --git a/browser-plugin/src/host/background.ts b/browser-plugin/src/host/background.ts index 05024cc..522d26d 100644 --- a/browser-plugin/src/host/background.ts +++ b/browser-plugin/src/host/background.ts @@ -5,6 +5,10 @@ log.info('Background script running'); const updateAuthRules = (backendUrl: string) => { try { + if (typeof backendUrl !== 'string' || !backendUrl.startsWith('http')) { + log.error('Invalid backendUrl for auth rules:', backendUrl); + return; + } const url = new URL(backendUrl); // Construct a pattern that matches the origin (protocol + host + port) // We match any path under this origin @@ -56,36 +60,63 @@ const updateAuthRules = (backendUrl: string) => { // Initialize rules on startup chrome.storage.local.get(['backendUrl'], (result) => { const backendUrl = result.backendUrl || DEFAULT_BACKEND_URL; - updateAuthRules(backendUrl); + if (typeof backendUrl === 'string') { + updateAuthRules(backendUrl); + } }); // Listen for settings changes chrome.storage.onChanged.addListener((changes, namespace) => { if (namespace === 'local' && changes.backendUrl) { - updateAuthRules(changes.backendUrl.newValue); + const newUrl = changes.backendUrl.newValue; + if (typeof newUrl === 'string') { + updateAuthRules(newUrl); + } } }); chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { if (request.type === 'PROXY_REQ') { - const { url, headers } = request; - log.info(`Proxying request to ${url}`); + const { url, headers, timeout = 5000 } = request; + log.info(`Proxying request to ${url} with timeout ${timeout}ms`); + + let responseSent = false; + const onceSendResponse = (response: any) => { + if (!responseSent) { + responseSent = true; + sendResponse(response); + } + }; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => { + controller.abort(); + onceSendResponse({ ok: false, error: 'Connection timed out' }); + }, timeout); - fetch(url, { headers }) + fetch(url, { headers, signal: controller.signal }) .then(async (response) => { + clearTimeout(timeoutId); if (response.ok) { const data = await response.json(); - return { ok: response.ok, status: response.status, data }; + onceSendResponse({ ok: true, status: response.status, data }); } else { const errorText = await response.text(); - return { ok: response.ok, status: response.status, error: errorText }; + onceSendResponse({ + ok: false, + status: response.status, + error: errorText, + }); } }) - .then((result) => sendResponse(result)) .catch((err) => { - log.error('Proxy request failed:', err); - sendResponse({ ok: false, error: err.toString() }); + clearTimeout(timeoutId); + if (err.name !== 'AbortError') { + log.error('Proxy request failed:', err); + onceSendResponse({ ok: false, error: err.toString() }); + } }); - return true; + + return true; // Indicates an asynchronous response } }); diff --git a/browser-plugin/src/host/ui/options.tsx b/browser-plugin/src/host/ui/options.tsx index a0e37e9..142e613 100644 --- a/browser-plugin/src/host/ui/options.tsx +++ b/browser-plugin/src/host/ui/options.tsx @@ -25,7 +25,9 @@ const Options = () => { getSettings().then((loadedSettings) => { setSettings(loadedSettings); setLoaded(true); - testConnection(loadedSettings); + if (loadedSettings.enabled) { + testConnection(loadedSettings); + } }); }, []); @@ -42,9 +44,6 @@ const Options = () => { const testConnection = async (currentSettings: Settings) => { setStatus('loading'); - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 5000); - try { const headers: HeadersInit = {}; if (currentSettings.username && currentSettings.password) { @@ -56,27 +55,37 @@ const Options = () => { const url = currentSettings.backendUrl.replace(/\/$/, '') + '/api/domains'; - // Use background proxy to avoid browser auth dialog - const response = await chrome.runtime.sendMessage({ - type: 'PROXY_REQ', - url, - headers, + const response = await new Promise((resolve, reject) => { + chrome.runtime.sendMessage( + { type: 'PROXY_REQ', url, headers, timeout: 5000 }, + (res) => { + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + return; + } + resolve(res); + }, + ); }); - if (response.ok) { + if (response && response.ok) { setStatus('success'); await invalidateDomainCache(); + return true; } else { setStatus('error'); + return false; } - } catch { + } catch (e) { setStatus('error'); - } finally { - clearTimeout(timeoutId); + return false; } }; const handleTestClick = () => { + if (status === 'loading') { + return; // Ignore click if test is already in progress + } testConnection(settings); }; @@ -305,11 +314,35 @@ const Options = () => { const newSettings = { ...settings, enabled }; setSettings(newSettings); + // Persist immediately so the reloaded tab sees the new state + await chrome.storage.local.set(newSettings); + if (enabled) { - testConnection(newSettings); + const success = await testConnection(newSettings); + if (success) { + // On successful enable, reload tab and close popup + const [tab] = await chrome.tabs.query({ + active: true, + currentWindow: true, + }); + if (tab?.id && tab.url?.match(/^https?:\/\//)) { + chrome.tabs.reload(tab.id); + } + window.close(); + } + // If not successful, do nothing (window stays open, no reload) } else { + // On disable, invalidate cache, reload tab, and close popup setStatus('idle'); await invalidateDomainCache(); + const [tab] = await chrome.tabs.query({ + active: true, + currentWindow: true, + }); + if (tab?.id && tab.url?.match(/^https?:\/\//)) { + chrome.tabs.reload(tab.id); + } + window.close(); } }} style={{ marginRight: '10px', width: '18px', height: '18px' }} @@ -317,7 +350,6 @@ const Options = () => { Enable Extension - ); }; From 06ede2c9b96831a56824e5830c417639654d26f4 Mon Sep 17 00:00:00 2001 From: Harald Fielker Date: Tue, 20 Jan 2026 19:47:33 +0100 Subject: [PATCH 2/2] linter fix --- browser-plugin/src/host/background.ts | 4 +++- browser-plugin/src/host/ui/options.tsx | 8 +++++--- browser-plugin/src/shared/types.ts | 6 ++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 browser-plugin/src/shared/types.ts diff --git a/browser-plugin/src/host/background.ts b/browser-plugin/src/host/background.ts index 522d26d..f9032f9 100644 --- a/browser-plugin/src/host/background.ts +++ b/browser-plugin/src/host/background.ts @@ -1,5 +1,6 @@ import log from '../shared/logger'; import { DEFAULT_BACKEND_URL } from '../shared/settings'; +import { ProxyResponse } from '../shared/types'; log.info('Background script running'); @@ -75,13 +76,14 @@ chrome.storage.onChanged.addListener((changes, namespace) => { } }); + chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { if (request.type === 'PROXY_REQ') { const { url, headers, timeout = 5000 } = request; log.info(`Proxying request to ${url} with timeout ${timeout}ms`); let responseSent = false; - const onceSendResponse = (response: any) => { + const onceSendResponse = (response: ProxyResponse) => { if (!responseSent) { responseSent = true; sendResponse(response); diff --git a/browser-plugin/src/host/ui/options.tsx b/browser-plugin/src/host/ui/options.tsx index 142e613..4c87d2c 100644 --- a/browser-plugin/src/host/ui/options.tsx +++ b/browser-plugin/src/host/ui/options.tsx @@ -7,9 +7,11 @@ import { getSettings, Settings, } from '../../shared/settings'; +import { ProxyResponse } from '../../shared/types'; type Status = 'idle' | 'loading' | 'success' | 'error'; + const Options = () => { const [settings, setSettings] = useState({ backendUrl: DEFAULT_BACKEND_URL, @@ -55,10 +57,10 @@ const Options = () => { const url = currentSettings.backendUrl.replace(/\/$/, '') + '/api/domains'; - const response = await new Promise((resolve, reject) => { + const response = await new Promise((resolve, reject) => { chrome.runtime.sendMessage( { type: 'PROXY_REQ', url, headers, timeout: 5000 }, - (res) => { + (res: ProxyResponse) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; @@ -76,7 +78,7 @@ const Options = () => { setStatus('error'); return false; } - } catch (e) { + } catch { setStatus('error'); return false; } diff --git a/browser-plugin/src/shared/types.ts b/browser-plugin/src/shared/types.ts new file mode 100644 index 0000000..5658c0e --- /dev/null +++ b/browser-plugin/src/shared/types.ts @@ -0,0 +1,6 @@ +export interface ProxyResponse { + ok: boolean; + status?: number; + data?: unknown; + error?: string; +}