Files
holesail-browser/extension/background/message-router.js
T
2026-03-01 23:07:55 -05:00

189 lines
7.2 KiB
JavaScript

/**
* Central chrome.runtime.onMessage handler for the background service worker.
* Routes messages from content scripts and the dashboard to local handlers or
* the native host. Handled actions:
* - send Forward a typed payload to the native host (async).
* - registerSwarm Track a swarm ID for a tab (local, not forwarded).
* - subscribe Subscribe a tab to tunnel lifecycle events.
* - unsubscribe Unsubscribe a tab from tunnel lifecycle events.
* - registerDashboard Add a tab to the dashboard broadcast set.
* - unregisterDashboard Remove a tab from the dashboard broadcast set.
* - getState Fetch full extension + native host state (async).
* Depends on: state.js, logs.js, proxy.js (getActiveTlds, applyPAC, pacConfirmedActive),
* native-messaging.js (send, port, subscribedTabs, dashboardTabs)
*/
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
let responded = false;
function reply(value) {
if (responded) return;
responded = true;
try { sendResponse(value); } catch (_) {}
}
if (message.target !== 'holesail-native') {
reply({ error: 'Unknown target' });
return false;
}
// Handle registerSwarm locally (legacy) - don't forward to native host
if (message.action === 'send' && message.payload?.type === 'registerSwarm') {
const swarmId = message.payload?.payload?.swarmId;
if (swarmId && sender.tab?.id != null) {
if (!tabSwarms.has(sender.tab.id)) {
tabSwarms.set(sender.tab.id, new Set());
}
tabSwarms.get(sender.tab.id).add(swarmId);
const count = swarmRefCount.get(swarmId) || 0;
swarmRefCount.set(swarmId, count + 1);
log('Registered swarm', swarmId, 'for tab', sender.tab.id, '- refcount:', count + 1);
}
reply({ ok: true });
return false;
}
if (message.action === 'send') {
send(message.payload)
.then((r) => {
// After setVirtualHost or removeVirtualHost, refresh virtualHosts and re-apply PAC
if (r && r.ok && (
message.payload?.type === 'setVirtualHost' ||
message.payload?.type === 'removeVirtualHost'
)) {
send({ type: 'getState', payload: {} }).then((state) => {
if (state && state.ok && Array.isArray(state.virtualHosts)) {
extensionState.virtualHosts = state.virtualHosts;
const newTlds = getActiveTlds(extensionState.virtualHosts);
applyPAC(newTlds);
}
}).catch(() => {});
}
reply(r);
})
.catch((err) => reply({ error: err.message }));
return true; // async response
}
if (message.action === 'subscribe' && sender.tab && sender.tab.id != null) {
subscribedTabs.add(sender.tab.id);
reply({ ok: true });
return false;
}
if (message.action === 'unsubscribe' && sender.tab && sender.tab.id != null) {
subscribedTabs.delete(sender.tab.id);
reply({ ok: true });
return false;
}
if (message.action === 'registerDashboard' && sender.tab && sender.tab.id != null) {
dashboardTabs.add(sender.tab.id);
log('Dashboard registered for tab', sender.tab.id);
reply({ ok: true, logs: logs.slice(0) });
return false;
}
if (message.action === 'unregisterDashboard' && sender.tab && sender.tab.id != null) {
dashboardTabs.delete(sender.tab.id);
reply({ ok: true });
return false;
}
if (message.action === 'getState') {
debugLog('getState action: port=', !!port);
updateExtensionState();
if (port) {
send({ type: 'getState', payload: {} })
.then((payload) => {
debugLog('getState response: ok=', payload && payload.ok, 'servers=', payload && payload.servers && payload.servers.length, 'virtualHosts=', payload && payload.virtualHosts && payload.virtualHosts.length, 'proxyPort=', payload && payload.proxyPort, 'connectProxyPort=', payload && payload.connectProxyPort);
if (payload && payload.ok === true && (payload.servers || payload.virtualHosts || payload.proxyPort != null || payload.connectProxyPort != null)) {
extensionState.proxyPort = payload.proxyPort ?? null;
extensionState.connectProxyPort = payload.connectProxyPort ?? null;
extensionState.virtualHosts = payload.virtualHosts || [];
if (payload.settings && typeof payload.settings.notifyOnDisconnect === 'boolean') {
notifyOnDisconnect = payload.settings.notifyOnDisconnect;
}
if (payload.settings && typeof payload.settings.notifyOnTunnelError === 'boolean') {
notifyOnTunnelError = payload.settings.notifyOnTunnelError;
}
if (!pacConfirmedActive) {
log('getState: PAC not confirmed active, re-applying');
applyPAC(getActiveTlds(extensionState.virtualHosts));
}
reply({
ok: true,
state: {
hostConnected: true,
servers: payload.servers || [],
virtualHosts: payload.virtualHosts || [],
serviceTunnels: payload.serviceTunnels || [],
proxyPort: payload.proxyPort ?? null,
connectProxyPort: payload.connectProxyPort ?? null,
caInstalled: payload.caInstalled ?? false,
settings: payload.settings || {},
sshConnections: payload.sshConnections || [],
rdpConnections: payload.rdpConnections || [],
trafficStats: payload.trafficStats || null,
stats: { ...extensionState.stats, ...payload.stats }
}
});
} else {
reply({
ok: true,
state: {
hostConnected: true,
servers: extensionState.servers || [],
virtualHosts: extensionState.virtualHosts || [],
serviceTunnels: [],
sshConnections: [],
rdpConnections: [],
proxyPort: null,
connectProxyPort: null,
caInstalled: false,
stats: extensionState.stats
}
});
}
})
.catch((err) => {
debugLog('getState send failed:', err && err.message);
reply({
ok: true,
state: {
hostConnected: extensionState.hostConnected,
servers: extensionState.servers || [],
virtualHosts: extensionState.virtualHosts || [],
serviceTunnels: [],
sshConnections: [],
rdpConnections: [],
proxyPort: null,
connectProxyPort: null,
caInstalled: false,
stats: extensionState.stats
}
});
});
} else {
debugLog('getState: no port, replying hostConnected=false');
reply({
ok: true,
state: {
hostConnected: false,
servers: [],
virtualHosts: [],
serviceTunnels: [],
sshConnections: [],
rdpConnections: [],
proxyPort: null,
connectProxyPort: null,
caInstalled: false,
stats: extensionState.stats
}
});
}
return true;
}
reply({ error: 'Unknown action' });
return false;
});