CI / Build & Test (push) Successful in 2m54s
Add docs/CONTRIBUTING.md covering the build system, dev workflow, all npm scripts, how to add new native host message types, code style, and debugging guidance. Add CHANGELOG.md at the project root documenting all features and fixes across the 1.0.0 release. Add JSDoc (@param, @returns) to all previously undocumented exported functions across 35 JS files: - native-host/holesail-manager/ (index, virtual-hosts, service-tunnels, servers, port-allocator) - native-host top-level managers (startup, connect-proxy, https-proxy, certificate-authority, ssh-manager, rdp-manager) - extension/background/ (logs, native-messaging, proxy, message-router) - extension/dashboard/core/ (utils, navigation, init) - extension/dashboard/ui/ (modal, toast, state-tag) - extension/dashboard/pages/ (all 10 page files) - extension/dashboard/refresh.js, events.js - extension/dashboard/data/hostname-validator.js - scripts/ (build-host, run-install)
167 lines
6.9 KiB
JavaScript
167 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
|
|
}));
|
|
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 };
|
|
}
|
|
}
|