import * as decoding from "lib0/decoding"; import * as encoding from "lib0/encoding"; import { MESSAGE_AWARENESS } from "~/lib/editor-protocol"; /** * y-protocols awareness updates, framed without a server-side Awareness instance. The room only relays * and caches them, because Awareness keeps a timer running that would stop the Durable Object hibernating. */ export type AwarenessEntry = { clientId: number; clock: number; state: unknown }; export function decodeAwareness(update: Uint8Array): AwarenessEntry[] { const decoder = decoding.createDecoder(update); const count = decoding.readVarUint(decoder); const entries: AwarenessEntry[] = []; for (let index = 0; index < count; index++) { const clientId = decoding.readVarUint(decoder); const clock = decoding.readVarUint(decoder); entries.push({ clientId, clock, state: JSON.parse(decoding.readVarString(decoder)) }); } return entries; } export function encodeAwareness(entries: AwarenessEntry[]): Uint8Array { const encoder = encoding.createEncoder(); encoding.writeVarUint(encoder, entries.length); for (const entry of entries) { encoding.writeVarUint(encoder, entry.clientId); encoding.writeVarUint(encoder, entry.clock); encoding.writeVarString(encoder, JSON.stringify(entry.state)); } return encoding.toUint8Array(encoder); } /** Wraps an awareness update in the room's binary frame. */ export function awarenessFrame(update: Uint8Array): Uint8Array { const encoder = encoding.createEncoder(); encoding.writeVarUint(encoder, MESSAGE_AWARENESS); encoding.writeVarUint8Array(encoder, update); return encoding.toUint8Array(encoder); } /** A removal tells peers a client left: same id, a higher clock and a null state. */ export function removalFrame(clientId: number, clock: number): Uint8Array { return awarenessFrame(encodeAwareness([{ clientId, clock: clock + 1, state: null }])); } /** Stable synthetic awareness client id for an agent, outside the random 32-bit range Yjs clients use. */ export function agentClientId(name: string): number { let hash = 0; for (const char of name) hash = (hash * 31 + char.charCodeAt(0)) >>> 0; return 2 ** 32 + hash; } export function base64(bytes: Uint8Array): string { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary); } export function unbase64(value: string): Uint8Array { return Uint8Array.from(atob(value), (char) => char.charCodeAt(0)); }