refactor(native-host): modularize host.js and holesail-manager.js
CI / Build & Test (push) Successful in 2m46s
CI / Build & Test (push) Successful in 2m46s
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.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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 };
|
||||
Reference in New Issue
Block a user