feat(smartnetwork): add Rust-powered network diagnostics bridge and IP intelligence lookups
This commit is contained in:
181
ts/smartnetwork.classes.rustbridge.ts
Normal file
181
ts/smartnetwork.classes.rustbridge.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
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<string, never>;
|
||||
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<string, never>;
|
||||
result: {
|
||||
interfaceName: string;
|
||||
addresses: Array<{ family: string; address: string }>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<typeof plugins.smartrust.RustBridge<TNetworkCommands>>;
|
||||
|
||||
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<TNetworkCommands>({
|
||||
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<void> {
|
||||
const ok = await this.bridge.spawn();
|
||||
if (!ok) {
|
||||
throw new Error('Failed to spawn rustnetwork binary');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the Rust binary.
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
this.bridge.kill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the bridge is running before sending a command.
|
||||
*/
|
||||
private async ensureRunning(): Promise<void> {
|
||||
// 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<TNetworkCommands['ping']['result']> {
|
||||
return this.bridge.sendCommand('ping', { host, count, timeoutMs });
|
||||
}
|
||||
|
||||
public async traceroute(
|
||||
host: string,
|
||||
maxHops?: number,
|
||||
timeoutMs?: number,
|
||||
): Promise<TNetworkCommands['traceroute']['result']> {
|
||||
return this.bridge.sendCommand('traceroute', { host, maxHops, timeoutMs });
|
||||
}
|
||||
|
||||
public async tcpPortCheck(
|
||||
host: string,
|
||||
port: number,
|
||||
timeoutMs?: number,
|
||||
): Promise<TNetworkCommands['tcpPortCheck']['result']> {
|
||||
return this.bridge.sendCommand('tcpPortCheck', { host, port, timeoutMs });
|
||||
}
|
||||
|
||||
public async isLocalPortFree(
|
||||
port: number,
|
||||
): Promise<TNetworkCommands['isLocalPortFree']['result']> {
|
||||
return this.bridge.sendCommand('isLocalPortFree', { port });
|
||||
}
|
||||
|
||||
public async defaultGateway(): Promise<TNetworkCommands['defaultGateway']['result']> {
|
||||
return this.bridge.sendCommand('defaultGateway', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
public async healthPing(): Promise<TNetworkCommands['healthPing']['result']> {
|
||||
return this.bridge.sendCommand('healthPing', {} as Record<string, never>);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user