import { WebSocketServer } from 'ws'; import * as net from 'net'; import * as tls from 'tls'; import { parse } from 'url'; // Create WebSocket server instance const wss = new WebSocketServer({ noServer: true }); // Active connections and their proxies const connections = new Map(); // Handle WebSocket connections wss.on('connection', (ws, req, mudHost, mudPort, useSSL) => { console.log(`WebSocket connection established for ${mudHost}:${mudPort} (SSL: ${useSSL})`); // Create a unique ID for this connection const connectionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // Special handling for test connections if (mudHost === 'example.com' && mudPort === '23') { console.log('Test connection detected - using echo server mode'); // Send welcome message ws.send('Hello from WebSocket test server! This is an echo server.'); // Echo back messages ws.on('message', (message) => { console.log('Test server received:', message.toString()); ws.send(`Echo: ${message.toString()}`); }); // Handle close ws.on('close', () => { console.log('Test connection closed'); connections.delete(connectionId); }); // Store the connection (without a socket) connections.set(connectionId, { ws, testMode: true }); return; } let socket; try { // Create a TCP socket connection to the MUD server // Use tls for SSL connections, net for regular connections socket = useSSL ? tls.connect({ host: mudHost, port: parseInt(mudPort), rejectUnauthorized: false }) : net.createConnection({ host: mudHost, port: parseInt(mudPort) }); // Add error handler socket.on('error', (error) => { console.error(`Socket error for ${mudHost}:${mudPort}:`, error.message); // Send error to client if (ws.readyState === 1) { ws.send(Buffer.from(`ERROR: Connection to MUD server failed: ${error.message}\r\n`)); setTimeout(() => { if (ws.readyState === 1) ws.close(); }, 1000); } // Remove from connections map connections.delete(connectionId); }); // Store the connection connections.set(connectionId, { ws, socket }); } catch (error) { console.error(`Error creating socket connection: ${error.message}`); if (ws.readyState === 1) { ws.send(Buffer.from(`ERROR: Failed to connect to MUD server: ${error.message}\r\n`)); ws.close(); } return; } // Handle data from the MUD server - only in regular mode, not test mode if (socket) { socket.on('data', (data) => { // Check for GMCP data (IAC SB GMCP) - very basic check for debugging // IAC = 255, SB = 250, GMCP = 201 let isGmcp = false; for (let i = 0; i < data.length - 2; i++) { if (data[i] === 255 && data[i+1] === 250 && data[i+2] === 201) { isGmcp = true; console.log('WebSocket server: Detected GMCP data in server response'); break; } } // Forward data to the WebSocket client if it's still open if (ws.readyState === 1) { // WebSocket.OPEN ws.send(data); console.log(`WebSocket server: Sent ${data.length} bytes to client${isGmcp ? ' (contains GMCP data)' : ''}`); } }); } // Handle socket close if (socket) { socket.on('close', () => { console.log(`MUD connection closed for ${mudHost}:${mudPort}`); // Close WebSocket if it's still open if (ws.readyState === 1) { ws.close(); } // Remove from connections map connections.delete(connectionId); }); } // Handle WebSocket messages (data from client to server) ws.on('message', (message) => { try { // Skip if this is a test connection (already handled in the test mode section) const conn = connections.get(connectionId); if (conn.testMode) return; // Check for GMCP data (IAC SB GMCP) in client messages let isGmcp = false; if (message instanceof Buffer || message instanceof Uint8Array) { for (let i = 0; i < message.length - 2; i++) { if (message[i] === 255 && message[i+1] === 250 && message[i+2] === 201) { isGmcp = true; console.log('WebSocket server: Detected GMCP data in client message'); break; } } } // Forward data to the MUD server // The message might be Buffer, ArrayBuffer, or string if (conn.socket && conn.socket.writable) { conn.socket.write(message); console.log(`WebSocket server: Sent ${message.length} bytes to MUD server${isGmcp ? ' (contains GMCP data)' : ''}`); } else { console.error('Socket not writable, cannot send data to MUD server'); if (ws.readyState === 1) { // WebSocket.OPEN ws.send(Buffer.from(`ERROR: Cannot send data to MUD server: Socket not connected\r\n`)); } } } catch (error) { console.error('Error forwarding message to MUD server:', error); if (ws.readyState === 1) { // WebSocket.OPEN ws.send(Buffer.from(`ERROR: Failed to send data to MUD server: ${error.message}\r\n`)); } } }); // Handle WebSocket close ws.on('close', () => { console.log(`WebSocket closed for ${mudHost}:${mudPort}`); // Close socket if it's still open const conn = connections.get(connectionId); if (conn && conn.socket) { conn.socket.end(); } // Remove from connections map connections.delete(connectionId); }); // Handle WebSocket errors ws.on('error', (error) => { console.error(`WebSocket error for ${mudHost}:${mudPort}:`, error.message); // Close socket on error const conn = connections.get(connectionId); if (conn && conn.socket) { conn.socket.end(); } // Remove from connections map connections.delete(connectionId); }); }); // Handle WebSocket upgrades export const handleWebSocket = (server) => { server.on('upgrade', (request, socket, head) => { // Parse URL to get query parameters const { pathname, query } = parse(request.url, true); // Only handle WebSocket connections to /mud-ws if (pathname === '/mud-ws') { // Extract MUD server details from query parameters const { host, port, useSSL } = query; if (!host || !port) { socket.write('HTTP/1.1 400 Bad Request\r\n\r\n'); socket.destroy(); return; } // Handle WebSocket upgrade wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request, host, port, useSSL === 'true'); }); } else { // For other upgrades (not to /mud-ws), let SvelteKit handle them // Don't destroy the socket here } }); }; // Standard SvelteKit hook export async function handle({ event, resolve }) { return await resolve(event); }