Files
holesail-browser/extension/background/message-router.js
T
Raven Scott f1e98a7edd
CI / Build & Test (push) Successful in 2m54s
docs: add CONTRIBUTING.md, CHANGELOG.md, and JSDoc to entire codebase
Add docs/CONTRIBUTING.md covering the build system, dev workflow, all
npm scripts, how to add new native host message types, code style, and
debugging guidance.

Add CHANGELOG.md at the project root documenting all features and fixes
across the 1.0.0 release.

Add JSDoc (@param, @returns) to all previously undocumented exported
functions across 35 JS files:
- native-host/holesail-manager/ (index, virtual-hosts, service-tunnels,
  servers, port-allocator)
- native-host top-level managers (startup, connect-proxy, https-proxy,
  certificate-authority, ssh-manager, rdp-manager)
- extension/background/ (logs, native-messaging, proxy, message-router)
- extension/dashboard/core/ (utils, navigation, init)
- extension/dashboard/ui/ (modal, toast, state-tag)
- extension/dashboard/pages/ (all 10 page files)
- extension/dashboard/refresh.js, events.js
- extension/dashboard/data/hostname-validator.js
- scripts/ (build-host, run-install)
2026-03-01 00:40:53 -05:00

202 lines
7.9 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);
if (message.payload?.type === 'setVirtualHost' && message.payload?.payload?.hostname) {
const hostname = message.payload.payload.hostname;
const parts = hostname.split('.');
if (parts.length >= 3) {
const baseDomain = parts.slice(-2).join('.');
if (baseDomain !== 'hole.sail') {
const origin = '*://*.' + baseDomain + '/*';
browser.permissions.request({ origins: [origin] }, (granted) => {
log('permissions.request for', origin, ':', granted ? 'granted' : 'denied');
});
}
}
}
}
}).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;
});