Skip to content

Instantly share code, notes, and snippets.

@randName
Last active February 6, 2025 14:22
Show Gist options
  • Save randName/11431fee29b38b4380897e264e410d21 to your computer and use it in GitHub Desktop.
Save randName/11431fee29b38b4380897e264e410d21 to your computer and use it in GitHub Desktop.
node WebSocket server

WebSocket server in node

import { createServer } from 'node:http'
import { makeHandshake, readMessage, generateMessage } from './wss.js'
const server = createServer(async (req, res) => {
//
})
server.on('upgrade', (req, socket) => {
socket.write(makeHandshake(req))
const { host } = new URL(req.headers.origin)
console.log(`connected from`, host)
socket.on('readable', () => {
console.log('message', readMessage((n) => socket.read(n)))
})
const send = (s) => socket.write(generateMessage(s))
})
import { createHash } from 'node:crypto'
/** @import { IncomingMessage } from 'node:http' */
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
const WS_HEAD = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
]
const MAX_MSG_LEN = 2 ** 16 - 1
const encoder = new TextEncoder()
const decoder = new TextDecoder()
/** @param {IncomingMessage} req */
export function makeHandshake(req) {
const key = `${req.headers['sec-websocket-key']}${WS_MAGIC}`
const dg = createHash('sha1').update(key).digest('base64')
return [...WS_HEAD, `Sec-WebSocket-Accept: ${dg}`, '', ''].join('\r\n')
}
/** @param {string} message */
export function generateMessage(message) {
const msg = encoder.encode(message)
const len = msg.length
if (len > MAX_MSG_LEN) throw new Error(`too long (${len})`)
const head = len < 126 ? [0x81, len] : [0x81, 0x7e, len >> 8, len & 0xff]
const out = new Uint8Array(head.length + len)
out.set(head)
out.set(msg, head.length)
return out
}
/** @param {(n: number) => Buffer} read */
export function readMessage(read) {
read(1)
const lenB = read(1)[0] - 128
const msgLen =
lenB < 126
? lenB
: lenB === 126
? read(2).readUint16BE(0)
: Number(read(8).readBigUInt64BE(0))
const mask = read(4)
const msg = read(msgLen)
if (!msg) return ''
const encoded = msg.map((v, i) => v ^ mask[i % 4])
return decoder.decode(encoded)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment