summary refs log tree commit diff
path: root/src/gateway/events
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2026-08-15 17:09:40 +0200
committerRory& <root@rory.gay>2026-08-15 17:15:40 +0200
commit9f5f2a2c3b71550f21f75500534bf1332b94f771 (patch)
tree4572ef19b7e25fee06da36a62105b197cb45fade /src/gateway/events
parentAllow avatar_description field in profile update (diff)
downloadserver-ts-dev/gwsep.tar.xz
Decouple gateway connection state from inner ws object github/dev/gwsep dev/gwsep
Diffstat (limited to 'src/gateway/events')
-rw-r--r--src/gateway/events/Close.ts52
-rw-r--r--src/gateway/events/Connection.ts43
-rw-r--r--src/gateway/events/Message.ts46
3 files changed, 67 insertions, 74 deletions
diff --git a/src/gateway/events/Close.ts b/src/gateway/events/Close.ts

index 0cdd5741..3f3e9475 100644 --- a/src/gateway/events/Close.ts +++ b/src/gateway/events/Close.ts
@@ -22,41 +22,41 @@ import { WebSocket } from "@spacebar/gateway/util"; import { emitEvent, PresenceUpdateEvent, SessionsReplace, VoiceStateUpdateEvent, distributePresenceUpdate } from "@spacebar/util"; import { ProcessLifecycle } from "@spacebar/util/util/ProcessLifecycle"; -export async function Close(this: WebSocket, code: number, reason: Buffer) { +export async function Close(socket: WebSocket, code: number, reason: Buffer) { console.log("[WebSocket] closed", code, reason.toString()); - if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); - if (this.readyTimeout) clearTimeout(this.readyTimeout); - this.deflate?.close(); - this.inflate?.close(); - this.removeAllListeners(); + if (socket.heartbeatTimeout) clearTimeout(socket.heartbeatTimeout); + if (socket.readyTimeout) clearTimeout(socket.readyTimeout); + socket.deflate?.close(); + socket.inflate?.close(); + socket.rawSocket.removeAllListeners(); - if (this.session) { - const authSessionId = this.session?.session_id; + if (socket.session) { + const authSessionId = socket.session?.session_id; const closedAt = Date.now(); if (!(ProcessLifecycle.state === "stopping" || ProcessLifecycle.state === "stopped")) setTimeout(async () => { console.log("Handling presence update after disconnect"); try { - if (authSessionId && this.user_id) { + if (authSessionId && socket.user_id) { const s = await Session.findOne({ - where: { user_id: this.user_id, session_id: authSessionId }, + where: { user_id: socket.user_id, session_id: authSessionId }, }); if (s && (s.last_seen?.getTime() ?? 0) <= closedAt) { console.log("... updating session"); - await Session.update({ user_id: this.user_id, session_id: authSessionId }, { status: "offline", activities: [], client_status: {} }); - this.session = await Session.findOneOrFail({ where: { session_id: this.session_id } }); + await Session.update({ user_id: socket.user_id, session_id: authSessionId }, { status: "offline", activities: [], client_status: {} }); + socket.session = await Session.findOneOrFail({ where: { session_id: socket.session_id } }); console.log("... distributing PRESENCE_UPDATE"); - await distributePresenceUpdate(this.user_id, { + await distributePresenceUpdate(socket.user_id, { event: "PRESENCE_UPDATE", data: { - user: (await User.findOneOrFail({ where: { id: this.user_id } })).toPublicUser(), - status: this.session!.getPublicStatus(), - client_status: this.session!.client_status, - activities: this.session!.activities, + user: (await User.findOneOrFail({ where: { id: socket.user_id } })).toPublicUser(), + status: socket.session!.getPublicStatus(), + client_status: socket.session!.client_status, + activities: socket.session!.activities, }, origin: "GATEWAY_CLOSE", - transaction_id: `IDENT_${this.user_id}_${Random.getString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 6)}`, + transaction_id: `IDENT_${socket.user_id}_${Random.getString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 6)}`, } satisfies PresenceUpdateEvent); console.log("... done!"); } else console.log("... Discarding presence update as the session reactivated"); @@ -66,13 +66,13 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) { } }, 10_000); - if (!this.user_id) console.error("No user id in websocket???", this); + if (!socket.user_id) console.error("No user id in websocket???", socket); const voiceState = await VoiceState.findOne({ - where: { user_id: this.user_id }, + where: { user_id: socket.user_id }, }); // clear the voice state for this session if user was in voice channel - if (voiceState && voiceState.session_id === this.session_id && voiceState.channel_id) { + if (voiceState && voiceState.session_id === socket.session_id && voiceState.channel_id) { const prevGuildId = voiceState.guild_id; const prevChannelId = voiceState.channel_id; @@ -104,13 +104,13 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) { } } - if (this.user_id) { + if (socket.user_id) { const sessions = await Session.find({ - where: { user_id: this.user_id }, + where: { user_id: socket.user_id }, }); await emitEvent({ event: "SESSIONS_REPLACE", - user_id: this.user_id, + user_id: socket.user_id, data: sessions.map((x) => x.toPrivateGatewayDeviceInfo()), } as SessionsReplace); const session = sessions[0] || { @@ -119,13 +119,13 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) { status: "offline", }; - const user = await User.getPublicUser(this.user_id).catch(() => undefined); + const user = await User.getPublicUser(socket.user_id).catch(() => undefined); // Special case: dont emit a presence update for deleted users if (user !== undefined) await emitEvent({ event: "PRESENCE_UPDATE", - user_id: this.user_id, + user_id: socket.user_id, data: { user: user, activities: session.activities, diff --git a/src/gateway/events/Connection.ts b/src/gateway/events/Connection.ts
index 32f4e14d..bd17628f 100644 --- a/src/gateway/events/Connection.ts +++ b/src/gateway/events/Connection.ts
@@ -48,10 +48,12 @@ const openConnectionCount = Monitoring.attachMetric( }), ); -export async function Connection(this: WS.Server, socket: WebSocket, request: IncomingMessage) { +export async function Connection(this: WS.Server, rawSocket: WS, request: IncomingMessage) { + const socket = new WebSocket(rawSocket); + openConnections.push(socket); openConnectionCount.set(openConnections.length); - socket.on("close", () => { + socket.rawSocket.on("close", () => { const index = openConnections.indexOf(socket); if (index !== -1) openConnections.splice(index, 1); openConnectionCount.set(openConnections.length); @@ -64,15 +66,15 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In d: Math.round(Math.random() * 5000), }); - const closeListeners = socket.listeners("close"); + const closeListeners = socket.rawSocket.listeners("close"); for (const listener of closeListeners) { - socket.off("close", listener); + socket.rawSocket.off("close", listener); // noinspection JSVoidFunctionReturnValueUsed - awaiting results const res = listener.call(socket, 1000, 0) as void | Promise<void>; if (res) await res; } - socket.close(1000); + socket.rawSocket.close(1000); }; if (ProcessLifecycle.state == "stopping" || ProcessLifecycle.state == "stopped") return await onShutdown(); @@ -86,12 +88,12 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In if (!ipAddress && Config.get().security.cdnSignatureIncludeIp) { console.error("Gateway connection rejected: No IP address found."); - return socket.close(CLOSECODES.Decode_error, "Gateway connection rejected: IP address is required."); + return socket.rawSocket.close(CLOSECODES.Decode_error, "Gateway connection rejected: IP address is required."); } if (!socket.userAgent && Config.get().security.cdnSignatureIncludeUserAgent) { console.error("Gateway connection rejected: No User-Agent header found."); - return socket.close(CLOSECODES.Decode_error, "Gateway connection rejected: User-Agent header is required."); + return socket.rawSocket.close(CLOSECODES.Decode_error, "Gateway connection rejected: User-Agent header is required."); } if (request.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) { @@ -105,12 +107,9 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In socket.session_id = "TEMP_" + genSessionId(); //Set the session of the WebSocket object try { - // @ts-ignore - socket.on("close", Close); - // @ts-ignore - socket.on("message", Message); - - socket.on("error", (err) => console.error(`[Gateway/${socket.user_id ?? socket.ipAddress}]`, err)); + socket.rawSocket.on("close", (code, reason) => Close(socket, code, reason)); + socket.rawSocket.on("message", (data, isBinary) => Message(socket, data as Buffer)); + socket.rawSocket.on("error", (err) => console.error(`[Gateway/${socket.user_id ?? socket.ipAddress}]`, err)); console.log(`[Gateway] New connection from ${ipAddress}, total ${this.clients.size}`); @@ -125,7 +124,7 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In "pong", "unexpected-response", ].forEach((x) => { - socket.on(x, (y) => console.log(x, y)); + socket.rawSocket.on(x, (y) => console.log(x, y)); }); const { searchParams } = new URL(`http://localhost${request.url}`); @@ -133,13 +132,13 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In socket.encoding = searchParams.get("encoding") || "json"; if (!["json", "etf"].includes(socket.encoding)) { console.error(`[Gateway/${socket.ipAddress}] Unknown encoding: ${socket.encoding}`); - return socket.close(CLOSECODES.Decode_error); + return socket.rawSocket.close(CLOSECODES.Decode_error); } socket.version = Number(searchParams.get("version")) || 8; if (socket.version != 8) { console.error(`[Gateway/${socket.ipAddress}] Invalid API version: ${socket.version}`); - return socket.close(CLOSECODES.Invalid_API_version); + return socket.rawSocket.close(CLOSECODES.Invalid_API_version); } // @ts-ignore @@ -153,16 +152,10 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In socket.zstdDecoder = new Decoder(); } else { console.error(`[Gateway/${socket.user_id}] Unknown compression: ${socket.compress}`); - return socket.close(CLOSECODES.Decode_error); + return socket.rawSocket.close(CLOSECODES.Decode_error); } } - socket.recentTransactions = []; - socket.events = {}; - socket.member_events = {}; - socket.permissions = {}; - socket.sequence = 0; - setHeartbeat(socket); await Send(socket, { @@ -172,9 +165,9 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In }, }); - socket.readyTimeout = setTimeout(() => socket.close(CLOSECODES.Session_timed_out), 1000 * 30); + socket.readyTimeout = setTimeout(() => socket.rawSocket.close(CLOSECODES.Session_timed_out), 1000 * 30); } catch (error) { console.error(error); - return socket.close(CLOSECODES.Unknown_error); + return socket.rawSocket.close(CLOSECODES.Unknown_error); } } diff --git a/src/gateway/events/Message.ts b/src/gateway/events/Message.ts
index 7a2e49d3..d143ee69 100644 --- a/src/gateway/events/Message.ts +++ b/src/gateway/events/Message.ts
@@ -28,7 +28,7 @@ import { PayloadSchema } from "@spacebar/schemas"; const bigIntJson = BigIntJson({ storeAsString: true }); -export async function Message(this: WebSocket, buffer: WS.Data) { +export async function Message(socket: WebSocket, buffer: WS.Data) { // TODO: compression let data: Payload; @@ -37,60 +37,60 @@ export async function Message(this: WebSocket, buffer: WS.Data) { typeof buffer === "string" ) { data = bigIntJson.parse(buffer.toString()); - } else if (this.encoding === "json" && Buffer.isBuffer(buffer)) { - if (this.compress === "zlib-stream") { + } else if (socket.encoding === "json" && Buffer.isBuffer(buffer)) { + if (socket.compress === "zlib-stream") { try { - buffer = this.inflate!.process(buffer); + buffer = socket.inflate!.process(buffer); } catch { buffer = buffer.toString(); } - } else if (this.compress === "zstd-stream") { + } else if (socket.compress === "zstd-stream") { try { - buffer = await this.zstdDecoder!.decode(buffer); + buffer = await socket.zstdDecoder!.decode(buffer); } catch { buffer = buffer.toString(); } } data = bigIntJson.parse(buffer as string); - } else if (this.encoding === "etf" && Buffer.isBuffer(buffer) && erlpack) { + } else if (socket.encoding === "etf" && Buffer.isBuffer(buffer) && erlpack) { try { - // cast is ~safe: unpack returns the parsed data in the shape it was provided, @yukikaze-bot/erlpack got around this by returning `any` instead of an actual type union. + // cast is ~safe: unpack returns the parsed data in the shape it was provided, @yukikaze-bot/erlpack got around socket by returning `any` instead of an actual type union. data = erlpack.unpack(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)) as unknown as Payload; } catch { - console.error(`[Gateway/${this.user_id ?? this.ipAddress}] Failed to decode ETF payload`); - return this.close(CLOSECODES.Decode_error); + console.error(`[Gateway/${socket.user_id ?? socket.ipAddress}] Failed to decode ETF payload`); + return socket.rawSocket.close(CLOSECODES.Decode_error); } } else { - console.error(`[Gateway/${this.user_id ?? this.ipAddress}] Unknown payload format`); - return this.close(CLOSECODES.Decode_error); + console.error(`[Gateway/${socket.user_id ?? socket.ipAddress}] Unknown payload format`); + return socket.rawSocket.close(CLOSECODES.Decode_error); } - if (process.env.WS_VERBOSE) console.log(`[Websocket] Incomming message: ${JSON.stringify(data)}`); + if (process.env.WS_VERBOSE) console.log(`[Websocket] Incoming message: ${JSON.stringify(data)}`); if (process.env.WS_DUMP) { - const id = this.session_id || "unknown"; + const id = socket.session_id || "unknown"; await fs.mkdir(path.join("dump", id), { recursive: true }); await fs.writeFile(path.join("dump", id, `${Date.now()}.in.json`), JSON.stringify(data, null, 2)); - if (!this.session_id) console.log(`[Gateway/${this.user_id ?? this.ipAddress}] Unknown session id, dumping to unknown folder`); + if (!socket.session_id) console.log(`[Gateway/${socket.user_id ?? socket.ipAddress}] Unknown session id, dumping to unknown folder`); } - check.call(this, PayloadSchema, data); + check.call(socket, PayloadSchema, data); const OPCodeHandler = OPCodeHandlers[data.op]; if (!OPCodeHandler) { - console.error(`[Gateway/${this.user_id ?? this.ipAddress}] Unknown opcode`, data.op); - // TODO: if all opcodes are implemented comment this out: - // this.close(CLOSECODES.Unknown_opcode); + console.error(`[Gateway/${socket.user_id ?? socket.ipAddress}] Unknown opcode`, data.op); + // TODO: if all opcodes are implemented comment socket out: + // socket.close(CLOSECODES.Unknown_opcode); return; } try { - return await OPCodeHandler.call(this, data); + return await OPCodeHandler.call(socket, data); } catch (error) { - console.error(`[Gateway/${this.user_id ?? this.ipAddress}] Error: Op ${data.op}`, error); - // if (!this.CLOSED && this.CLOSING) - return this.close(CLOSECODES.Unknown_error); + console.error(`[Gateway/${socket.user_id ?? socket.ipAddress}] Error: Op ${data.op}`, error); + // if (!socket.CLOSED && socket.CLOSING) + return socket.rawSocket.close(CLOSECODES.Unknown_error); } }