import * as plugins from './plugins.js'; // IPC command map for type-safe bridge communication export type TClientDnsCommands = { resolve: { params: IResolveParams; result: IResolveResult; }; ping: { params: Record; result: { pong: boolean }; }; }; export interface IResolveParams { name: string; recordType: string; protocol: 'udp' | 'doh'; serverAddr?: string; dohUrl?: string; timeoutMs?: number; } export interface IClientDnsAnswer { name: string; type: string; ttl: number; value: string; } export interface IResolveResult { answers: IClientDnsAnswer[]; adFlag: boolean; rcode: number; } /** * Bridge to the Rust DNS client binary via smartrust IPC. */ export class RustDnsClientBridge { private bridge: InstanceType>; private spawnPromise: Promise | null = null; constructor() { const packageDir = plugins.path.resolve( plugins.path.dirname(new URL(import.meta.url).pathname), '..' ); // Determine platform suffix for dist_rust binaries (matches tsrust naming) const platformSuffix = getPlatformSuffix(); const localPaths: string[] = []; // dist_rust/ candidates (tsrust cross-compiled output, platform-specific) if (platformSuffix) { localPaths.push(plugins.path.join(packageDir, 'dist_rust', `rustdns-client_${platformSuffix}`)); } // dist_rust/ without suffix (native build) localPaths.push(plugins.path.join(packageDir, 'dist_rust', 'rustdns-client')); // Local dev build paths localPaths.push(plugins.path.join(packageDir, 'rust', 'target', 'release', 'rustdns-client')); localPaths.push(plugins.path.join(packageDir, 'rust', 'target', 'debug', 'rustdns-client')); this.bridge = new plugins.smartrust.RustBridge({ binaryName: 'rustdns-client', cliArgs: ['--management'], requestTimeoutMs: 30_000, readyTimeoutMs: 10_000, localPaths, searchSystemPath: false, }); this.bridge.on('stderr', (line: string) => { if (line.trim()) { console.log(`[rustdns-client] ${line}`); } }); } /** * Lazily spawn the Rust binary. Only spawns once, caches the promise. */ public async ensureSpawned(): Promise { if (!this.spawnPromise) { this.spawnPromise = this.bridge.spawn(); } const ok = await this.spawnPromise; if (!ok) { this.spawnPromise = null; throw new Error('Failed to spawn rustdns-client binary'); } } /** * Resolve a DNS query through the Rust binary. */ public async resolve( name: string, recordType: string, protocol: 'udp' | 'doh', serverAddr?: string, dohUrl?: string, timeoutMs?: number ): Promise { await this.ensureSpawned(); const params: IResolveParams = { name, recordType, protocol, }; if (serverAddr) params.serverAddr = serverAddr; if (dohUrl) params.dohUrl = dohUrl; if (timeoutMs) params.timeoutMs = timeoutMs; return this.bridge.sendCommand('resolve', params); } /** * Ping the Rust binary for health check. */ public async ping(): Promise { await this.ensureSpawned(); const result = await this.bridge.sendCommand('ping', {} as Record); return result.pong; } /** * Kill the Rust process. */ public kill(): void { this.bridge.kill(); this.spawnPromise = null; } /** * Whether the bridge is running. */ public get running(): boolean { return this.bridge.running; } } /** * Get the tsrust platform suffix for the current platform. */ 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; }