Files
holesail-browser/native-host/host/startup.js
T
Raven Scott 3a13334779
CI / Build & Test (push) Failing after 2m44s
feat(virtual-hosts): add TLS option for HTTPS/443 backends
- Add "Use TLS (secure connection)" checkbox in Add Virtual Host modal
- Persist and restore useTls in state; show TLS badge in table
- When enabled, HTTPS proxy connects to tunnel backend over TLS (SNI =
  hostname) for HTTP and WebSocket; supports services on port 443
2026-03-03 03:59:53 -05:00

146 lines
5.9 KiB
JavaScript

/**
* Proxy startup and persisted tunnel restore.
* Starts the HTTPS and CONNECT proxies after loading persisted settings,
* then asynchronously restores all tunnels from state.json.
*/
const { log, debugLog } = require('./logger.js');
const PROXY_PORT = 8443;
const CONNECT_PROXY_PORT = 8442;
let proxiesReadyPromise = null;
let tunnelsRestoredPromise = null;
/**
* Re-start all tunnels that were persisted in state.json.
* Runs after the proxies are ready so tunnels have a working backend to connect through.
* Logs per-type success/failure counts but never throws — failures are non-fatal.
* @param {object} holesailManager - The holesail manager module.
* @param {object} restored - The persisted state object returned by `holesailManager.restorePersistedState()`.
* @returns {Promise<void>}
*/
async function restorePersistedTunnels(holesailManager, restored) {
try {
debugLog('restorePersistedTunnels: starting');
const { servers: savedServers, virtualHosts: savedVhosts, serviceTunnels: savedServiceTunnels } = restored;
log('Restore: loaded', savedServers.length, 'servers,', savedVhosts.length, 'virtual hosts,', savedServiceTunnels.length, 'service tunnels');
let serverOk = 0, serverFail = 0;
for (const s of savedServers) {
const result = await holesailManager.startServer({
serverId: s.id,
port: s.port,
host: s.host || '127.0.0.1',
secure: s.secure !== false,
udp: s.udp === true,
label: s.label || ''
});
if (result.ok) serverOk++; else serverFail++;
}
let vhostOk = 0, vhostFail = 0;
for (const v of savedVhosts) {
const result = await holesailManager.setVirtualHost({ hostname: v.hostname, hsUrl: v.hsUrl, useTls: v.useTls === true });
if (result.ok) vhostOk++; else vhostFail++;
}
let svcOk = 0, svcFail = 0;
for (const t of savedServiceTunnels) {
const result = await holesailManager.startServiceTunnel({ tunnelId: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort });
if (result.ok) svcOk++; else svcFail++;
}
if (savedServers.length || savedVhosts.length || savedServiceTunnels.length) {
log('Restored on startup: servers', serverOk + '/' + savedServers.length, 'virtual hosts', vhostOk + '/' + savedVhosts.length, 'service tunnels', svcOk + '/' + savedServiceTunnels.length);
if (serverFail || vhostFail || svcFail) log('Restore failures: servers', serverFail, 'virtual hosts', vhostFail, 'service tunnels', svcFail);
}
} catch (e) {
log('Restore persisted tunnels failed:', e.message);
}
}
/**
* Kick off the async startup sequence: restore settings, start the CA, start both
* proxies, then begin restoring persisted tunnels in the background.
* Sets `proxiesReadyPromise` which resolves once the proxies are up (or have failed).
* Sets `tunnelsRestoredPromise` which resolves once all tunnels have been re-started.
* @param {object} holesailManager
* @param {object} certificateAuthority
* @param {object} httpsProxy
* @param {object} connectProxy
*/
function initStartup(holesailManager, certificateAuthority, httpsProxy, connectProxy) {
if (typeof setImmediate !== 'function') return;
proxiesReadyPromise = new Promise((resolve) => {
setImmediate(async () => {
try {
const restored = holesailManager.restorePersistedState();
const savedProxyPort = (restored.settings && restored.settings.proxyPort) || PROXY_PORT;
const savedConnectPort = (restored.settings && restored.settings.connectProxyPort) || CONNECT_PROXY_PORT;
try {
await certificateAuthority.caReady;
} catch (e) {
log('CA generation failed:', e.message, '— HTTPS proxy may not work correctly');
}
try {
await new Promise((res, rej) => {
httpsProxy.start(savedProxyPort, certificateAuthority, (err) => {
if (err) return rej(err);
res();
});
});
log('HTTPS proxy ready on port', savedProxyPort);
} catch (e) {
log('HTTPS proxy startup failed:', e.message);
}
try {
await new Promise((res, rej) => {
const timeout = setTimeout(() => {
rej(new Error('CONNECT proxy listen timed out after 5s'));
}, 5000);
connectProxy.start(savedConnectPort, savedProxyPort, (err) => {
clearTimeout(timeout);
if (err) return rej(err);
res();
});
});
log('CONNECT proxy ready on port', savedConnectPort);
} catch (e) {
log('CONNECT proxy startup failed:', e.message);
}
log('Proxies startup complete: HTTPS', savedProxyPort, 'CONNECT', connectProxy.getPort() ?? 'FAILED');
// Assign tunnelsRestoredPromise BEFORE resolving proxiesReadyPromise so
// that any getState call awaiting proxiesReadyPromise immediately sees the
// non-null promise rather than the one-microtask window where it is null.
tunnelsRestoredPromise = restorePersistedTunnels(holesailManager, restored).catch((e) => log('Restore tunnels failed:', e.message));
} catch (e) {
log('Unexpected startup error:', e.message);
} finally {
resolve();
}
});
});
}
/** @returns {Promise<void>|null} Resolves when both proxies have started (or failed). */
function getProxiesReadyPromise() { return proxiesReadyPromise; }
/** @returns {Promise<void>|null} Resolves when all persisted tunnels have been re-started. */
function getTunnelsRestoredPromise() { return tunnelsRestoredPromise; }
/**
* Override the tunnels-restored promise (used by message-router to assign it
* before proxiesReadyPromise resolves, avoiding a one-microtask null window).
* @param {Promise<void>} p
*/
function setTunnelsRestoredPromise(p) { tunnelsRestoredPromise = p; }
module.exports = {
initStartup,
restorePersistedTunnels,
getProxiesReadyPromise,
getTunnelsRestoredPromise,
setTunnelsRestoredPromise
};