colabbd/src/protocol.ts

65 lines
1.8 KiB
TypeScript

// 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<string, unknown>;
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;
}
}