Files
holesail-browser/native-host/holesail-manager/index.js
T
Raven Scott 15caac7032
CI / Build & Test (push) Successful in 2m46s
refactor(native-host): modularize host.js and holesail-manager.js
Split host.js (435 lines) into host/{paths,logger,startup,message-router}.js.
Split holesail-manager.js (698 lines) into holesail-manager/{state,settings,
connections,port-allocator,servers,virtual-hosts,service-tunnels,index}.js.

Top-level host.js and holesail-manager.js become thin shims so index.mjs
requires no changes. Deleted dev scratch file test-cp.mjs.

Updated CI with 12 new node --check lines for all sub-modules.
Updated docs/ARCHITECTURE.md with per-file tables for host/ and
holesail-manager/ sub-modules.

No functionality changed. No new dependencies.
2026-02-28 23:15:08 -05:00

140 lines
5.6 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;
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 };
}
}