Files
smartdns/ts_client/classes.rustdnsclientbridge.ts

169 lines
4.1 KiB
TypeScript

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<string, never>;
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<typeof plugins.smartrust.RustBridge<TClientDnsCommands>>;
private spawnPromise: Promise<boolean> | 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<TClientDnsCommands>({
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<void> {
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<IResolveResult> {
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<boolean> {
await this.ensureSpawned();
const result = await this.bridge.sendCommand('ping', {} as Record<string, never>);
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<string, string> = {
'linux': 'linux',
'darwin': 'macos',
'win32': 'windows',
};
const archMap: Record<string, string> = {
'x64': 'amd64',
'arm64': 'arm64',
};
const p = platformMap[platform];
const a = archMap[arch];
if (p && a) {
return `${p}_${a}`;
}
return null;
}