@xnetjs/runtime 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -4
- package/dist/index.js +64 -36
- package/package.json +10 -10
package/dist/index.d.ts
CHANGED
|
@@ -605,10 +605,7 @@ declare class WebSocketSyncProvider {
|
|
|
605
605
|
readonly url: string;
|
|
606
606
|
readonly awareness: Awareness;
|
|
607
607
|
private ws;
|
|
608
|
-
private
|
|
609
|
-
private maxReconnectAttempts;
|
|
610
|
-
private reconnectAttempts;
|
|
611
|
-
private reconnectTimer;
|
|
608
|
+
private reconnectScheduler;
|
|
612
609
|
private destroyed;
|
|
613
610
|
private connected;
|
|
614
611
|
private synced;
|
package/dist/index.js
CHANGED
|
@@ -147,6 +147,43 @@ function createBlobSyncProvider(config) {
|
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// src/sync/connection-manager.ts
|
|
151
|
+
import { capped, exponential, fixed, jittered, limitAttempts } from "@xnetjs/core";
|
|
152
|
+
|
|
153
|
+
// src/sync/reconnect-scheduler.ts
|
|
154
|
+
function createReconnectScheduler(options) {
|
|
155
|
+
let attempts = 0;
|
|
156
|
+
let timer = null;
|
|
157
|
+
return {
|
|
158
|
+
get attempts() {
|
|
159
|
+
return attempts;
|
|
160
|
+
},
|
|
161
|
+
get pending() {
|
|
162
|
+
return timer !== null;
|
|
163
|
+
},
|
|
164
|
+
schedule() {
|
|
165
|
+
if (timer) return false;
|
|
166
|
+
const delay = options.policy().delayFor(attempts + 1);
|
|
167
|
+
if (delay === null) return false;
|
|
168
|
+
attempts++;
|
|
169
|
+
timer = setTimeout(() => {
|
|
170
|
+
timer = null;
|
|
171
|
+
options.onRetry();
|
|
172
|
+
}, delay);
|
|
173
|
+
return true;
|
|
174
|
+
},
|
|
175
|
+
reset() {
|
|
176
|
+
attempts = 0;
|
|
177
|
+
},
|
|
178
|
+
cancel() {
|
|
179
|
+
if (timer) {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
timer = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
150
187
|
// src/sync/connection-manager.ts
|
|
151
188
|
function log(...args) {
|
|
152
189
|
if (typeof localStorage !== "undefined" && localStorage.getItem("xnet:sync:debug") === "true") {
|
|
@@ -156,8 +193,6 @@ function log(...args) {
|
|
|
156
193
|
function createConnectionManager(config) {
|
|
157
194
|
let ws = null;
|
|
158
195
|
let status = "disconnected";
|
|
159
|
-
let reconnectAttempts = 0;
|
|
160
|
-
let reconnectTimer = null;
|
|
161
196
|
let connectTimer = null;
|
|
162
197
|
let destroyed = false;
|
|
163
198
|
let connectInProgress = false;
|
|
@@ -168,6 +203,15 @@ function createConnectionManager(config) {
|
|
|
168
203
|
const maxReconnects = config.maxReconnects ?? Infinity;
|
|
169
204
|
const connectTimeout = config.connectTimeout ?? 1e4;
|
|
170
205
|
const initialConnectTimeout = config.initialConnectTimeout ?? (connectTimeout > 0 ? Math.min(connectTimeout, 6e3) : 0);
|
|
206
|
+
const reconnectPolicy = limitAttempts(
|
|
207
|
+
capped(exponential(reconnectDelay), maxReconnectDelay),
|
|
208
|
+
maxReconnects
|
|
209
|
+
);
|
|
210
|
+
const rateLimitPolicy = limitAttempts(jittered(fixed(rateLimitBackoffMs)), maxReconnects);
|
|
211
|
+
const reconnectScheduler = createReconnectScheduler({
|
|
212
|
+
policy: () => policyViolation ? rateLimitPolicy : reconnectPolicy,
|
|
213
|
+
onRetry: () => void doConnect()
|
|
214
|
+
});
|
|
171
215
|
function clearConnectTimer() {
|
|
172
216
|
if (connectTimer) {
|
|
173
217
|
clearTimeout(connectTimer);
|
|
@@ -197,7 +241,7 @@ function createConnectionManager(config) {
|
|
|
197
241
|
scheduleReconnect();
|
|
198
242
|
}
|
|
199
243
|
function armConnectTimeout() {
|
|
200
|
-
const timeout =
|
|
244
|
+
const timeout = reconnectScheduler.attempts === 0 ? initialConnectTimeout : connectTimeout;
|
|
201
245
|
if (timeout <= 0 || !Number.isFinite(timeout)) return;
|
|
202
246
|
connectTimer = setTimeout(onConnectTimeout, timeout);
|
|
203
247
|
}
|
|
@@ -317,7 +361,7 @@ function createConnectionManager(config) {
|
|
|
317
361
|
connectInProgress = false;
|
|
318
362
|
log("WebSocket connected");
|
|
319
363
|
setStatus("connected");
|
|
320
|
-
|
|
364
|
+
reconnectScheduler.reset();
|
|
321
365
|
policyViolation = false;
|
|
322
366
|
if (rooms.size > 0) {
|
|
323
367
|
const roomList = Array.from(rooms.keys());
|
|
@@ -350,20 +394,10 @@ function createConnectionManager(config) {
|
|
|
350
394
|
}
|
|
351
395
|
}
|
|
352
396
|
function scheduleReconnect() {
|
|
353
|
-
if (destroyed
|
|
354
|
-
if (
|
|
355
|
-
reconnectAttempts++;
|
|
356
|
-
let backoff;
|
|
357
|
-
if (policyViolation) {
|
|
397
|
+
if (destroyed) return;
|
|
398
|
+
if (reconnectScheduler.schedule()) {
|
|
358
399
|
policyViolation = false;
|
|
359
|
-
backoff = rateLimitBackoffMs + Math.floor(Math.random() * rateLimitBackoffMs * 0.5);
|
|
360
|
-
} else {
|
|
361
|
-
backoff = Math.min(reconnectDelay * 2 ** (reconnectAttempts - 1), maxReconnectDelay);
|
|
362
400
|
}
|
|
363
|
-
reconnectTimer = setTimeout(() => {
|
|
364
|
-
reconnectTimer = null;
|
|
365
|
-
void doConnect();
|
|
366
|
-
}, backoff);
|
|
367
401
|
}
|
|
368
402
|
return {
|
|
369
403
|
get status() {
|
|
@@ -379,10 +413,7 @@ function createConnectionManager(config) {
|
|
|
379
413
|
disconnect() {
|
|
380
414
|
destroyed = true;
|
|
381
415
|
clearConnectTimer();
|
|
382
|
-
|
|
383
|
-
clearTimeout(reconnectTimer);
|
|
384
|
-
reconnectTimer = null;
|
|
385
|
-
}
|
|
416
|
+
reconnectScheduler.cancel();
|
|
386
417
|
if (ws) {
|
|
387
418
|
if (rooms.size > 0) {
|
|
388
419
|
send({ type: "unsubscribe", topics: Array.from(rooms.keys()) });
|
|
@@ -2865,6 +2896,7 @@ async function runAdapterConformance(makeClient) {
|
|
|
2865
2896
|
}
|
|
2866
2897
|
|
|
2867
2898
|
// src/sync/WebSocketSyncProvider.ts
|
|
2899
|
+
import { fixed as fixed2, limitAttempts as limitAttempts2 } from "@xnetjs/core";
|
|
2868
2900
|
import { resolveSyncReplicationPolicy as resolveSyncReplicationPolicy2, signYjsUpdate as signYjsUpdate2, verifyYjsEnvelopeV1 as verifyYjsEnvelopeV12 } from "@xnetjs/sync";
|
|
2869
2901
|
import {
|
|
2870
2902
|
Awareness as Awareness2,
|
|
@@ -2930,10 +2962,7 @@ var WebSocketSyncProvider = class {
|
|
|
2930
2962
|
url;
|
|
2931
2963
|
awareness;
|
|
2932
2964
|
ws = null;
|
|
2933
|
-
|
|
2934
|
-
maxReconnectAttempts;
|
|
2935
|
-
reconnectAttempts = 0;
|
|
2936
|
-
reconnectTimer = null;
|
|
2965
|
+
reconnectScheduler;
|
|
2937
2966
|
destroyed = false;
|
|
2938
2967
|
connected = false;
|
|
2939
2968
|
synced = false;
|
|
@@ -2947,8 +2976,14 @@ var WebSocketSyncProvider = class {
|
|
|
2947
2976
|
this.room = options.room;
|
|
2948
2977
|
this.url = options.url;
|
|
2949
2978
|
this.options = options;
|
|
2950
|
-
|
|
2951
|
-
|
|
2979
|
+
const reconnectPolicy = limitAttempts2(
|
|
2980
|
+
fixed2(options.reconnectDelay ?? 2e3),
|
|
2981
|
+
options.maxReconnectAttempts ?? Infinity
|
|
2982
|
+
);
|
|
2983
|
+
this.reconnectScheduler = createReconnectScheduler({
|
|
2984
|
+
policy: () => reconnectPolicy,
|
|
2985
|
+
onRetry: () => this._connect()
|
|
2986
|
+
});
|
|
2952
2987
|
this.replicationPolicy = resolveSyncReplicationPolicy2(options.replication);
|
|
2953
2988
|
this.peerId = Math.random().toString(36).slice(2, 10);
|
|
2954
2989
|
this.awareness = new Awareness2(doc);
|
|
@@ -2988,10 +3023,7 @@ var WebSocketSyncProvider = class {
|
|
|
2988
3023
|
this.doc.off("update", this._onDocUpdate);
|
|
2989
3024
|
this.awareness.off("update", this._onAwarenessUpdate);
|
|
2990
3025
|
removeAwarenessStates2(this.awareness, [this.doc.clientID], this);
|
|
2991
|
-
|
|
2992
|
-
clearTimeout(this.reconnectTimer);
|
|
2993
|
-
this.reconnectTimer = null;
|
|
2994
|
-
}
|
|
3026
|
+
this.reconnectScheduler.cancel();
|
|
2995
3027
|
if (this.ws) {
|
|
2996
3028
|
this._send({ type: "unsubscribe", topics: [this.room] });
|
|
2997
3029
|
this.ws.close();
|
|
@@ -3008,7 +3040,7 @@ var WebSocketSyncProvider = class {
|
|
|
3008
3040
|
this.ws.onopen = () => {
|
|
3009
3041
|
log3(this, "WebSocket connected to", this.url);
|
|
3010
3042
|
this.connected = true;
|
|
3011
|
-
this.
|
|
3043
|
+
this.reconnectScheduler.reset();
|
|
3012
3044
|
this.emit("status", { connected: true });
|
|
3013
3045
|
log3(this, "Subscribing to room:", this.room);
|
|
3014
3046
|
this._send({ type: "subscribe", topics: [this.room] });
|
|
@@ -3055,11 +3087,7 @@ var WebSocketSyncProvider = class {
|
|
|
3055
3087
|
}
|
|
3056
3088
|
_scheduleReconnect() {
|
|
3057
3089
|
if (this.destroyed) return;
|
|
3058
|
-
|
|
3059
|
-
this.reconnectAttempts++;
|
|
3060
|
-
this.reconnectTimer = setTimeout(() => {
|
|
3061
|
-
this._connect();
|
|
3062
|
-
}, this.reconnectDelay);
|
|
3090
|
+
this.reconnectScheduler.schedule();
|
|
3063
3091
|
}
|
|
3064
3092
|
_send(msg) {
|
|
3065
3093
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnetjs/runtime",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Framework-agnostic xNet runtime: createXNetClient() and sync orchestration, usable from any framework, a CLI, a worker, or a Node service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"y-protocols": "^1.0.6",
|
|
30
30
|
"yjs": "^13.6.24",
|
|
31
|
-
"@xnetjs/core": "0.
|
|
32
|
-
"@xnetjs/crypto": "0.
|
|
33
|
-
"@xnetjs/data": "0.
|
|
34
|
-
"@xnetjs/data-bridge": "0.
|
|
35
|
-
"@xnetjs/history": "0.
|
|
36
|
-
"@xnetjs/identity": "0.
|
|
37
|
-
"@xnetjs/plugins": "0.
|
|
38
|
-
"@xnetjs/storage": "0.
|
|
39
|
-
"@xnetjs/sync": "0.
|
|
31
|
+
"@xnetjs/core": "0.12.0",
|
|
32
|
+
"@xnetjs/crypto": "0.12.0",
|
|
33
|
+
"@xnetjs/data": "0.12.0",
|
|
34
|
+
"@xnetjs/data-bridge": "0.12.0",
|
|
35
|
+
"@xnetjs/history": "0.12.0",
|
|
36
|
+
"@xnetjs/identity": "0.12.0",
|
|
37
|
+
"@xnetjs/plugins": "0.12.0",
|
|
38
|
+
"@xnetjs/storage": "0.12.0",
|
|
39
|
+
"@xnetjs/sync": "0.12.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"jsdom": "^26.0.0",
|