Files
holesail-browser/native-host/holesail-manager/index.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

166 lines
6.9 KiB
JavaScript

/**
* Holesail manager — assembles all sub-modules and exposes the same API
* as the original monolithic holesail-manager.js.
*/
const stateModule = require('./state.js');
const settingsModule = require('./settings.js');
const connectionsModule = require('./connections.js');
const serversModule = require('./servers.js');
const vhostsModule = require('./virtual-hosts.js');
const svcModule = require('./service-tunnels.js');
// ── Event emitter ─────────────────────────────────────────────────────────────
let eventEmit = null;
/**
* Register the callback used to push tunnel lifecycle events to the extension.
* Must be called before any tunnels are started so events are not lost.
* @param {Function} emit - Called as `emit(eventName, payloadObject)`.
*/
function setEventEmitter(emit) { eventEmit = emit; }
function emit(event, payload) { if (eventEmit) eventEmit(event, payload); }
// ── Shared saveState snapshot ─────────────────────────────────────────────────
// Collects current state from all sub-modules and persists it.
function saveState() {
const serversList = serversModule.getServers().map(s => ({
id: s.id, port: s.port, host: s.host, secure: s.secure, udp: s.udp || false, label: s.label || ''
}));
const virtualHostsList = vhostsModule.getVirtualHosts()
.map(v => ({ hostname: v.hostname, hsUrl: v.hsUrl, useTls: v.useTls === true }));
const serviceTunnelsList = svcModule.getServiceTunnels().map(t => ({
id: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort
}));
stateModule.saveStateSync({
version: 2,
settings: settingsModule.getSettings(),
nextServerId: serversModule.getNextServerId(),
nextServiceTunnelId: svcModule.getNextServiceTunnelId(),
servers: serversList,
virtualHosts: virtualHostsList,
serviceTunnels: serviceTunnelsList,
sshConnections: connectionsModule.getSshConnections(),
rdpConnections: connectionsModule.getRdpConnections()
});
}
// Inject the shared saveState and emit callbacks into each sub-module
settingsModule.init(saveState);
connectionsModule.init(saveState);
serversModule.init(saveState, settingsModule.getReadyTimeoutMs);
vhostsModule.init(saveState, emit, settingsModule.getReadyTimeoutMs, settingsModule.getTunnelAutoReconnect);
svcModule.init(saveState, emit, settingsModule.getReadyTimeoutMs, settingsModule.getTunnelAutoReconnect);
// ── Storage path ──────────────────────────────────────────────────────────────
/**
* Set the base directory used for state.json persistence.
* Must be called before `restorePersistedState`.
* @param {string} baseDir - Absolute path to the storage directory.
*/
function setStoragePath(baseDir) {
stateModule.setStoragePath(baseDir);
}
// ── Restore persisted state ───────────────────────────────────────────────────
/**
* Load state.json and apply the persisted settings, connection lists, and ID counters
* to all sub-modules. Returns the raw persisted data so the caller can re-start tunnels.
* @returns {{settings: object, servers: Array, virtualHosts: Array, serviceTunnels: Array, sshConnections: Array, rdpConnections: Array}}
*/
function restorePersistedState() {
const loaded = stateModule.loadState();
settingsModule.applyLoaded(loaded.settings);
connectionsModule.applyLoaded(loaded.sshConnections, loaded.rdpConnections);
serversModule.applyLoaded(loaded.nextServerId);
svcModule.applyLoaded(loaded.nextServiceTunnelId);
return {
settings: settingsModule.getSettings(),
servers: loaded.servers,
virtualHosts: loaded.virtualHosts,
serviceTunnels: loaded.serviceTunnels || [],
sshConnections: connectionsModule.getSshConnections(),
rdpConnections: connectionsModule.getRdpConnections()
};
}
// ── Cleanup ───────────────────────────────────────────────────────────────────
/**
* Close all active tunnels across all sub-modules.
* Called during native host shutdown to release resources cleanly.
* @returns {Promise<void>}
*/
async function cleanup() {
await serversModule.cleanupServers();
await vhostsModule.cleanupVirtualHosts();
await svcModule.cleanupServiceTunnels();
}
// ── Re-export full original API ───────────────────────────────────────────────
module.exports = {
// Event emitter
setEventEmitter,
// Storage
setStoragePath,
// Settings
getSettings: settingsModule.getSettings,
updateSettings: settingsModule.updateSettings,
getProxyPort: settingsModule.getProxyPort,
setProxyPort: settingsModule.setProxyPort,
// SSH/RDP connection lists
getSshConnections: connectionsModule.getSshConnections,
setSshConnections: connectionsModule.setSshConnections,
getRdpConnections: connectionsModule.getRdpConnections,
setRdpConnections: connectionsModule.setRdpConnections,
// Servers
startServer: serversModule.startServer,
stopServer: serversModule.stopServer,
getServers: serversModule.getServers,
// Virtual hosts
setVirtualHost: vhostsModule.setVirtualHost,
removeVirtualHost: vhostsModule.removeVirtualHost,
getVirtualHosts: vhostsModule.getVirtualHosts,
getLocalPortForHostname: vhostsModule.getLocalPortForHostname,
getLocalBackend: vhostsModule.getLocalBackend,
getVirtualHostMap: vhostsModule.getVirtualHostMap,
// Service tunnels
startServiceTunnel: svcModule.startServiceTunnel,
stopServiceTunnel: svcModule.stopServiceTunnel,
getServiceTunnels: svcModule.getServiceTunnels,
// Lookup
lookup,
// Lifecycle
restorePersistedState,
cleanup
};
// ── Lookup (standalone, only needs Holesail) ──────────────────────────────────
let Holesail = null;
try { Holesail = require('holesail'); } catch (_) {}
/**
* Resolve an hs:// key via the DHT and return connection metadata without
* establishing a full tunnel.
* @param {object} payload
* @param {string} payload.hsUrl - The hs:// key to look up.
* @returns {Promise<{ok: boolean, host?: string, port?: number, protocol?: string, secure?: boolean, error?: string}>}
*/
async function lookup(payload) {
if (!Holesail) return { ok: false, error: 'Holesail module not installed' };
const url = payload.url || payload.hsUrl;
if (!url) return { ok: false, error: 'url required' };
try {
const result = await Holesail.lookup(url);
return { ok: true, ...result };
} catch (e) {
return { ok: false, error: e.message };
}
}