refactor(websocket): move websocket logic to its own module

This commit is contained in:
Haileyesus
2026-04-25 15:39:22 +03:00
parent 3fd2353ffe
commit 2323a576a6
16 changed files with 1407 additions and 661 deletions

View File

@@ -1,3 +1,5 @@
import type { IncomingMessage } from 'node:http';
//----------------- HTTP RESPONSE SHAPES ------------
/**
* Canonical success envelope used by backend APIs that return a structured payload.
@@ -18,6 +20,43 @@ export type ApiSuccessShape<TData = unknown> = {
*/
export type AnyRecord = Record<string, any>;
// ---------------------------
//----------------- WEBSOCKET TRANSPORT TYPES ------------
/**
* Minimal websocket client contract used by backend broadcaster services.
*
* Any transport object added to `connectedClients` must implement these two
* members so shared services can safely send JSON strings and check whether the
* socket is still open before broadcasting.
*/
export type RealtimeClientConnection = {
readyState: number;
send(data: string): void;
};
/**
* Authenticated user payload attached to websocket upgrade requests.
*
* Platform and OSS auth flows currently use either `id` or `userId`; both are
* represented here so websocket handlers can resolve a stable writer user id.
*/
export type AuthenticatedWebSocketUser = {
id?: string | number;
userId?: string | number;
username?: string;
[key: string]: unknown;
};
/**
* HTTP upgrade request shape after websocket authentication succeeds.
*
* `verifyClient` populates `request.user` with the authenticated payload, and
* downstream websocket handlers rely on this extended request type.
*/
export type AuthenticatedWebSocketRequest = IncomingMessage & {
user?: AuthenticatedWebSocketUser;
};
// ---------------------------
//----------------- PROVIDER MESSAGE MODEL ------------
/**

View File

@@ -182,6 +182,62 @@ export const readStringRecord = (value: unknown): Record<string, string> | undef
return Object.keys(normalized).length > 0 ? normalized : undefined;
};
// ---------------------------
//----------------- WEBSOCKET PAYLOAD PARSING UTILITIES ------------
/**
* Parses one websocket message payload into a plain JSON object record.
*
* Use this in realtime handlers that receive raw websocket payloads as `string`,
* `Buffer`, `ArrayBuffer`, or chunk arrays. The helper converts supported
* payload formats to UTF-8 text, parses JSON, and returns only object payloads.
* Primitive/array/invalid payloads return `null` so callers can handle bad input
* without throwing from deeply nested message handlers.
*/
export const parseIncomingJsonObject = (payload: unknown): AnyRecord | null => {
let text: string | null = null;
if (typeof payload === 'string') {
text = payload;
} else if (Buffer.isBuffer(payload)) {
text = payload.toString('utf8');
} else if (payload instanceof ArrayBuffer) {
text = Buffer.from(payload).toString('utf8');
} else if (Array.isArray(payload)) {
const buffers = payload
.map((entry) => {
if (Buffer.isBuffer(entry)) {
return entry;
}
if (entry instanceof ArrayBuffer) {
return Buffer.from(entry);
}
if (ArrayBuffer.isView(entry)) {
return Buffer.from(entry.buffer, entry.byteOffset, entry.byteLength);
}
return null;
})
.filter((entry): entry is Buffer => entry !== null);
if (buffers.length > 0) {
text = Buffer.concat(buffers).toString('utf8');
}
}
if (typeof text !== 'string' || text.trim().length === 0) {
return null;
}
try {
const parsed = JSON.parse(text) as unknown;
return readObjectRecord(parsed);
} catch {
return null;
}
};
/**
* Reads a JSON config file and guarantees a plain object result.
*