import { type CloseEvent, readAuthMessage } from "@hocuspocus/common"; import { readVarInt, readVarString } from "lib0/decoding"; import * as awarenessProtocol from "y-protocols/awareness"; import { messageYjsSyncStep2, readSyncMessage } from "y-protocols/sync"; import type { HocuspocusProvider } from "./HocuspocusProvider.ts"; import type { IncomingMessage } from "./IncomingMessage.ts"; import { OutgoingMessage } from "./OutgoingMessage.ts"; import { MessageType } from "./types.ts"; export class MessageReceiver { message: IncomingMessage; constructor(message: IncomingMessage) { this.message = message; } public apply(provider: HocuspocusProvider, emitSynced: boolean) { const { message } = this; const type = message.readVarUint(); const emptyMessageLength = message.length(); switch (type) { case MessageType.Sync: this.applySyncMessage(provider, emitSynced); break; case MessageType.Awareness: this.applyAwarenessMessage(provider); break; case MessageType.Auth: this.applyAuthMessage(provider); break; case MessageType.QueryAwareness: this.applyQueryAwarenessMessage(provider); break; case MessageType.Stateless: provider.receiveStateless(readVarString(message.decoder)); break; case MessageType.SyncStatus: this.applySyncStatusMessage( provider, readVarInt(message.decoder) === 1, ); break; case MessageType.CLOSE: { // eslint-disable-next-line no-case-declarations const event: CloseEvent = { code: 1000, reason: readVarString(message.decoder), }; provider.onClose(); provider.configuration.onClose({ event }); provider.forwardClose({ event }); break; } default: console.error(`Can’t apply message of unknown type: ${type}`); } // Reply if (message.length() > emptyMessageLength + 1) { // length of documentName (considered in emptyMessageLength plus length of yjs sync type, set in applySyncMessage) // @ts-expect-error provider.send(OutgoingMessage, { encoder: message.encoder }); } } private applySyncMessage(provider: HocuspocusProvider, emitSynced: boolean) { const { message } = this; message.writeVarUint(MessageType.Sync); // Apply update const syncMessageType = readSyncMessage( message.decoder, message.encoder, provider.document, provider, ); // Synced once we receive Step2 if (emitSynced && syncMessageType === messageYjsSyncStep2) { provider.synced = true; } } applySyncStatusMessage(provider: HocuspocusProvider, applied: boolean) { if (applied) { provider.decrementUnsyncedChanges(); } } private applyAwarenessMessage(provider: HocuspocusProvider) { if (!provider.awareness) return; const { message } = this; awarenessProtocol.applyAwarenessUpdate( provider.awareness, message.readVarUint8Array(), provider, ); } private applyAuthMessage(provider: HocuspocusProvider) { const { message } = this; readAuthMessage( message.decoder, provider.sendToken.bind(provider), provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider), ); } private applyQueryAwarenessMessage(provider: HocuspocusProvider) { if (!provider.awareness) return; const { message } = this; message.writeVarUint(MessageType.Awareness); message.writeVarUint8Array( awarenessProtocol.encodeAwarenessUpdate( provider.awareness, Array.from(provider.awareness.getStates().keys()), ), ); } }