import * as plugins from './plugins.js'; import { EventEmitter } from 'events'; // Command map for the edge side of remoteingress-bin type TEdgeCommands = { ping: { params: Record; result: { pong: boolean }; }; startEdge: { params: { hubHost: string; hubPort: number; edgeId: string; secret: string; listenPorts: number[]; stunIntervalSecs?: number; }; result: { started: boolean }; }; stopEdge: { params: Record; result: { stopped: boolean; wasRunning?: boolean }; }; getEdgeStatus: { params: Record; result: { running: boolean; connected: boolean; publicIp: string | null; activeStreams: number; listenPorts: number[]; }; }; }; export interface IEdgeConfig { hubHost: string; hubPort?: number; edgeId: string; secret: string; listenPorts: number[]; stunIntervalSecs?: number; } export class RemoteIngressEdge extends EventEmitter { private bridge: InstanceType>; private started = false; constructor() { super(); const packageDir = plugins.path.resolve( plugins.path.dirname(new URL(import.meta.url).pathname), '..', ); this.bridge = new plugins.smartrust.RustBridge({ binaryName: 'remoteingress-bin', cliArgs: ['--management'], requestTimeoutMs: 30_000, readyTimeoutMs: 10_000, localPaths: [ plugins.path.join(packageDir, 'dist_rust'), plugins.path.join(packageDir, 'rust', 'target', 'release'), plugins.path.join(packageDir, 'rust', 'target', 'debug'), ], searchSystemPath: false, }); // Forward events from Rust binary this.bridge.on('management:tunnelConnected', () => { this.emit('tunnelConnected'); }); this.bridge.on('management:tunnelDisconnected', () => { this.emit('tunnelDisconnected'); }); this.bridge.on('management:publicIpDiscovered', (data: { ip: string }) => { this.emit('publicIpDiscovered', data); }); } /** * Start the edge — spawns the Rust binary and connects to the hub. */ public async start(config: IEdgeConfig): Promise { const spawned = await this.bridge.spawn(); if (!spawned) { throw new Error('Failed to spawn remoteingress-bin'); } await this.bridge.sendCommand('startEdge', { hubHost: config.hubHost, hubPort: config.hubPort ?? 8443, edgeId: config.edgeId, secret: config.secret, listenPorts: config.listenPorts, stunIntervalSecs: config.stunIntervalSecs, }); this.started = true; } /** * Stop the edge and kill the Rust process. */ public async stop(): Promise { if (this.started) { try { await this.bridge.sendCommand('stopEdge', {} as Record); } catch { // Process may already be dead } this.bridge.kill(); this.started = false; } } /** * Get the current edge status. */ public async getStatus() { return this.bridge.sendCommand('getEdgeStatus', {} as Record); } /** * Check if the bridge is running. */ public get running(): boolean { return this.bridge.running; } }