Skip to content

Instantly share code, notes, and snippets.

View yakovenkodenis's full-sized avatar
:octocat:

Denis Yakovenko yakovenkodenis

:octocat:
View GitHub Profile
@yakovenkodenis
yakovenkodenis / ws.js
Last active January 14, 2024 09:28
WebSocket server from scratch in Node.js
const http = require('node:http');
const crypto = require('node:crypto');
const { setTimeout: sleep } = require('node:timers/promises');
const { EventEmitter } = require('node:events');
class WebSocketServer extends EventEmitter {
constructor(options = {}) {
super();
this.clients = new Set();
this.port = options.port || 8080;
const PORT = 4000;
const server = new WebSocketServer({ port: PORT });
server.on('data', async (message, reply) => {
if (!message) return;
const data = JSON.parse(message);
const { method, args = [] } = data;
const handler = api[method];
if (!handler) return reply({ error: 'Not Found' });
const api = {
auth: async (login, password) => {
await sleep(300); // simulate asynchronous call
if (login === 'admin' && password === 'secret') {
return {
token: crypto.randomBytes(20).toString('hex')
};
}
return {
const PORT = 4000.
const server = new WebSocketServer({ port: PORT });
server.on('headers', ({ headers }) => console.log(headers));
server.on('data', (message, reply) => {
if (!message) return;
const data = JSON.parse(message);
return reply({ pong: data });
});
socket.on('data', (buffer) =>
this.emit(
'data',
this.parseFrame(buffer),
(data) => socket.write(this.createFrame(data))
)
);
createFrame(data) {
const payload = JSON.stringify(data);
const payloadByteLength = Buffer.byteLength(payload);
let payloadBytesOffset = 2;
let payloadLength = payloadByteLength;
if (payloadByteLength > 65535) { // length value cannot fit in 2 bytes
payloadBytesOffset += 8;
payloadLength = 127;
const PORT = 4000.
const server = new WebSocketServer({ port: PORT });
server.on('data', (message) => {
if (!message) return;
const data = JSON.parse(message);
console.log('Message received:', data);
});
this._server.on('upgrade', (req, socket) =>
// ...request upgrade logic...
socket.on('data', (buffer) =>
this.emit('data', this.parseFrame(buffer))
);
});
_unmask(payload, maskingKey) {
const result = Buffer.alloc(payload.byteLength);
for (let i = 0; i < payload.byteLength; ++i) {
const j = i % 4;
const maskingKeyByteShift = j === 3 ? 0 : (3 - j) << 3;
const maskingKeyByte = (maskingKeyByteShift === 0 ? maskingKey : maskingKey >>> maskingKeyByteShift) & 0b11111111;
const transformedByte = maskingKeyByte ^ payload.readUInt8(i);
result.writeUInt8(transformedByte, i);
}
parseFrame(buffer) {
// ... first and second byte processing ...
const isMasked = Boolean((secondByte >>> 7) & 0b00000001); // get first bit of a second byte
if (isMasked) {
const maskingKey = buffer.readUInt32BE(offset); // read 4-byte (32-bit) masking key
offset += 4;
const payload = buffer.subarray(offset);
const result = this._unmask(payload, maskingKey);