UNPKG

@nodedaemon/core

Version:

Production-ready Node.js process manager with zero external dependencies

181 lines 6.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SimpleWebSocketServer = exports.SimpleWebSocket = void 0; const events_1 = require("events"); const crypto_1 = require("crypto"); const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; class SimpleWebSocket extends events_1.EventEmitter { socket; isConnected = true; constructor(socket) { super(); this.socket = socket; this.socket.on('data', this.handleData.bind(this)); this.socket.on('close', () => { this.isConnected = false; this.emit('close'); }); this.socket.on('error', (err) => this.emit('error', err)); } handleData(data) { try { const frames = this.parseFrames(data); for (const frame of frames) { this.handleFrame(frame); } } catch (error) { this.emit('error', error); } } parseFrames(buffer) { const frames = []; let offset = 0; while (offset < buffer.length) { if (buffer.length - offset < 2) break; const firstByte = buffer[offset]; const secondByte = buffer[offset + 1]; const fin = !!(firstByte & 0x80); const opcode = firstByte & 0x0f; const masked = !!(secondByte & 0x80); let payloadLength = secondByte & 0x7f; offset += 2; if (payloadLength === 126) { if (buffer.length - offset < 2) break; payloadLength = buffer.readUInt16BE(offset); offset += 2; } else if (payloadLength === 127) { if (buffer.length - offset < 8) break; // For simplicity, we'll limit to 32-bit lengths offset += 4; // Skip high 32 bits payloadLength = buffer.readUInt32BE(offset); offset += 4; } let maskKey = null; if (masked) { if (buffer.length - offset < 4) break; maskKey = buffer.slice(offset, offset + 4); offset += 4; } if (buffer.length - offset < payloadLength) break; let payload = buffer.slice(offset, offset + payloadLength); offset += payloadLength; if (masked && maskKey) { for (let i = 0; i < payload.length; i++) { payload[i] ^= maskKey[i % 4]; } } frames.push({ fin, opcode, masked, payload }); } return frames; } handleFrame(frame) { switch (frame.opcode) { case 0x1: // Text frame this.emit('message', frame.payload.toString('utf8')); break; case 0x2: // Binary frame this.emit('message', frame.payload); break; case 0x8: // Close frame this.close(); break; case 0x9: // Ping frame this.pong(frame.payload); break; case 0xa: // Pong frame this.emit('pong', frame.payload); break; } } send(data) { if (!this.isConnected) return; const isBuffer = Buffer.isBuffer(data); const payload = isBuffer ? data : Buffer.from(data, 'utf8'); const opcode = isBuffer ? 0x2 : 0x1; this.sendFrame(opcode, payload); } sendFrame(opcode, payload) { const payloadLength = payload.length; let frame; if (payloadLength < 126) { frame = Buffer.allocUnsafe(2); frame[0] = 0x80 | opcode; // FIN = 1 frame[1] = payloadLength; } else if (payloadLength < 65536) { frame = Buffer.allocUnsafe(4); frame[0] = 0x80 | opcode; frame[1] = 126; frame.writeUInt16BE(payloadLength, 2); } else { frame = Buffer.allocUnsafe(10); frame[0] = 0x80 | opcode; frame[1] = 127; frame.writeUInt32BE(0, 2); // High 32 bits frame.writeUInt32BE(payloadLength, 6); } this.socket.write(Buffer.concat([frame, payload])); } ping(data) { this.sendFrame(0x9, data || Buffer.alloc(0)); } pong(data) { this.sendFrame(0xa, data || Buffer.alloc(0)); } close() { if (!this.isConnected) return; this.isConnected = false; this.sendFrame(0x8, Buffer.alloc(0)); this.socket.end(); } get readyState() { return this.isConnected ? 1 : 3; // OPEN : CLOSED } } exports.SimpleWebSocket = SimpleWebSocket; class SimpleWebSocketServer extends events_1.EventEmitter { clients = new Set(); handleUpgrade(req, socket, head) { const key = req.headers['sec-websocket-key']; if (!key) { socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); return; } const acceptKey = (0, crypto_1.createHash)('sha1') .update(key + GUID) .digest('base64'); const responseHeaders = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${acceptKey}`, '', '' ].join('\r\n'); socket.write(responseHeaders); const ws = new SimpleWebSocket(socket); this.clients.add(ws); ws.on('close', () => { this.clients.delete(ws); }); this.emit('connection', ws, req); } close() { for (const client of this.clients) { client.close(); } this.clients.clear(); } } exports.SimpleWebSocketServer = SimpleWebSocketServer; //# sourceMappingURL=WebSocketServer.js.map