169 lines
4.1 KiB
TypeScript
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;
|
|
}
|