import { assertRealtimeRoomName, type RealtimeMessage } from "./messages.ts"; export interface BrowserRoomConnection { readonly name: string; send(message: unknown): BrowserRoomConnection; on( type: string | ((message: unknown) => void), callback?: (message: unknown) => void, ): BrowserRoomConnection; close(): void; } export interface WrnexusRealtimeWindow extends Window { wire?: { room?: (name: string, query?: string) => BrowserRoomConnection; }; } const QUERY_KEY = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/; export function roomQuery( params: Record, ): string { const query = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (!QUERY_KEY.test(key)) throw new TypeError(`Invalid realtime query key: ${key}`); if (value !== undefined && value !== null) query.set(key, String(value)); } const result = query.toString(); if (result.length > 2_048) throw new RangeError("Realtime room query exceeds 2048 characters."); return result; } export function connectRoom( name: string, options: { query?: Record; window?: WrnexusRealtimeWindow; } = {}, ): BrowserRoomConnection { const roomName = assertRealtimeRoomName(name); const target = options.window ?? (globalThis as unknown as WrnexusRealtimeWindow); const factory = target.wire?.room; if (!factory) { throw new Error( "WRN-REALTIME-CLIENT-NOT-READY: add a RealtimeRoom/data-room component before calling connectRoom().", ); } return factory(roomName, options.query ? roomQuery(options.query) : undefined); } export function sendRoomMessage( room: BrowserRoomConnection, message: RealtimeMessage | T, ): BrowserRoomConnection { return room.send(message); }