2026-02-16 11:22:23 +00:00
|
|
|
import * as plugins from './plugins.js';
|
|
|
|
|
import { EventEmitter } from 'events';
|
2026-02-17 19:36:40 +00:00
|
|
|
import { decodeConnectionToken } from './classes.token.js';
|
2026-02-16 11:22:23 +00:00
|
|
|
|
|
|
|
|
// Command map for the edge side of remoteingress-bin
|
|
|
|
|
type TEdgeCommands = {
|
|
|
|
|
ping: {
|
|
|
|
|
params: Record<string, never>;
|
|
|
|
|
result: { pong: boolean };
|
|
|
|
|
};
|
|
|
|
|
startEdge: {
|
|
|
|
|
params: {
|
|
|
|
|
hubHost: string;
|
|
|
|
|
hubPort: number;
|
|
|
|
|
edgeId: string;
|
|
|
|
|
secret: string;
|
|
|
|
|
};
|
|
|
|
|
result: { started: boolean };
|
|
|
|
|
};
|
|
|
|
|
stopEdge: {
|
|
|
|
|
params: Record<string, never>;
|
|
|
|
|
result: { stopped: boolean; wasRunning?: boolean };
|
|
|
|
|
};
|
|
|
|
|
getEdgeStatus: {
|
|
|
|
|
params: Record<string, never>;
|
|
|
|
|
result: {
|
|
|
|
|
running: boolean;
|
|
|
|
|
connected: boolean;
|
|
|
|
|
publicIp: string | null;
|
|
|
|
|
activeStreams: number;
|
|
|
|
|
listenPorts: number[];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export interface IEdgeConfig {
|
|
|
|
|
hubHost: string;
|
|
|
|
|
hubPort?: number;
|
|
|
|
|
edgeId: string;
|
|
|
|
|
secret: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class RemoteIngressEdge extends EventEmitter {
|
|
|
|
|
private bridge: InstanceType<typeof plugins.smartrust.RustBridge<TEdgeCommands>>;
|
|
|
|
|
private started = false;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
super();
|
|
|
|
|
|
|
|
|
|
const packageDir = plugins.path.resolve(
|
|
|
|
|
plugins.path.dirname(new URL(import.meta.url).pathname),
|
|
|
|
|
'..',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.bridge = new plugins.smartrust.RustBridge<TEdgeCommands>({
|
|
|
|
|
binaryName: 'remoteingress-bin',
|
|
|
|
|
cliArgs: ['--management'],
|
|
|
|
|
requestTimeoutMs: 30_000,
|
|
|
|
|
readyTimeoutMs: 10_000,
|
|
|
|
|
localPaths: [
|
2026-02-17 09:56:23 +00:00
|
|
|
// Platform-suffixed binary in dist_rust (production)
|
|
|
|
|
plugins.path.join(packageDir, 'dist_rust', `remoteingress-bin_${process.platform === 'win32' ? 'windows' : 'linux'}_${process.arch === 'x64' ? 'amd64' : process.arch}`),
|
|
|
|
|
// Exact binaryName fallback in dist_rust
|
|
|
|
|
plugins.path.join(packageDir, 'dist_rust', 'remoteingress-bin'),
|
|
|
|
|
// Development build paths (cargo output uses exact name)
|
|
|
|
|
plugins.path.join(packageDir, 'rust', 'target', 'release', 'remoteingress-bin'),
|
|
|
|
|
plugins.path.join(packageDir, 'rust', 'target', 'debug', 'remoteingress-bin'),
|
2026-02-16 11:22:23 +00:00
|
|
|
],
|
|
|
|
|
searchSystemPath: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Forward events from Rust binary
|
|
|
|
|
this.bridge.on('management:tunnelConnected', () => {
|
|
|
|
|
this.emit('tunnelConnected');
|
|
|
|
|
});
|
|
|
|
|
this.bridge.on('management:tunnelDisconnected', () => {
|
|
|
|
|
this.emit('tunnelDisconnected');
|
|
|
|
|
});
|
|
|
|
|
this.bridge.on('management:publicIpDiscovered', (data: { ip: string }) => {
|
|
|
|
|
this.emit('publicIpDiscovered', data);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Start the edge — spawns the Rust binary and connects to the hub.
|
2026-02-17 19:36:40 +00:00
|
|
|
* Accepts either a connection token or an explicit IEdgeConfig.
|
2026-02-16 11:22:23 +00:00
|
|
|
*/
|
2026-02-17 19:36:40 +00:00
|
|
|
public async start(config: { token: string } | IEdgeConfig): Promise<void> {
|
|
|
|
|
let edgeConfig: IEdgeConfig;
|
|
|
|
|
|
|
|
|
|
if ('token' in config) {
|
|
|
|
|
const decoded = decodeConnectionToken(config.token);
|
|
|
|
|
edgeConfig = {
|
|
|
|
|
hubHost: decoded.hubHost,
|
|
|
|
|
hubPort: decoded.hubPort,
|
|
|
|
|
edgeId: decoded.edgeId,
|
|
|
|
|
secret: decoded.secret,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
edgeConfig = config;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 11:22:23 +00:00
|
|
|
const spawned = await this.bridge.spawn();
|
|
|
|
|
if (!spawned) {
|
|
|
|
|
throw new Error('Failed to spawn remoteingress-bin');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.bridge.sendCommand('startEdge', {
|
2026-02-17 19:36:40 +00:00
|
|
|
hubHost: edgeConfig.hubHost,
|
|
|
|
|
hubPort: edgeConfig.hubPort ?? 8443,
|
|
|
|
|
edgeId: edgeConfig.edgeId,
|
|
|
|
|
secret: edgeConfig.secret,
|
2026-02-16 11:22:23 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.started = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stop the edge and kill the Rust process.
|
|
|
|
|
*/
|
|
|
|
|
public async stop(): Promise<void> {
|
|
|
|
|
if (this.started) {
|
|
|
|
|
try {
|
|
|
|
|
await this.bridge.sendCommand('stopEdge', {} as Record<string, never>);
|
|
|
|
|
} catch {
|
|
|
|
|
// Process may already be dead
|
|
|
|
|
}
|
|
|
|
|
this.bridge.kill();
|
|
|
|
|
this.started = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the current edge status.
|
|
|
|
|
*/
|
|
|
|
|
public async getStatus() {
|
|
|
|
|
return this.bridge.sendCommand('getEdgeStatus', {} as Record<string, never>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if the bridge is running.
|
|
|
|
|
*/
|
|
|
|
|
public get running(): boolean {
|
|
|
|
|
return this.bridge.running;
|
|
|
|
|
}
|
|
|
|
|
}
|