Files
remoteingress/ts/classes.remoteingresshub.ts

143 lines
4.0 KiB
TypeScript
Raw Normal View History

import * as plugins from './plugins.js';
import { EventEmitter } from 'events';
// Command map for the hub side of remoteingress-bin
type THubCommands = {
ping: {
params: Record<string, never>;
result: { pong: boolean };
};
startHub: {
params: {
tunnelPort: number;
targetHost?: string;
};
result: { started: boolean };
};
stopHub: {
params: Record<string, never>;
result: { stopped: boolean; wasRunning?: boolean };
};
updateAllowedEdges: {
params: {
edges: Array<{ id: string; secret: string }>;
};
result: { updated: boolean };
};
getHubStatus: {
params: Record<string, never>;
result: {
running: boolean;
tunnelPort: number;
connectedEdges: Array<{
edgeId: string;
connectedAt: number;
activeStreams: number;
}>;
};
};
};
export interface IHubConfig {
tunnelPort?: number;
targetHost?: string;
}
export class RemoteIngressHub extends EventEmitter {
private bridge: InstanceType<typeof plugins.smartrust.RustBridge<THubCommands>>;
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<THubCommands>({
binaryName: 'remoteingress-bin',
cliArgs: ['--management'],
requestTimeoutMs: 30_000,
readyTimeoutMs: 10_000,
localPaths: [
// 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'),
],
searchSystemPath: false,
});
// Forward events from Rust binary
this.bridge.on('management:edgeConnected', (data: { edgeId: string }) => {
this.emit('edgeConnected', data);
});
this.bridge.on('management:edgeDisconnected', (data: { edgeId: string }) => {
this.emit('edgeDisconnected', data);
});
this.bridge.on('management:streamOpened', (data: { edgeId: string; streamId: number }) => {
this.emit('streamOpened', data);
});
this.bridge.on('management:streamClosed', (data: { edgeId: string; streamId: number }) => {
this.emit('streamClosed', data);
});
}
/**
* Start the hub spawns the Rust binary and starts the tunnel server.
*/
public async start(config: IHubConfig = {}): Promise<void> {
const spawned = await this.bridge.spawn();
if (!spawned) {
throw new Error('Failed to spawn remoteingress-bin');
}
await this.bridge.sendCommand('startHub', {
tunnelPort: config.tunnelPort ?? 8443,
targetHost: config.targetHost ?? '127.0.0.1',
});
this.started = true;
}
/**
* Stop the hub and kill the Rust process.
*/
public async stop(): Promise<void> {
if (this.started) {
try {
await this.bridge.sendCommand('stopHub', {} as Record<string, never>);
} catch {
// Process may already be dead
}
this.bridge.kill();
this.started = false;
}
}
/**
* Update the list of allowed edges that can connect to this hub.
*/
public async updateAllowedEdges(edges: Array<{ id: string; secret: string }>): Promise<void> {
await this.bridge.sendCommand('updateAllowedEdges', { edges });
}
/**
* Get the current hub status.
*/
public async getStatus() {
return this.bridge.sendCommand('getHubStatus', {} as Record<string, never>);
}
/**
* Check if the bridge is running.
*/
public get running(): boolean {
return this.bridge.running;
}
}