chatapi / src /utils /protocol.ts
wilooper's picture
initial commit
87cb242
Raw History Blame Contribute Delete
2.53 kB
import WebSocket from 'ws';
// ── Inbound message types (client β†’ server) ──────────────────────────────────
export type ClientMessageType =
| 'heartbeat'
| 'offer'
| 'answer'
| 'ice-candidate'
| 'message'
| 'get-upload-url'
| 'relay'
| 'friend';
// ── Outbound message types (server β†’ client) ──────────────────────────────────
export type ServerMessageType =
| 'connected'
| 'ice-servers'
| 'offer'
| 'answer'
| 'ice-candidate'
| 'message'
| 'upload-url'
| 'relay'
| 'peer-online'
| 'peer-offline'
| 'offline-flush'
| 'error';
export interface ClientMessage {
type: ClientMessageType;
payload: Record<string, unknown>;
// requestId allows client to correlate responses
requestId?: string;
}
export interface ServerMessage {
type: ServerMessageType;
payload: Record<string, unknown>;
requestId?: string;
ts: number; // epoch ms
}
/**
* Parse and validate an incoming raw WS message.
* Returns null if the data is malformed β€” caller should silently drop.
*/
export function parseClientMessage(raw: WebSocket.RawData): ClientMessage | null {
try {
const str = raw.toString('utf8');
if (str.length > 65536) return null; // 64 KB hard cap β€” protects against flood
const obj = JSON.parse(str) as unknown;
if (
typeof obj !== 'object' ||
obj === null ||
typeof (obj as Record<string, unknown>).type !== 'string'
) {
return null;
}
return obj as ClientMessage;
} catch {
return null;
}
}
/**
* Serialize a server message.
* Using a single Buffer allocation avoids repeated JSON.stringify on hot paths.
*/
export function buildServerMessage(
type: ServerMessageType,
payload: Record<string, unknown>,
requestId?: string
): Buffer {
const msg: ServerMessage = { type, payload, requestId, ts: Date.now() };
return Buffer.from(JSON.stringify(msg), 'utf8');
}
/**
* Send a message to a WebSocket, catching send errors silently.
* Avoids crashing the process when a socket closes mid-send.
*/
export function safeSend(
ws: WebSocket,
type: ServerMessageType,
payload: Record<string, unknown>,
requestId?: string
): void {
if (ws.readyState !== WebSocket.OPEN) return;
try {
ws.send(buildServerMessage(type, payload, requestId));
} catch {
// Socket probably closed between the readyState check and send β€” ignore
}
}