import * as plugins from './smartnetwork.plugins.js'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; /** * Command map for the rustnetwork IPC binary. * Each key maps to { params, result } defining the typed IPC protocol. */ type TNetworkCommands = { healthPing: { params: Record; result: { pong: boolean }; }; ping: { params: { host: string; count?: number; timeoutMs?: number }; result: { alive: boolean; times: (number | null)[]; min: number | null; max: number | null; avg: number | null; stddev: number | null; packetLoss: number; }; }; traceroute: { params: { host: string; maxHops?: number; timeoutMs?: number }; result: { hops: Array<{ ttl: number; ip: string; rtt: number | null }>; }; }; tcpPortCheck: { params: { host: string; port: number; timeoutMs?: number }; result: { isOpen: boolean; latencyMs: number | null }; }; isLocalPortFree: { params: { port: number }; result: { free: boolean }; }; defaultGateway: { params: Record; result: { interfaceName: string; addresses: Array<{ family: string; address: string }>; }; }; }; function getPlatformSuffix(): string | null { const platform = process.platform; const arch = process.arch; const platformMap: Record = { linux: 'linux', darwin: 'macos', win32: 'windows', }; const archMap: Record = { x64: 'amd64', arm64: 'arm64', }; const p = platformMap[platform]; const a = archMap[arch]; if (p && a) { return `${p}_${a}`; } return null; } /** * Singleton bridge to the rustnetwork binary. * Manages the IPC lifecycle for network diagnostics operations. */ export class RustNetworkBridge { private static instance: RustNetworkBridge | null = null; public static getInstance(): RustNetworkBridge { if (!RustNetworkBridge.instance) { RustNetworkBridge.instance = new RustNetworkBridge(); } return RustNetworkBridge.instance; } private bridge: InstanceType>; private constructor() { const packageDir = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..', ); const platformSuffix = getPlatformSuffix(); const localPaths: string[] = []; // Platform-specific cross-compiled binary if (platformSuffix) { localPaths.push(path.join(packageDir, 'dist_rust', `rustnetwork_${platformSuffix}`)); } // Native build without suffix localPaths.push(path.join(packageDir, 'dist_rust', 'rustnetwork')); // Local dev paths localPaths.push(path.join(packageDir, 'rust', 'target', 'release', 'rustnetwork')); localPaths.push(path.join(packageDir, 'rust', 'target', 'debug', 'rustnetwork')); this.bridge = new plugins.smartrust.RustBridge({ binaryName: 'rustnetwork', cliArgs: ['--management'], requestTimeoutMs: 30_000, readyTimeoutMs: 10_000, localPaths, searchSystemPath: false, }); } /** * Spawn the Rust binary and wait for it to be ready. */ public async start(): Promise { const ok = await this.bridge.spawn(); if (!ok) { throw new Error('Failed to spawn rustnetwork binary'); } } /** * Kill the Rust binary. */ public async stop(): Promise { this.bridge.kill(); } /** * Ensure the bridge is running before sending a command. */ private async ensureRunning(): Promise { // The bridge will throw if not spawned — we just call start() if not yet running } // ===== Command wrappers ===== public async ping( host: string, count?: number, timeoutMs?: number, ): Promise { return this.bridge.sendCommand('ping', { host, count, timeoutMs }); } public async traceroute( host: string, maxHops?: number, timeoutMs?: number, ): Promise { return this.bridge.sendCommand('traceroute', { host, maxHops, timeoutMs }); } public async tcpPortCheck( host: string, port: number, timeoutMs?: number, ): Promise { return this.bridge.sendCommand('tcpPortCheck', { host, port, timeoutMs }); } public async isLocalPortFree( port: number, ): Promise { return this.bridge.sendCommand('isLocalPortFree', { port }); } public async defaultGateway(): Promise { return this.bridge.sendCommand('defaultGateway', {} as Record); } public async healthPing(): Promise { return this.bridge.sendCommand('healthPing', {} as Record); } }