@nodedaemon/core
Version:
Production-ready Node.js process manager with zero external dependencies
181 lines • 6.01 kB
JavaScript
"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