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

120 lines
4.1 KiB
JavaScript

/**
* Holesail server tunnel management.
* Exposes a local TCP/UDP port to the Holesail P2P network (server mode).
*/
let Holesail = null;
try { Holesail = require('holesail'); } catch (e) {
if (process.stderr) process.stderr.write('[holesail-manager] Holesail not installed: ' + e.message + '\n');
}
const DEBUG = process.env.HOLESAIL_DEBUG === '1' || process.env.HOLESAIL_DEBUG === 'true';
function debugLog(...args) {
if (!DEBUG) return;
const msg = '[holesail-manager:debug] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
if (process.stderr) process.stderr.write(msg + '\n');
}
const servers = new Map(); // serverId -> { holesail, port, host, secure, udp, label, url, createdAt }
let nextServerId = 0;
let _saveState = null;
let _getReadyTimeoutMs = null;
function init(saveStateFn, getReadyTimeoutMsFn) {
_saveState = saveStateFn;
_getReadyTimeoutMs = getReadyTimeoutMsFn;
}
function applyLoaded(loadedNextServerId) {
if (loadedNextServerId > nextServerId) nextServerId = loadedNextServerId;
}
function parseServerIdNum(serverId) {
if (!serverId || typeof serverId !== 'string') return 0;
const m = serverId.match(/^server_(\d+)$/);
return m ? Math.max(0, parseInt(m[1], 10)) : 0;
}
function generateServerId() {
nextServerId += 1;
return 'server_' + nextServerId;
}
function useServerId(givenId) {
if (givenId && typeof givenId === 'string' && givenId.startsWith('server_')) {
const n = parseServerIdNum(givenId);
if (n >= nextServerId) nextServerId = n + 1;
return givenId;
}
return generateServerId();
}
function readyWithTimeout(hs, label) {
const ms = _getReadyTimeoutMs ? _getReadyTimeoutMs() : 0;
if (!ms || ms <= 0) return hs.ready();
return new Promise((resolve, reject) => {
const t = setTimeout(() => reject(new Error('Tunnel ready timeout after ' + ms + 'ms for ' + label)), ms);
hs.ready().then(() => { clearTimeout(t); resolve(); }, (e) => { clearTimeout(t); reject(e); });
});
}
async function startServer(payload) {
if (!Holesail) return { ok: false, error: 'Holesail module not installed' };
const port = payload.port || 3000;
const host = payload.host || '127.0.0.1';
const secure = payload.secure !== false;
const udp = payload.udp === true;
const label = (payload.label || '').trim();
const serverId = useServerId(payload.serverId);
debugLog('startServer: serverId=', serverId, 'port=', port, 'host=', host, 'secure=', secure, 'udp=', udp, 'label=', label);
if (servers.has(serverId)) {
debugLog('startServer: serverId already in use');
return { ok: false, error: 'Server id already in use' };
}
try {
const hs = new Holesail({ server: true, port, host, secure, udp });
await readyWithTimeout(hs, 'server:' + serverId);
const url = hs.info.url;
servers.set(serverId, { holesail: hs, port: hs.info.port, host: hs.info.host, secure, udp, label, url, createdAt: Date.now() });
if (_saveState) _saveState();
debugLog('startServer: ok serverId=', serverId, 'url=', url);
return { ok: true, serverId, url, port: hs.info.port, host: hs.info.host, secure, udp, label };
} catch (e) {
debugLog('startServer: error', e.message);
return { ok: false, error: e.message };
}
}
async function stopServer(payload) {
const serverId = payload.serverId;
debugLog('stopServer: serverId=', serverId);
const entry = servers.get(serverId);
if (!entry) {
debugLog('stopServer: not found');
return { ok: false, error: 'Server not found' };
}
try { await entry.holesail.close(); } catch (_) {}
servers.delete(serverId);
if (_saveState) _saveState();
return { ok: true };
}
function getServers() {
const list = [];
for (const [id, s] of servers) {
list.push({ id, serverId: id, port: s.port, host: s.host, url: s.url, secure: s.secure, udp: s.udp || false, label: s.label || '', createdAt: s.createdAt });
}
return list;
}
function getNextServerId() { return nextServerId; }
async function cleanupServers() {
for (const [, s] of servers) {
try { await s.holesail.close(); } catch (_) {}
}
servers.clear();
}
module.exports = { init, applyLoaded, startServer, stopServer, getServers, getNextServerId, cleanupServers };