/** * 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; 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 })); 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); svcModule.init(saveState, emit, settingsModule.getReadyTimeoutMs); // ── Storage path ────────────────────────────────────────────────────────────── function setStoragePath(baseDir) { stateModule.setStoragePath(baseDir); } // ── Restore persisted state ─────────────────────────────────────────────────── 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 ─────────────────────────────────────────────────────────────────── 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 (_) {} 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 }; } }