// message types between daemon and adapters export type ClientMessage = | { type: "join"; room: string } | { type: "leave" } | { type: "update"; data: number[] } // yjs update as byte array | { type: "awareness"; data: number[] }; export type AwarenessState = { clientId: number; cursor?: { line: number; col: number }; selection?: { startLine: number; startCol: number; endLine: number; endCol: number }; name?: string; }; export type ServerMessage = | { type: "sync"; data: number[] } // full yjs state | { type: "update"; data: number[] } | { type: "awareness"; data: AwarenessState } | { type: "peers"; count: number } | { type: "error"; message: string }; export type BridgeMessage = | { type: "ready" } | { type: "connected"; room: string } | { type: "disconnected" } | { type: "content"; text: string } | { type: "peers"; count: number } | { type: "error"; message: string }; export function encode(msg: ServerMessage): string { return JSON.stringify(msg); } function isClientMessage(obj: unknown): obj is ClientMessage { if (typeof obj !== "object" || obj === null) return false; const msg = obj as Record; switch (msg.type) { case "join": return typeof msg.room === "string"; case "leave": return true; case "update": return ( Array.isArray(msg.data) && msg.data.every((n) => typeof n === "number") ); case "awareness": return ( Array.isArray(msg.data) && msg.data.every((n) => typeof n === "number") ); default: return false; } } export function decode(raw: string): ClientMessage | null { try { const parsed = JSON.parse(raw); if (!isClientMessage(parsed)) return null; return parsed; } catch { return null; } }