feat(gateway): add optional bonjour/mdns discovery

This commit is contained in:
William Valentin
2026-02-16 01:48:59 -08:00
parent 7a298576e8
commit 323bef0526
9 changed files with 410 additions and 1 deletions
+50 -1
View File
@@ -9,6 +9,7 @@ import { LaneQueue } from './lane-queue.js';
import { MetricsCollector } from './metrics.js';
import { authenticateRequest } from './auth.js';
import type { AuthConfig } from './auth.js';
import { startGatewayDiscovery, type GatewayDiscoveryHandle } from './discovery.js';
import {
parseMessage,
makeError,
@@ -86,6 +87,12 @@ export interface GatewayServerConfig {
commandRegistry?: CommandRegistry;
intentRegistry?: ComponentRegistry;
routingPolicy?: RoutingPolicy;
discovery?: {
enabled: boolean;
serviceName: string;
serviceType: string;
txtRecord?: Record<string, string>;
};
}
export class GatewayServer {
@@ -103,6 +110,7 @@ export class GatewayServer {
private sessionBridge: SessionBridge;
private laneQueue: LaneQueue;
private metrics: MetricsCollector;
private discoveryHandle: GatewayDiscoveryHandle | null = null;
private connectionMap: Map<WebSocket, string> = new Map();
private connectionRateMap: Map<string, {
tokens: number;
@@ -272,12 +280,24 @@ export class GatewayServer {
this.httpServer.listen(port, host, () => {
console.log(`Gateway server listening on ${host}:${port}`);
resolve();
void this.startDiscovery(host, port).finally(() => {
resolve();
});
});
});
}
async stop(): Promise<void> {
if (this.discoveryHandle) {
try {
await this.discoveryHandle.stop();
} catch (err) {
console.error('Failed to stop mDNS discovery:', err instanceof Error ? err.message : err);
} finally {
this.discoveryHandle = null;
}
}
// Close all WebSocket connections first
for (const [ws, connectionId] of this.connectionMap) {
this.sessionBridge.disconnect(connectionId);
@@ -543,6 +563,35 @@ export class GatewayServer {
this.config.gmailHandler = handler;
}
private async startDiscovery(host: string, port: number): Promise<void> {
const discovery = this.config.discovery;
if (!discovery?.enabled) {
return;
}
if (host === '127.0.0.1' || host === '::1') {
console.warn('mDNS discovery is enabled, but server.localhost=true restricts gateway to loopback; skipping advertisement');
return;
}
try {
const txtRecord: Record<string, string> = {
instance: `pid-${process.pid}`,
version: this.config.version ?? '0.1.0',
...(discovery.txtRecord ?? {}),
};
this.discoveryHandle = await startGatewayDiscovery({
serviceName: discovery.serviceName,
serviceType: discovery.serviceType,
port,
txtRecord,
});
console.log(`mDNS discovery enabled: ${discovery.serviceName}.${discovery.serviceType}.local:${port}`);
} catch (err) {
console.warn(`mDNS discovery failed to start: ${err instanceof Error ? err.message : String(err)}`);
}
}
/** Read the full request body as a string. */
private readRequestBody(req: IncomingMessage): Promise<string> {
const maxBytes = this.config.maxRequestBodyBytes ?? GatewayServer.DEFAULT_MAX_REQUEST_BODY_BYTES;