/** * WebSocket Protocol Utilities */ import * as crypto from 'crypto'; import { WEBSOCKET_MAGIC_STRING } from './constants.js'; import type { RawData } from './types.js'; /** * Get the length of a WebSocket message regardless of its type * (handles all possible WebSocket message data types) */ export function getMessageSize(data: RawData): number { if (typeof data === 'string') { // For string data, get the byte length return Buffer.from(data, 'utf8').length; } else if (data instanceof Buffer) { // For Node.js Buffer return data.length; } else if (data instanceof ArrayBuffer) { // For ArrayBuffer return data.byteLength; } else if (Array.isArray(data)) { // For array of buffers, sum their lengths return data.reduce((sum, chunk) => { if (chunk instanceof Buffer) { return sum + chunk.length; } else if (chunk instanceof ArrayBuffer) { return sum + chunk.byteLength; } return sum; }, 0); } else { // For other types, try to determine the size or return 0 try { return Buffer.from(data).length; } catch (e) { return 0; } } } /** * Convert any raw WebSocket data to Buffer for consistent handling */ export function toBuffer(data: RawData): Buffer { if (typeof data === 'string') { return Buffer.from(data, 'utf8'); } else if (data instanceof Buffer) { return data; } else if (data instanceof ArrayBuffer) { return Buffer.from(data); } else if (Array.isArray(data)) { // For array of buffers, concatenate them return Buffer.concat(data.map(chunk => { if (chunk instanceof Buffer) { return chunk; } else if (chunk instanceof ArrayBuffer) { return Buffer.from(chunk); } return Buffer.from(chunk); })); } else { // For other types, try to convert to Buffer or return empty Buffer try { return Buffer.from(data); } catch (e) { return Buffer.alloc(0); } } } /** * Generate WebSocket accept key from client key */ export function generateAcceptKey(clientKey: string): string { const hash = crypto.createHash('sha1'); hash.update(clientKey + WEBSOCKET_MAGIC_STRING); return hash.digest('base64'); } /** * Validate WebSocket upgrade request */ export function isWebSocketUpgrade(headers: Record): boolean { const upgrade = headers['upgrade']; const connection = headers['connection']; return upgrade?.toLowerCase() === 'websocket' && connection?.toLowerCase().includes('upgrade'); } /** * Generate random WebSocket key for client handshake */ export function generateWebSocketKey(): string { return crypto.randomBytes(16).toString('base64'); }