forked from phenom/healer-man
Release Healer Man 0.1.16
This commit is contained in:
1 parent
f49e54f35f
commit
b57d2b2839
89 files changed
+6055
-17
No files matched your search
@@ -0,0 +1,119 @@
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user