import { createHash } from "node:crypto"; const WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; function frame(opcode, payload = Buffer.alloc(0)) { const body = Buffer.isBuffer(payload) ? payload : Buffer.from(payload); let header; if (body.length < 126) { header = Buffer.from([0x80 | opcode, body.length]); } else if (body.length <= 0xffff) { header = Buffer.allocUnsafe(4); header[0] = 0x80 | opcode; header[1] = 126; header.writeUInt16BE(body.length, 2); } else { header = Buffer.allocUnsafe(10); header[0] = 0x80 | opcode; header[1] = 127; header.writeBigUInt64BE(BigInt(body.length), 2); } return Buffer.concat([header, body]); } export function upgradeArenaWebSocket(request, socket, head, arenaService) { const url = new URL(request.url ?? "/", "http://localhost"); if (url.pathname !== "/ws/arena") return false; const key = String(request.headers["sec-websocket-key"] ?? ""); const version = String(request.headers["sec-websocket-version"] ?? ""); const accountId = arenaService.consumeSocketTicket(url.searchParams.get("ticket") ?? ""); if (!accountId || !key || version !== "13") { socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"); return true; } const accept = createHash("sha1").update(`${key}${WEBSOCKET_GUID}`).digest("base64"); socket.write([ "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Connection: Upgrade", `Sec-WebSocket-Accept: ${accept}`, "\r\n", ].join("\r\n")); let buffered = Buffer.from(head ?? Buffer.alloc(0)); let closed = false; const send = (message) => { if (!closed && socket.writable) socket.write(frame(0x1, JSON.stringify(message))); }; const unsubscribe = arenaService.subscribe(accountId, send); function close(code = 1000, reason = "") { if (closed) return; closed = true; const reasonBytes = Buffer.from(String(reason).slice(0, 100)); const body = Buffer.allocUnsafe(2 + reasonBytes.length); body.writeUInt16BE(code, 0); reasonBytes.copy(body, 2); if (socket.writable) socket.end(frame(0x8, body)); unsubscribe(); } function parse() { while (buffered.length >= 2) { const first = buffered[0]; const second = buffered[1]; const final = Boolean(first & 0x80); const opcode = first & 0x0f; const masked = Boolean(second & 0x80); let length = second & 0x7f; let offset = 2; if (!final || !masked) return close(1002, "Invalid client frame"); if (length === 126) { if (buffered.length < 4) return; length = buffered.readUInt16BE(2); offset = 4; } else if (length === 127) { if (buffered.length < 10) return; const largeLength = buffered.readBigUInt64BE(2); if (largeLength > 65536n) return close(1009, "Frame too large"); length = Number(largeLength); offset = 10; } if (length > 65536) return close(1009, "Frame too large"); if (buffered.length < offset + 4 + length) return; const mask = buffered.subarray(offset, offset + 4); const payload = Buffer.from(buffered.subarray(offset + 4, offset + 4 + length)); buffered = buffered.subarray(offset + 4 + length); for (let index = 0; index < payload.length; index += 1) payload[index] ^= mask[index % 4]; if (opcode === 0x8) return close(); if (opcode === 0x9) { if (socket.writable) socket.write(frame(0xa, payload)); continue; } if (opcode !== 0x1) return close(1003, "Text messages only"); try { const message = JSON.parse(payload.toString("utf8")); const result = arenaService.receive(accountId, message); if (!result.accepted) send({ type: "intent-rejected", sequence: message?.sequence, reason: result.reason }); } catch { send({ type: "protocol-error", message: "Arena messages must be valid JSON." }); } } } socket.on("data", (chunk) => { if (closed) return; buffered = Buffer.concat([buffered, chunk]); parse(); }); socket.on("error", () => close(1011, "Socket error")); socket.on("close", () => { if (!closed) { closed = true; unsubscribe(); } }); if (buffered.length) parse(); send({ type: "connected", queue: arenaService.queueState(accountId) }); return true; }