import { Buffer } from 'node:buffer' import type { WSContext } from 'hono/ws' import { error as logError } from '../logger' const textDecoder = new TextDecoder() export const MAX_WS_MESSAGE_SIZE = 10 * 1024 * 1024 export type DecodedWsMessage = | { ok: true; data: string; size: number } | { ok: false; reason: string; size?: number } export function decodeWsPayload(data: unknown): DecodedWsMessage { if (typeof data === 'string') { return { ok: true, data, size: Buffer.byteLength(data, 'utf8') } } if (data instanceof ArrayBuffer) { if (data.byteLength > MAX_WS_MESSAGE_SIZE) { return { ok: false, reason: 'message too large', size: data.byteLength } } return { ok: true, data: textDecoder.decode(data), size: data.byteLength } } if (data instanceof Uint8Array) { if (data.byteLength > MAX_WS_MESSAGE_SIZE) { return { ok: false, reason: 'message too large', size: data.byteLength } } return { ok: true, data: textDecoder.decode(data), size: data.byteLength } } if ( typeof SharedArrayBuffer !== 'undefined' && data instanceof SharedArrayBuffer ) { const bytes = new Uint8Array(data) if (bytes.byteLength > MAX_WS_MESSAGE_SIZE) { return { ok: false, reason: 'message too large', size: bytes.byteLength } } return { ok: true, data: textDecoder.decode(bytes), size: bytes.byteLength } } return { ok: false, reason: typeof data } } export function handleSizedWsPayload( ws: WSContext, logPrefix: string, label: string, payload: unknown, handleMessage: (data: string) => void, ): boolean { const decoded = decodeWsPayload(payload) if (!decoded.ok) { if (decoded.reason === 'message too large' && decoded.size !== undefined) { logError( `${logPrefix} Message too large on ${label}: size=${decoded.size} limit=${MAX_WS_MESSAGE_SIZE}`, ) ws.close(1009, 'message too large') return false } logError( `${logPrefix} Unsupported message payload on ${label}: ${decoded.reason}`, ) ws.close(1003, 'unsupported message payload') return false } if (decoded.size > MAX_WS_MESSAGE_SIZE) { logError( `${logPrefix} Message too large on ${label}: size=${decoded.size} limit=${MAX_WS_MESSAGE_SIZE}`, ) ws.close(1009, 'message too large') return false } handleMessage(decoded.data) return true }