import { WebSocket, WebSocketServer } from 'ws';
import { v4 as uuidv4 } from 'uuid';
import { Command, BridgeResponse } from './types.js';
export class BridgeManager {
private wss: WebSocketServer | null = null;
private activeClient: WebSocket | null = null;
private pendingResponses = new Map<string, { resolve: (value: any) => void; reject: (reason?: any) => void }>();
constructor() {}
public startServer(port: number) {
this.wss = new WebSocketServer({ port });
this.wss.on('connection', (ws) => {
console.error('Roblox client connected');
this.activeClient = ws;
ws.on('message', (data) => {
try {
const messageStr = data.toString();
const response: BridgeResponse = JSON.parse(messageStr);
this.handleResponse(response);
} catch (err) {
console.error('Failed to parse message from Roblox:', err);
}
});
ws.on('close', () => {
console.error('Roblox client disconnected');
if (this.activeClient === ws) {
this.activeClient = null;
}
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});
});
console.error(`WebSocket server listening on port ${port}`);
}
/**
* Enqueues a command to be sent to the Roblox client.
* Returns a promise that resolves with the result from Roblox.
*/
public async enqueueCommand(method: string, params: Record<string, any>): Promise<any> {
if (!this.activeClient || this.activeClient.readyState !== WebSocket.OPEN) {
throw new Error('No Roblox client connected');
}
const id = uuidv4();
const command: Command = { id, method, params };
return new Promise((resolve, reject) => {
this.pendingResponses.set(id, { resolve, reject });
// Timeout after 30 seconds
const timeout = setTimeout(() => {
if (this.pendingResponses.has(id)) {
this.pendingResponses.delete(id);
reject(new Error('Command timed out waiting for Roblox response'));
}
}, 30000);
this.activeClient!.send(JSON.stringify(command), (err) => {
if (err) {
clearTimeout(timeout);
this.pendingResponses.delete(id);
reject(err);
}
});
});
}
private handleResponse(response: BridgeResponse) {
if (!response || !response.id) return;
const pending = this.pendingResponses.get(response.id);
if (pending) {
if (response.success) {
pending.resolve(response.result);
} else {
pending.reject(new Error(response.error || 'Unknown error from Roblox'));
}
this.pendingResponses.delete(response.id);
}
}
}