UNPKG

@hocuspocus/provider

Version:

hocuspocus provider

999 lines (982 loc) 35.5 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) { __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } } } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let _hocuspocus_common = require("@hocuspocus/common"); let y_protocols_awareness = require("y-protocols/awareness"); y_protocols_awareness = __toESM(y_protocols_awareness); let yjs = require("yjs"); yjs = __toESM(yjs); let _lifeomic_attempt = require("@lifeomic/attempt"); let lib0_decoding = require("lib0/decoding"); let lib0_encoding = require("lib0/encoding"); lib0_encoding = __toESM(lib0_encoding); let lib0_time = require("lib0/time"); lib0_time = __toESM(lib0_time); let y_protocols_sync = require("y-protocols/sync"); y_protocols_sync = __toESM(y_protocols_sync); //#region packages/provider/src/EventEmitter.ts var EventEmitter = class { constructor() { this.callbacks = {}; } on(event, fn) { if (!this.callbacks[event]) this.callbacks[event] = []; this.callbacks[event].push(fn); return this; } emit(event, ...args) { const callbacks = this.callbacks[event]; if (callbacks) callbacks.forEach((callback) => callback.apply(this, args)); return this; } off(event, fn) { const callbacks = this.callbacks[event]; if (callbacks) if (fn) this.callbacks[event] = callbacks.filter((callback) => callback !== fn); else delete this.callbacks[event]; return this; } removeAllListeners() { this.callbacks = {}; } }; //#endregion //#region packages/provider/src/IncomingMessage.ts var IncomingMessage = class { constructor(data) { this.data = data; this.encoder = (0, lib0_encoding.createEncoder)(); this.decoder = (0, lib0_decoding.createDecoder)(new Uint8Array(this.data)); } peekVarString() { return (0, lib0_decoding.peekVarString)(this.decoder); } readVarUint() { return (0, lib0_decoding.readVarUint)(this.decoder); } readVarString() { return (0, lib0_decoding.readVarString)(this.decoder); } readVarUint8Array() { return (0, lib0_decoding.readVarUint8Array)(this.decoder); } writeVarUint(type) { return (0, lib0_encoding.writeVarUint)(this.encoder, type); } writeVarString(string) { return (0, lib0_encoding.writeVarString)(this.encoder, string); } writeVarUint8Array(data) { return (0, lib0_encoding.writeVarUint8Array)(this.encoder, data); } length() { return (0, lib0_encoding.length)(this.encoder); } }; //#endregion //#region packages/provider/src/types.ts let MessageType = /* @__PURE__ */ function(MessageType) { MessageType[MessageType["Sync"] = 0] = "Sync"; MessageType[MessageType["Awareness"] = 1] = "Awareness"; MessageType[MessageType["Auth"] = 2] = "Auth"; MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness"; MessageType[MessageType["Stateless"] = 5] = "Stateless"; MessageType[MessageType["CLOSE"] = 7] = "CLOSE"; MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus"; MessageType[MessageType["Ping"] = 9] = "Ping"; MessageType[MessageType["Pong"] = 10] = "Pong"; return MessageType; }({}); let WebSocketStatus = /* @__PURE__ */ function(WebSocketStatus) { WebSocketStatus["Connecting"] = "connecting"; WebSocketStatus["Connected"] = "connected"; WebSocketStatus["Disconnected"] = "disconnected"; return WebSocketStatus; }({}); //#endregion //#region packages/provider/src/OutgoingMessage.ts var OutgoingMessage = class { constructor() { this.encoder = (0, lib0_encoding.createEncoder)(); } get(args) { return args.encoder; } toUint8Array() { return (0, lib0_encoding.toUint8Array)(this.encoder); } }; //#endregion //#region packages/provider/src/OutgoingMessages/CloseMessage.ts var CloseMessage = class extends OutgoingMessage { constructor(..._args) { super(..._args); this.type = MessageType.CLOSE; this.description = "Ask the server to close the connection"; } get(args) { lib0_encoding.writeVarString(this.encoder, args.documentName); lib0_encoding.writeVarUint(this.encoder, this.type); return this.encoder; } }; //#endregion //#region packages/provider/src/HocuspocusProviderWebsocket.ts var HocuspocusProviderWebsocket = class HocuspocusProviderWebsocket extends EventEmitter { static { this.DEDUPLICATABLE_TYPES = new Set([MessageType.Awareness, MessageType.QueryAwareness]); } constructor(configuration) { super(); this.messageQueue = []; this.configuration = { url: "", autoConnect: true, preserveTrailingSlash: false, document: void 0, WebSocketPolyfill: void 0, messageReconnectTimeout: 3e4, delay: 1e3, initialDelay: 0, factor: 2, maxAttempts: 0, minDelay: 1e3, maxDelay: 3e4, jitter: true, timeout: 0, onOpen: () => null, onConnect: () => null, onMessage: () => null, onOutgoingMessage: () => null, onStatus: () => null, onDisconnect: () => null, onClose: () => null, onDestroy: () => null, onAwarenessUpdate: () => null, onAwarenessChange: () => null, handleTimeout: null, providerMap: /* @__PURE__ */ new Map() }; this.webSocket = null; this.webSocketHandlers = {}; this.shouldConnect = true; this.status = WebSocketStatus.Disconnected; this.lastMessageReceived = 0; this.identifier = 0; this.intervals = { connectionChecker: null }; this.connectionAttempt = null; this.receivedOnOpenPayload = void 0; this.closeTries = 0; this.setConfiguration(configuration); this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket; this.on("open", this.configuration.onOpen); this.on("open", this.onOpen.bind(this)); this.on("connect", this.configuration.onConnect); this.on("message", this.configuration.onMessage); this.on("outgoingMessage", this.configuration.onOutgoingMessage); this.on("status", this.configuration.onStatus); this.on("disconnect", this.configuration.onDisconnect); this.on("close", this.configuration.onClose); this.on("destroy", this.configuration.onDestroy); this.on("awarenessUpdate", this.configuration.onAwarenessUpdate); this.on("awarenessChange", this.configuration.onAwarenessChange); this.on("close", this.onClose.bind(this)); this.on("message", this.onMessage.bind(this)); this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.configuration.messageReconnectTimeout / 10); if (this.shouldConnect) this.connect(); } async onOpen(event) { this.status = WebSocketStatus.Connected; this.emit("status", { status: WebSocketStatus.Connected }); this.cancelWebsocketRetry = void 0; this.receivedOnOpenPayload = event; } attach(provider) { const key = provider.effectiveName; const existing = this.configuration.providerMap.get(key); if (existing && existing !== provider) { if (existing.isAuthenticated) throw new Error(`Cannot attach two providers with the same effective name "${key}". Use sessionAwareness: true to multiplex providers with the same document name.`); } this.configuration.providerMap.set(key, provider); if (this.status === WebSocketStatus.Disconnected && this.shouldConnect) this.connect(); if (this.receivedOnOpenPayload && this.status === WebSocketStatus.Connected) provider.onOpen(this.receivedOnOpenPayload); } detach(provider) { const key = provider.effectiveName; if (this.configuration.providerMap.has(key)) { provider.send(CloseMessage, { documentName: key }); this.configuration.providerMap.delete(key); } } setConfiguration(configuration = {}) { this.configuration = { ...this.configuration, ...configuration }; if (!this.configuration.autoConnect) this.shouldConnect = false; } async connect() { if (this.status === WebSocketStatus.Connected) return; if (this.cancelWebsocketRetry) { this.cancelWebsocketRetry(); this.cancelWebsocketRetry = void 0; } this.receivedOnOpenPayload = void 0; this.shouldConnect = true; const abortableRetry = () => { let cancelAttempt = false; return { retryPromise: (0, _lifeomic_attempt.retry)(this.createWebSocketConnection.bind(this), { delay: this.configuration.delay, initialDelay: this.configuration.initialDelay, factor: this.configuration.factor, maxAttempts: this.configuration.maxAttempts, minDelay: this.configuration.minDelay, maxDelay: this.configuration.maxDelay, jitter: this.configuration.jitter, timeout: this.configuration.timeout, handleTimeout: this.configuration.handleTimeout, beforeAttempt: (context) => { if (!this.shouldConnect || cancelAttempt) context.abort(); } }).catch((error) => { if (error && error.code !== "ATTEMPT_ABORTED") throw error; }), cancelFunc: () => { cancelAttempt = true; } }; }; const { retryPromise, cancelFunc } = abortableRetry(); this.cancelWebsocketRetry = cancelFunc; return retryPromise; } attachWebSocketListeners(ws, reject) { const { identifier } = ws; const onMessageHandler = (payload) => this.emit("message", payload); const onCloseHandler = (payload) => this.emit("close", { event: payload }); const onOpenHandler = (payload) => this.emit("open", payload); const onErrorHandler = (err) => { reject(err); }; this.webSocketHandlers[identifier] = { message: onMessageHandler, close: onCloseHandler, open: onOpenHandler, error: onErrorHandler }; const handlers = this.webSocketHandlers[ws.identifier]; Object.keys(handlers).forEach((name) => { ws.addEventListener(name, handlers[name]); }); } cleanupWebSocket() { if (!this.webSocket) return; const { identifier } = this.webSocket; const handlers = this.webSocketHandlers[identifier]; Object.keys(handlers).forEach((name) => { this.webSocket?.removeEventListener(name, handlers[name]); delete this.webSocketHandlers[identifier]; }); this.webSocket.close(); this.webSocket = null; } createWebSocketConnection() { return new Promise((resolve, reject) => { if (this.webSocket) { this.messageQueue = []; this.cleanupWebSocket(); } this.lastMessageReceived = 0; this.identifier += 1; const ws = new this.configuration.WebSocketPolyfill(this.url); ws.binaryType = "arraybuffer"; ws.identifier = this.identifier; this.attachWebSocketListeners(ws, reject); this.webSocket = ws; this.status = WebSocketStatus.Connecting; this.emit("status", { status: WebSocketStatus.Connecting }); this.connectionAttempt = { resolve, reject }; }); } onMessage(event) { this.resolveConnectionAttempt(); this.lastMessageReceived = lib0_time.getUnixTime(); const data = new Uint8Array(event.data); if (data.length === 1 && data[0] === MessageType.Ping) { this.sendPong(); return; } const rawKey = new IncomingMessage(data).peekVarString(); this.configuration.providerMap.get(rawKey)?.onMessage(event); } /** * Send application-level Pong response to server Ping */ sendPong() { const encoder = lib0_encoding.createEncoder(); lib0_encoding.writeVarUint(encoder, MessageType.Pong); this.send(lib0_encoding.toUint8Array(encoder)); } resolveConnectionAttempt() { if (this.connectionAttempt) { this.connectionAttempt.resolve(); this.connectionAttempt = null; this.status = WebSocketStatus.Connected; this.emit("status", { status: WebSocketStatus.Connected }); this.emit("connect"); this.messageQueue.forEach((message) => this.send(message)); this.messageQueue = []; } } stopConnectionAttempt() { this.connectionAttempt = null; } rejectConnectionAttempt() { this.connectionAttempt?.reject(); this.connectionAttempt = null; } checkConnection() { if (this.status !== WebSocketStatus.Connected) return; if (!this.lastMessageReceived) return; if (this.configuration.messageReconnectTimeout >= lib0_time.getUnixTime() - this.lastMessageReceived) return; this.closeTries += 1; if (this.closeTries > 2) { this.onClose({ event: { code: 4408, reason: "forced" } }); this.closeTries = 0; } else { this.webSocket?.close(); this.messageQueue = []; } } get serverUrl() { if (this.configuration.preserveTrailingSlash) return this.configuration.url; let url = this.configuration.url; while (url[url.length - 1] === "/") url = url.slice(0, url.length - 1); return url; } get url() { return this.serverUrl; } disconnect() { this.shouldConnect = false; if (this.webSocket === null) return; try { this.webSocket.close(); this.messageQueue = []; } catch (e) { console.error(e); } } parseQueuedMessage(message) { try { const decoder = (0, lib0_decoding.createDecoder)(message); return { documentName: (0, lib0_decoding.readVarString)(decoder), messageType: (0, lib0_decoding.readVarUint)(decoder) }; } catch { return null; } } addToQueue(message) { if (message instanceof Uint8Array) { const parsed = this.parseQueuedMessage(message); if (parsed && HocuspocusProviderWebsocket.DEDUPLICATABLE_TYPES.has(parsed.messageType)) this.messageQueue = this.messageQueue.filter((queued) => { if (!(queued instanceof Uint8Array)) return true; const queuedParsed = this.parseQueuedMessage(queued); if (!queuedParsed) return true; return !(queuedParsed.documentName === parsed.documentName && queuedParsed.messageType === parsed.messageType); }); } this.messageQueue.push(message); } send(message) { if (this.webSocket?.readyState === _hocuspocus_common.WsReadyStates.Open) this.webSocket.send(message); else this.addToQueue(message); } onClose({ event }) { this.closeTries = 0; this.cleanupWebSocket(); if (this.connectionAttempt) this.rejectConnectionAttempt(); this.status = WebSocketStatus.Disconnected; this.emit("status", { status: WebSocketStatus.Disconnected }); this.emit("disconnect", { event }); if (!this.cancelWebsocketRetry && this.shouldConnect) setTimeout(() => { this.connect(); }, this.configuration.delay); } destroy() { this.emit("destroy"); clearInterval(this.intervals.connectionChecker); this.stopConnectionAttempt(); this.disconnect(); this.removeAllListeners(); this.cleanupWebSocket(); } }; //#endregion //#region packages/provider/src/MessageReceiver.ts var MessageReceiver = class { constructor(message) { this.message = message; } apply(provider, emitSynced) { 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((0, lib0_decoding.readVarString)(message.decoder)); break; case MessageType.SyncStatus: this.applySyncStatusMessage(provider, (0, lib0_decoding.readVarInt)(message.decoder) === 1); break; case MessageType.CLOSE: { const event = { code: 1e3, reason: (0, lib0_decoding.readVarString)(message.decoder) }; provider.onClose(); provider.configuration.onClose({ event }); provider.forwardClose({ event }); break; } default: console.error(`Can’t apply message of unknown type: ${type}`); } if (message.length() > emptyMessageLength + 1) provider.send(OutgoingMessage, { encoder: message.encoder }); } applySyncMessage(provider, emitSynced) { const { message } = this; message.writeVarUint(MessageType.Sync); const syncMessageType = (0, y_protocols_sync.readSyncMessage)(message.decoder, message.encoder, provider.document, provider); if (emitSynced && syncMessageType === y_protocols_sync.messageYjsSyncStep2) provider.synced = true; } applySyncStatusMessage(provider, applied) { if (applied) provider.decrementUnsyncedChanges(); } applyAwarenessMessage(provider) { if (!provider.awareness) return; const { message } = this; y_protocols_awareness.applyAwarenessUpdate(provider.awareness, message.readVarUint8Array(), provider); } applyAuthMessage(provider) { const { message } = this; (0, _hocuspocus_common.readAuthMessage)(message.decoder, provider.sendToken.bind(provider), provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider)); } applyQueryAwarenessMessage(provider) { if (!provider.awareness) return; const { message } = this; message.writeVarUint(MessageType.Awareness); message.writeVarUint8Array(y_protocols_awareness.encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys()))); } }; //#endregion //#region packages/provider/src/MessageSender.ts var MessageSender = class { constructor(Message, args = {}) { this.message = new Message(); this.encoder = this.message.get(args); } create() { return (0, lib0_encoding.toUint8Array)(this.encoder); } send(webSocket) { webSocket?.send(this.create()); } }; //#endregion //#region packages/provider/src/version.ts const version = "4.4.0"; //#endregion //#region packages/provider/src/OutgoingMessages/AuthenticationMessage.ts var AuthenticationMessage = class extends OutgoingMessage { constructor(..._args) { super(..._args); this.type = MessageType.Auth; this.description = "Authentication"; } get(args) { if (typeof args.token === "undefined") throw new Error("The authentication message requires `token` as an argument."); (0, lib0_encoding.writeVarString)(this.encoder, args.documentName); (0, lib0_encoding.writeVarUint)(this.encoder, this.type); (0, _hocuspocus_common.writeAuthentication)(this.encoder, args.token); (0, lib0_encoding.writeVarString)(this.encoder, version); return this.encoder; } }; //#endregion //#region packages/provider/src/OutgoingMessages/AwarenessMessage.ts var AwarenessMessage = class extends OutgoingMessage { constructor(..._args) { super(..._args); this.type = MessageType.Awareness; this.description = "Awareness states update"; } get(args) { if (typeof args.awareness === "undefined") throw new Error("The awareness message requires awareness as an argument"); if (typeof args.clients === "undefined") throw new Error("The awareness message requires clients as an argument"); lib0_encoding.writeVarString(this.encoder, args.documentName); lib0_encoding.writeVarUint(this.encoder, this.type); let awarenessUpdate; if (args.states === void 0) awarenessUpdate = (0, y_protocols_awareness.encodeAwarenessUpdate)(args.awareness, args.clients); else awarenessUpdate = (0, y_protocols_awareness.encodeAwarenessUpdate)(args.awareness, args.clients, args.states); lib0_encoding.writeVarUint8Array(this.encoder, awarenessUpdate); return this.encoder; } }; //#endregion //#region packages/provider/src/OutgoingMessages/StatelessMessage.ts var StatelessMessage = class extends OutgoingMessage { constructor(..._args) { super(..._args); this.type = MessageType.Stateless; this.description = "A stateless message"; } get(args) { (0, lib0_encoding.writeVarString)(this.encoder, args.documentName); (0, lib0_encoding.writeVarUint)(this.encoder, this.type); (0, lib0_encoding.writeVarString)(this.encoder, args.payload ?? ""); return this.encoder; } }; //#endregion //#region packages/provider/src/OutgoingMessages/SyncStepOneMessage.ts var SyncStepOneMessage = class extends OutgoingMessage { constructor(..._args) { super(..._args); this.type = MessageType.Sync; this.description = "First sync step"; } get(args) { if (typeof args.document === "undefined") throw new Error("The sync step one message requires document as an argument"); lib0_encoding.writeVarString(this.encoder, args.documentName); lib0_encoding.writeVarUint(this.encoder, this.type); y_protocols_sync.writeSyncStep1(this.encoder, args.document); return this.encoder; } }; //#endregion //#region packages/provider/src/OutgoingMessages/UpdateMessage.ts var UpdateMessage = class extends OutgoingMessage { constructor(..._args) { super(..._args); this.type = MessageType.Sync; this.description = "A document update"; } get(args) { (0, lib0_encoding.writeVarString)(this.encoder, args.documentName); (0, lib0_encoding.writeVarUint)(this.encoder, this.type); (0, y_protocols_sync.writeUpdate)(this.encoder, args.update); return this.encoder; } }; //#endregion //#region packages/provider/src/HocuspocusProvider.ts var AwarenessError = class extends Error { constructor(..._args) { super(..._args); this.code = 1001; } }; var HocuspocusProvider = class extends EventEmitter { /** * The effective name used as the first VarString in messages. * When `sessionAwareness` is enabled, returns a composite key (documentName\0sessionId). * Otherwise, returns the plain document name. */ get effectiveName() { return this.configuration.sessionAwareness ? (0, _hocuspocus_common.makeRoutingKey)(this.configuration.name, this.sessionId) : this.configuration.name; } constructor(configuration) { super(); this.configuration = { name: "", document: void 0, awareness: void 0, token: null, sessionAwareness: false, forceSyncInterval: false, flushDelay: false, onAuthenticated: () => null, onAuthenticationFailed: () => null, onOpen: () => null, onConnect: () => null, onMessage: () => null, onOutgoingMessage: () => null, onSynced: () => null, onStatus: () => null, onDisconnect: () => null, onClose: () => null, onDestroy: () => null, onAwarenessUpdate: () => null, onAwarenessChange: () => null, onStateless: () => null, onUnsyncedChanges: () => null }; this.isSynced = false; this.unsyncedChanges = 0; this.isAuthenticated = false; this.authorizedScope = void 0; this.manageSocket = false; this._isAttached = false; this.pendingUpdates = []; this.pendingAwarenessClients = /* @__PURE__ */ new Set(); this.flushTimeout = null; this.sessionId = Math.random().toString(36).slice(2); this.intervals = { forceSync: null }; this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this); this.boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this); this.boundPageHide = this.pageHide.bind(this); this.boundOnOpen = this.onOpen.bind(this); this.boundOnClose = this.onClose.bind(this); this.forwardConnect = () => this.emit("connect"); this.forwardStatus = (e) => this.emit("status", e); this.forwardClose = (e) => this.emit("close", e); this.forwardDisconnect = (e) => this.emit("disconnect", e); this.forwardDestroy = () => this.emit("destroy"); this.setConfiguration(configuration); this.configuration.document = configuration.document ? configuration.document : new yjs.Doc(); this.configuration.awareness = configuration.awareness !== void 0 ? configuration.awareness : new y_protocols_awareness.Awareness(this.document); this.on("open", this.configuration.onOpen); this.on("message", this.configuration.onMessage); this.on("outgoingMessage", this.configuration.onOutgoingMessage); this.on("synced", this.configuration.onSynced); this.on("destroy", this.configuration.onDestroy); this.on("awarenessUpdate", this.configuration.onAwarenessUpdate); this.on("awarenessChange", this.configuration.onAwarenessChange); this.on("stateless", this.configuration.onStateless); this.on("unsyncedChanges", this.configuration.onUnsyncedChanges); this.on("authenticated", this.configuration.onAuthenticated); this.on("authenticationFailed", this.configuration.onAuthenticationFailed); this.awareness?.on("update", () => { this.emit("awarenessUpdate", { states: (0, _hocuspocus_common.awarenessStatesToArray)(this.awareness.getStates()) }); }); this.awareness?.on("change", () => { this.emit("awarenessChange", { states: (0, _hocuspocus_common.awarenessStatesToArray)(this.awareness.getStates()) }); }); this.document.on("update", this.boundDocumentUpdateHandler); this.awareness?.on("update", this.boundAwarenessUpdateHandler); this.registerEventListeners(); if (this.configuration.forceSyncInterval && typeof this.configuration.forceSyncInterval === "number") this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval); if (this.manageSocket) this.attach(); } setConfiguration(configuration = {}) { if (!configuration.websocketProvider) { this.manageSocket = true; this.configuration.websocketProvider = new HocuspocusProviderWebsocket(configuration); } this.configuration = { ...this.configuration, ...configuration }; } get document() { return this.configuration.document; } get isAttached() { return this._isAttached; } get awareness() { return this.configuration.awareness; } get hasUnsyncedChanges() { return this.unsyncedChanges > 0; } resetUnsyncedChanges() { this.unsyncedChanges = 1; this.emit("unsyncedChanges", { number: this.unsyncedChanges }); } incrementUnsyncedChanges() { this.unsyncedChanges += 1; this.emit("unsyncedChanges", { number: this.unsyncedChanges }); } decrementUnsyncedChanges() { if (this.unsyncedChanges > 0) this.unsyncedChanges -= 1; if (this.unsyncedChanges === 0) this.synced = true; this.emit("unsyncedChanges", { number: this.unsyncedChanges }); } forceSync() { this.resetUnsyncedChanges(); this.send(SyncStepOneMessage, { document: this.document, documentName: this.effectiveName }); } pageHide() { if (this.awareness) (0, y_protocols_awareness.removeAwarenessStates)(this.awareness, [this.document.clientID], "page hide"); } registerEventListeners() { if (typeof window === "undefined" || !("addEventListener" in window)) return; window.addEventListener("pagehide", this.boundPageHide); } sendStateless(payload) { this.send(StatelessMessage, { documentName: this.effectiveName, payload }); } async sendToken() { let token; try { token = await this.getToken(); } catch (error) { this.permissionDeniedHandler(`Failed to get token during sendToken(): ${error}`); return; } this.send(AuthenticationMessage, { token: token ?? "", documentName: this.effectiveName }); } get batchingEnabled() { return !!this.configuration.flushDelay && typeof this.configuration.flushDelay === "number"; } documentUpdateHandler(update, origin) { if (origin === this) return; if (!this.batchingEnabled) { this.incrementUnsyncedChanges(); this.send(UpdateMessage, { update, documentName: this.effectiveName }); return; } if (this.pendingUpdates.length === 0) this.incrementUnsyncedChanges(); this.pendingUpdates.push(update); this.scheduleFlush(); } awarenessUpdateHandler({ added, updated, removed }, origin) { const changedClients = added.concat(updated).concat(removed); if (!this.batchingEnabled) { this.send(AwarenessMessage, { awareness: this.awareness, clients: changedClients, documentName: this.effectiveName }); return; } for (const client of changedClients) this.pendingAwarenessClients.add(client); this.scheduleFlush(); } scheduleFlush() { if (this.flushTimeout !== null) return; this.flushTimeout = setTimeout(() => { this.flushTimeout = null; this.flushPendingUpdates(); }, this.configuration.flushDelay); } /** * Send everything buffered for the current `flushDelay` window right away. * Buffered document updates are merged into a single message and awareness * collapses to the latest state of each changed client. Safe to call when * nothing is pending. */ flushPendingUpdates() { if (this.flushTimeout !== null) { clearTimeout(this.flushTimeout); this.flushTimeout = null; } if (this.pendingUpdates.length > 0) { const update = this.pendingUpdates.length === 1 ? this.pendingUpdates[0] : yjs.mergeUpdates(this.pendingUpdates); this.pendingUpdates = []; this.send(UpdateMessage, { update, documentName: this.effectiveName }); } if (this.pendingAwarenessClients.size > 0) { const clients = Array.from(this.pendingAwarenessClients); this.pendingAwarenessClients.clear(); this.send(AwarenessMessage, { awareness: this.awareness, clients, documentName: this.effectiveName }); } } /** * Indicates whether a first handshake with the server has been established * * Note: this does not mean all updates from the client have been persisted to the backend. For this, * use `hasUnsyncedChanges`. */ get synced() { return this.isSynced; } set synced(state) { if (this.isSynced === state) return; this.isSynced = state; if (state) this.emit("synced", { state }); } receiveStateless(payload) { this.emit("stateless", { payload }); } async connect() { if (this.manageSocket) return this.configuration.websocketProvider.connect(); console.warn("HocuspocusProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers."); } disconnect() { if (this.manageSocket) return this.configuration.websocketProvider.disconnect(); console.warn("HocuspocusProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers."); } async onOpen(event) { this.isAuthenticated = false; this.emit("open", { event }); await this.sendToken(); this.startSync(); } async getToken() { if (typeof this.configuration.token === "function") return await this.configuration.token(); return this.configuration.token; } startSync() { this.resetUnsyncedChanges(); this.send(SyncStepOneMessage, { document: this.document, documentName: this.effectiveName }); if (this.awareness && this.awareness.getLocalState() !== null) this.send(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID], documentName: this.effectiveName }); } send(message, args) { if (!this._isAttached) return; const messageSender = new MessageSender(message, args); this.emit("outgoingMessage", { message: messageSender.message }); messageSender.send(this.configuration.websocketProvider); } onMessage(event) { const message = new IncomingMessage(event.data); const { documentName } = (0, _hocuspocus_common.parseRoutingKey)(message.readVarString()); message.writeVarString(this.effectiveName); this.emit("message", { event, message: new IncomingMessage(event.data) }); new MessageReceiver(message).apply(this, true); } onClose() { this.isAuthenticated = false; this.synced = false; if (this.flushTimeout !== null) { clearTimeout(this.flushTimeout); this.flushTimeout = null; } this.pendingUpdates = []; this.pendingAwarenessClients.clear(); if (this.awareness) (0, y_protocols_awareness.removeAwarenessStates)(this.awareness, Array.from(this.awareness.getStates().keys()).filter((client) => client !== this.document.clientID), this); } destroy() { this.emit("destroy"); if (this.intervals.forceSync) clearInterval(this.intervals.forceSync); if (this.awareness) { (0, y_protocols_awareness.removeAwarenessStates)(this.awareness, [this.document.clientID], "provider destroy"); this.awareness.off("update", this.boundAwarenessUpdateHandler); this.awareness.destroy(); } this.flushPendingUpdates(); this.document.off("update", this.boundDocumentUpdateHandler); this.removeAllListeners(); this.detach(); if (this.manageSocket) this.configuration.websocketProvider.destroy(); if (typeof window === "undefined" || !("removeEventListener" in window)) return; window.removeEventListener("pagehide", this.boundPageHide); } detach() { this.configuration.websocketProvider.off("connect", this.configuration.onConnect); this.configuration.websocketProvider.off("connect", this.forwardConnect); this.configuration.websocketProvider.off("status", this.forwardStatus); this.configuration.websocketProvider.off("status", this.configuration.onStatus); this.configuration.websocketProvider.off("open", this.boundOnOpen); this.configuration.websocketProvider.off("close", this.boundOnClose); this.configuration.websocketProvider.off("close", this.configuration.onClose); this.configuration.websocketProvider.off("close", this.forwardClose); this.configuration.websocketProvider.off("disconnect", this.configuration.onDisconnect); this.configuration.websocketProvider.off("disconnect", this.forwardDisconnect); this.configuration.websocketProvider.off("destroy", this.configuration.onDestroy); this.configuration.websocketProvider.off("destroy", this.forwardDestroy); this.configuration.websocketProvider.detach(this); this._isAttached = false; } attach() { if (this._isAttached) return; this.configuration.websocketProvider.on("connect", this.configuration.onConnect); this.configuration.websocketProvider.on("connect", this.forwardConnect); this.configuration.websocketProvider.on("status", this.configuration.onStatus); this.configuration.websocketProvider.on("status", this.forwardStatus); this.configuration.websocketProvider.on("open", this.boundOnOpen); this.configuration.websocketProvider.on("close", this.boundOnClose); this.configuration.websocketProvider.on("close", this.configuration.onClose); this.configuration.websocketProvider.on("close", this.forwardClose); this.configuration.websocketProvider.on("disconnect", this.configuration.onDisconnect); this.configuration.websocketProvider.on("disconnect", this.forwardDisconnect); this.configuration.websocketProvider.on("destroy", this.configuration.onDestroy); this.configuration.websocketProvider.on("destroy", this.forwardDestroy); this.configuration.websocketProvider.attach(this); this._isAttached = true; } permissionDeniedHandler(reason) { this.emit("authenticationFailed", { reason }); this.isAuthenticated = false; } authenticatedHandler(scope) { this.isAuthenticated = true; this.authorizedScope = scope; this.emit("authenticated", { scope }); } setAwarenessField(key, value) { if (!this.awareness) throw new AwarenessError(`Cannot set awareness field "${key}" to ${JSON.stringify(value)}. You have disabled Awareness for this provider by explicitly passing awareness: null in the provider configuration.`); this.awareness.setLocalStateField(key, value); } }; //#endregion exports.AwarenessError = AwarenessError; exports.HocuspocusProvider = HocuspocusProvider; exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket; exports.MessageType = MessageType; exports.WebSocketStatus = WebSocketStatus; //# sourceMappingURL=hocuspocus-provider.cjs.map