🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@hocuspocus/provider

Package Overview
Dependencies
Maintainers
6
Versions
161
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hocuspocus/provider - npm Package Compare versions

Comparing version
4.3.0
to
4.4.0
+72
-11
dist/hocuspocus-provider.cjs

@@ -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();

@@ -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