Skip to content
Merged
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
55 changes: 44 additions & 11 deletions browser-plugin/src/host/background.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import log from '../shared/logger';
import { DEFAULT_BACKEND_URL } from '../shared/settings';
import { ProxyResponse } from '../shared/types';

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
Expand Down Expand Up @@ -56,36 +61,64 @@ 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: ProxyResponse) => {
if (!responseSent) {
responseSent = true;
sendResponse(response);
}
};

fetch(url, { headers })
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
onceSendResponse({ ok: false, error: 'Connection timed out' });
}, timeout);

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
}
});
62 changes: 48 additions & 14 deletions browser-plugin/src/host/ui/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Settings>({
backendUrl: DEFAULT_BACKEND_URL,
Expand All @@ -25,7 +27,9 @@ const Options = () => {
getSettings().then((loadedSettings) => {
setSettings(loadedSettings);
setLoaded(true);
testConnection(loadedSettings);
if (loadedSettings.enabled) {
testConnection(loadedSettings);
}
});
}, []);

Expand All @@ -42,9 +46,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) {
Expand All @@ -56,27 +57,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<ProxyResponse>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: 'PROXY_REQ', url, headers, timeout: 5000 },
(res: ProxyResponse) => {
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 {
setStatus('error');
} finally {
clearTimeout(timeoutId);
return false;
}
};

const handleTestClick = () => {
if (status === 'loading') {
return; // Ignore click if test is already in progress
}
testConnection(settings);
};

Expand Down Expand Up @@ -305,19 +316,42 @@ 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' }}
/>
Enable Extension
</label>
</div>

</div>
);
};
Expand Down
6 changes: 6 additions & 0 deletions browser-plugin/src/shared/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface ProxyResponse {
ok: boolean;
status?: number;
data?: unknown;
error?: string;
}