@hocuspocus/provider
Advanced tools
@@ -541,3 +541,3 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| //#region packages/provider/src/version.ts | ||
| const version = "4.3.0"; | ||
| const version = "4.4.0"; | ||
@@ -658,2 +658,3 @@ //#endregion | ||
| forceSyncInterval: false, | ||
| flushDelay: false, | ||
| onAuthenticated: () => null, | ||
@@ -681,2 +682,5 @@ onAuthenticationFailed: () => null, | ||
| this._isAttached = false; | ||
| this.pendingUpdates = []; | ||
| this.pendingAwarenessClients = /* @__PURE__ */ new Set(); | ||
| this.flushTimeout = null; | ||
| this.sessionId = Math.random().toString(36).slice(2); | ||
@@ -788,19 +792,69 @@ this.intervals = { forceSync: null }; | ||
| } | ||
| get batchingEnabled() { | ||
| return !!this.configuration.flushDelay && typeof this.configuration.flushDelay === "number"; | ||
| } | ||
| documentUpdateHandler(update, origin) { | ||
| if (origin === this) return; | ||
| this.incrementUnsyncedChanges(); | ||
| this.send(UpdateMessage, { | ||
| update, | ||
| documentName: this.effectiveName | ||
| }); | ||
| 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); | ||
| this.send(AwarenessMessage, { | ||
| awareness: this.awareness, | ||
| clients: changedClients, | ||
| documentName: this.effectiveName | ||
| }); | ||
| 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 | ||
@@ -871,2 +925,8 @@ * | ||
| 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); | ||
@@ -882,2 +942,3 @@ } | ||
| } | ||
| this.flushPendingUpdates(); | ||
| this.document.off("update", this.boundDocumentUpdateHandler); | ||
@@ -884,0 +945,0 @@ this.removeAllListeners(); |
@@ -511,3 +511,3 @@ import { WsReadyStates, awarenessStatesToArray, makeRoutingKey, parseRoutingKey, readAuthMessage, writeAuthentication } from "@hocuspocus/common"; | ||
| //#region packages/provider/src/version.ts | ||
| const version = "4.3.0"; | ||
| const version = "4.4.0"; | ||
@@ -628,2 +628,3 @@ //#endregion | ||
| forceSyncInterval: false, | ||
| flushDelay: false, | ||
| onAuthenticated: () => null, | ||
@@ -651,2 +652,5 @@ onAuthenticationFailed: () => null, | ||
| this._isAttached = false; | ||
| this.pendingUpdates = []; | ||
| this.pendingAwarenessClients = /* @__PURE__ */ new Set(); | ||
| this.flushTimeout = null; | ||
| this.sessionId = Math.random().toString(36).slice(2); | ||
@@ -758,19 +762,69 @@ this.intervals = { forceSync: null }; | ||
| } | ||
| get batchingEnabled() { | ||
| return !!this.configuration.flushDelay && typeof this.configuration.flushDelay === "number"; | ||
| } | ||
| documentUpdateHandler(update, origin) { | ||
| if (origin === this) return; | ||
| this.incrementUnsyncedChanges(); | ||
| this.send(UpdateMessage, { | ||
| update, | ||
| documentName: this.effectiveName | ||
| }); | ||
| 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); | ||
| this.send(AwarenessMessage, { | ||
| awareness: this.awareness, | ||
| clients: changedClients, | ||
| documentName: this.effectiveName | ||
| }); | ||
| 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] : Y.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 | ||
@@ -841,2 +895,8 @@ * | ||
| this.synced = false; | ||
| if (this.flushTimeout !== null) { | ||
| clearTimeout(this.flushTimeout); | ||
| this.flushTimeout = null; | ||
| } | ||
| this.pendingUpdates = []; | ||
| this.pendingAwarenessClients.clear(); | ||
| if (this.awareness) removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter((client) => client !== this.document.clientID), this); | ||
@@ -852,2 +912,3 @@ } | ||
| } | ||
| this.flushPendingUpdates(); | ||
| this.document.off("update", this.boundDocumentUpdateHandler); | ||
@@ -854,0 +915,0 @@ this.removeAllListeners(); |
+33
-0
@@ -339,2 +339,17 @@ import { Awareness } from "y-protocols/awareness"; | ||
| forceSyncInterval: false | number; | ||
| /** | ||
| * Batch outgoing document and awareness updates over a short window (in | ||
| * milliseconds) instead of sending one message per change. During heavy | ||
| * editing this drastically reduces the number of websocket messages: Yjs | ||
| * updates collected in the window are merged with `Y.mergeUpdates` into a | ||
| * single message, and awareness collapses to the latest state of each | ||
| * changed client. | ||
| * | ||
| * The window is a fixed batch, not a resetting debounce, so the added | ||
| * latency is capped at `flushDelay` even while the user keeps typing. Keep | ||
| * it small (e.g. 500) to avoid delaying what other clients see. | ||
| * | ||
| * Set to `false` (the default) to send every change immediately. | ||
| */ | ||
| flushDelay: false | number; | ||
| onAuthenticated: (data: onAuthenticatedParameters) => void; | ||
@@ -368,2 +383,11 @@ onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void; | ||
| /** | ||
| * Outgoing document updates buffered for the current `flushDelay` window. | ||
| */ | ||
| private pendingUpdates; | ||
| /** | ||
| * Awareness client ids whose latest state should be sent on the next flush. | ||
| */ | ||
| private pendingAwarenessClients; | ||
| private flushTimeout; | ||
| /** | ||
| * Unique session identifier for this provider instance. | ||
@@ -408,2 +432,3 @@ * Used for multiplexing multiple providers with the same document name on a single WebSocket. | ||
| sendToken(): Promise<void>; | ||
| private get batchingEnabled(); | ||
| documentUpdateHandler(update: Uint8Array, origin: any): void; | ||
@@ -415,3 +440,11 @@ awarenessUpdateHandler({ | ||
| }: any, origin: any): void; | ||
| private scheduleFlush; | ||
| /** | ||
| * 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(): void; | ||
| /** | ||
| * Indicates whether a first handshake with the server has been established | ||
@@ -418,0 +451,0 @@ * |
+3
-3
| { | ||
| "name": "@hocuspocus/provider", | ||
| "version": "4.3.0", | ||
| "version": "4.4.0", | ||
| "description": "hocuspocus provider", | ||
@@ -32,3 +32,3 @@ "homepage": "https://hocuspocus.dev", | ||
| "dependencies": { | ||
| "@hocuspocus/common": "^4.3.0", | ||
| "@hocuspocus/common": "^4.4.0", | ||
| "@lifeomic/attempt": "^3.0.2", | ||
@@ -41,3 +41,3 @@ "lib0": "^0.2.87" | ||
| }, | ||
| "gitHead": "1129ae0a0ec6807e2f3ef2e5884d720bcf1892d7", | ||
| "gitHead": "3634ee9ad71f7c9800a500f2556e42b1d14cf203", | ||
| "repository": { | ||
@@ -44,0 +44,0 @@ "url": "https://github.com/ueberdosis/hocuspocus" |
@@ -1,2 +0,6 @@ | ||
| import { awarenessStatesToArray, makeRoutingKey, parseRoutingKey } from "@hocuspocus/common"; | ||
| import { | ||
| awarenessStatesToArray, | ||
| makeRoutingKey, | ||
| parseRoutingKey, | ||
| } from "@hocuspocus/common"; | ||
| import { Awareness, removeAwarenessStates } from "y-protocols/awareness"; | ||
@@ -85,3 +89,3 @@ import * as Y from "yjs"; | ||
| * | ||
| * Only set this to `true` when connecting to a v4 server that does | ||
| * Only set this to `true` when connecting to a v4 server that does | ||
| * support session awareness. | ||
@@ -98,2 +102,18 @@ * | ||
| /** | ||
| * Batch outgoing document and awareness updates over a short window (in | ||
| * milliseconds) instead of sending one message per change. During heavy | ||
| * editing this drastically reduces the number of websocket messages: Yjs | ||
| * updates collected in the window are merged with `Y.mergeUpdates` into a | ||
| * single message, and awareness collapses to the latest state of each | ||
| * changed client. | ||
| * | ||
| * The window is a fixed batch, not a resetting debounce, so the added | ||
| * latency is capped at `flushDelay` even while the user keeps typing. Keep | ||
| * it small (e.g. 500) to avoid delaying what other clients see. | ||
| * | ||
| * Set to `false` (the default) to send every change immediately. | ||
| */ | ||
| flushDelay: false | number; | ||
| onAuthenticated: (data: onAuthenticatedParameters) => void; | ||
@@ -130,2 +150,3 @@ onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void; | ||
| forceSyncInterval: false, | ||
| flushDelay: false, | ||
| onAuthenticated: () => null, | ||
@@ -162,2 +183,14 @@ onAuthenticationFailed: () => null, | ||
| /** | ||
| * Outgoing document updates buffered for the current `flushDelay` window. | ||
| */ | ||
| private pendingUpdates: Uint8Array[] = []; | ||
| /** | ||
| * Awareness client ids whose latest state should be sent on the next flush. | ||
| */ | ||
| private pendingAwarenessClients = new Set<number>(); | ||
| private flushTimeout: ReturnType<typeof setTimeout> | null = null; | ||
| /** | ||
| * Unique session identifier for this provider instance. | ||
@@ -362,2 +395,9 @@ * Used for multiplexing multiple providers with the same document name on a single WebSocket. | ||
| private get batchingEnabled(): boolean { | ||
| return ( | ||
| !!this.configuration.flushDelay && | ||
| typeof this.configuration.flushDelay === "number" | ||
| ); | ||
| } | ||
| documentUpdateHandler(update: Uint8Array, origin: any) { | ||
@@ -368,4 +408,16 @@ if (origin === this) { | ||
| this.incrementUnsyncedChanges(); | ||
| this.send(UpdateMessage, { update, documentName: this.effectiveName }); | ||
| if (!this.batchingEnabled) { | ||
| this.incrementUnsyncedChanges(); | ||
| this.send(UpdateMessage, { update, documentName: this.effectiveName }); | ||
| return; | ||
| } | ||
| // Count one outstanding change per batch: the server acks once per | ||
| // merged message, so incrementing per buffered update would never balance. | ||
| if (this.pendingUpdates.length === 0) { | ||
| this.incrementUnsyncedChanges(); | ||
| } | ||
| this.pendingUpdates.push(update); | ||
| this.scheduleFlush(); | ||
| } | ||
@@ -376,10 +428,67 @@ | ||
| this.send(AwarenessMessage, { | ||
| awareness: this.awareness, | ||
| clients: changedClients, | ||
| documentName: this.effectiveName, | ||
| }); | ||
| 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(); | ||
| } | ||
| private scheduleFlush() { | ||
| // Fixed-window batching: the first pending change starts the timer and | ||
| // everything until it fires is flushed together, so latency stays capped | ||
| // at `flushDelay` instead of growing while the user keeps typing. | ||
| if (this.flushTimeout !== null) { | ||
| return; | ||
| } | ||
| this.flushTimeout = setTimeout(() => { | ||
| this.flushTimeout = null; | ||
| this.flushPendingUpdates(); | ||
| }, this.configuration.flushDelay as number); | ||
| } | ||
| /** | ||
| * 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] | ||
| : Y.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 | ||
@@ -492,2 +601,11 @@ * | ||
| // Drop anything buffered for batching; the reconnect sync handshake will | ||
| // reconcile these changes from the document via state vectors. | ||
| if (this.flushTimeout !== null) { | ||
| clearTimeout(this.flushTimeout); | ||
| this.flushTimeout = null; | ||
| } | ||
| this.pendingUpdates = []; | ||
| this.pendingAwarenessClients.clear(); | ||
| // update awareness (all users except local left) | ||
@@ -522,2 +640,6 @@ if (this.awareness) { | ||
| // Send any buffered updates before the socket is torn down. The websocket | ||
| // safely queues the message if it is no longer open. | ||
| this.flushPendingUpdates(); | ||
| this.document.off("update", this.boundDocumentUpdateHandler); | ||
@@ -524,0 +646,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
294437
6.85%4013
6.9%Updated