cross-tab-worker-databus 0.20.86 → 0.20.87
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/CHANGELOG.md +17 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-SDOV3UHG.js → chunk-ZNHJ5OMY.js} +126 -17
- package/dist/{chunk-SDOV3UHG.js.map → chunk-ZNHJ5OMY.js.map} +2 -2
- package/dist/cjs/centrifuge.cjs +125 -16
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/hooks.cjs +2 -2
- package/dist/cjs/hooks.cjs.map +2 -2
- package/dist/cjs/index.cjs +232 -36
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/cjs/vue.cjs +1 -1
- package/dist/cjs/vue.cjs.map +2 -2
- package/dist/core/data-bus.d.ts +28 -5
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/hooks.d.ts +2 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +2 -2
- package/dist/hooks.js.map +2 -2
- package/dist/index.js +108 -21
- package/dist/index.js.map +2 -2
- package/dist/vue.d.ts +2 -1
- package/dist/vue.d.ts.map +1 -1
- package/dist/vue.js +1 -1
- package/dist/vue.js.map +2 -2
- package/dist/websocket.d.ts +24 -2
- package/dist/websocket.d.ts.map +1 -1
- package/docs/api.md +14 -9
- package/docs/architecture.md +6 -2
- package/docs/roadmap.md +8 -1
- package/docs/transports.md +19 -2
- package/docs/zh/api.md +14 -9
- package/docs/zh/architecture.md +6 -2
- package/docs/zh/roadmap.md +8 -1
- package/docs/zh/transports.md +14 -2
- package/package.json +3 -3
package/dist/cjs/centrifuge.cjs
CHANGED
|
@@ -2331,7 +2331,7 @@ var DedupManager = class {
|
|
|
2331
2331
|
};
|
|
2332
2332
|
|
|
2333
2333
|
// src/core/version.ts
|
|
2334
|
-
var SDK_VERSION = true ? "0.20.
|
|
2334
|
+
var SDK_VERSION = true ? "0.20.87" : "";
|
|
2335
2335
|
|
|
2336
2336
|
// src/core/data-bus.ts
|
|
2337
2337
|
var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
|
|
@@ -2356,6 +2356,11 @@ var CrossTabDataBus = class {
|
|
|
2356
2356
|
started = false;
|
|
2357
2357
|
stopping = false;
|
|
2358
2358
|
transportReady = false;
|
|
2359
|
+
// Whether the installed transport has reported `connected` at least once
|
|
2360
|
+
// since the current open began. A clean `disconnected` after this point is
|
|
2361
|
+
// a lost working connection, not the pre-connect window of a worker-style
|
|
2362
|
+
// backend whose start() resolves before it reports the connection.
|
|
2363
|
+
transportHasConnected = false;
|
|
2359
2364
|
// Last transport failure, retained so ready() can surface it to callers who
|
|
2360
2365
|
// never awaited start() directly. Cleared on the next successful start.
|
|
2361
2366
|
lastError = null;
|
|
@@ -2394,6 +2399,19 @@ var CrossTabDataBus = class {
|
|
|
2394
2399
|
// a transport reopen succeeds so traces can correlate repeated failures.
|
|
2395
2400
|
recoveryAttempt = 0;
|
|
2396
2401
|
recoveryExhausted = false;
|
|
2402
|
+
// Gate that holds transport operations issued after a runtime `error` until
|
|
2403
|
+
// the scheduled recovery attempt has actually run. Without it, a dead
|
|
2404
|
+
// transport still has `transportReady === true` during the cooldown, so
|
|
2405
|
+
// publishes/subscribes would be written to the failed connection and lost.
|
|
2406
|
+
recoveryGate = null;
|
|
2407
|
+
recoveryGateRelease = null;
|
|
2408
|
+
recoveryTimer = null;
|
|
2409
|
+
recoveryTimerToken = 0;
|
|
2410
|
+
// Once an automatic attempt fails, an explicit transport operation may
|
|
2411
|
+
// recover immediately instead of waiting for the next paced attempt. The
|
|
2412
|
+
// gate still stays closed so the operation cannot reach the failed
|
|
2413
|
+
// transport; it is released by the successful on-demand reopen.
|
|
2414
|
+
recoveryDemandAllowed = false;
|
|
2397
2415
|
/** Monotonic generation incremented on every successful transport open.
|
|
2398
2416
|
* Stays in lockstep with `lastSuccessAt` so callers can detect that the
|
|
2399
2417
|
* transport has been reopened even if the timestamp window is short. */
|
|
@@ -2612,8 +2630,38 @@ var CrossTabDataBus = class {
|
|
|
2612
2630
|
this.queuedStart = queued;
|
|
2613
2631
|
return queued;
|
|
2614
2632
|
}
|
|
2633
|
+
/** Release every operation waiting on the scheduled recovery attempt. */
|
|
2634
|
+
releaseRecoveryGate() {
|
|
2635
|
+
const release = this.recoveryGateRelease;
|
|
2636
|
+
this.recoveryGate = null;
|
|
2637
|
+
this.recoveryGateRelease = null;
|
|
2638
|
+
this.recoveryDemandAllowed = false;
|
|
2639
|
+
release?.();
|
|
2640
|
+
}
|
|
2641
|
+
/** Cancel a pending automatic retry when an explicit lifecycle transition
|
|
2642
|
+
* supersedes it. The released gate re-enters runTransport(), which then
|
|
2643
|
+
* follows the newest start/stop/suspend intent. */
|
|
2644
|
+
cancelScheduledRecovery() {
|
|
2645
|
+
this.recoveryTimerToken += 1;
|
|
2646
|
+
if (this.recoveryTimer !== null) {
|
|
2647
|
+
clearTimeout(this.recoveryTimer);
|
|
2648
|
+
this.recoveryTimer = null;
|
|
2649
|
+
}
|
|
2650
|
+
this.releaseRecoveryGate();
|
|
2651
|
+
}
|
|
2652
|
+
/** Keep the recovery gate closed after a failed attempt while allowing the
|
|
2653
|
+
* next explicit transport operation to start an immediate on-demand reopen.
|
|
2654
|
+
* If no gate/successor retry remains, release any waiters. */
|
|
2655
|
+
allowDemandRecovery() {
|
|
2656
|
+
if (this.recoveryGate !== null && this.started && !this.stopping && !this.suspended && this.status === WORKER_STATUS.ERROR) {
|
|
2657
|
+
this.recoveryDemandAllowed = true;
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
this.releaseRecoveryGate();
|
|
2661
|
+
}
|
|
2615
2662
|
/** Reset failure and recovery diagnostics for a new explicit start session. */
|
|
2616
2663
|
resetFailureState() {
|
|
2664
|
+
this.cancelScheduledRecovery();
|
|
2617
2665
|
this.lastError = null;
|
|
2618
2666
|
this.lastErrorAt = null;
|
|
2619
2667
|
this.lastFailure = null;
|
|
@@ -2636,6 +2684,7 @@ var CrossTabDataBus = class {
|
|
|
2636
2684
|
return before.catch(() => void 0).then(() => {
|
|
2637
2685
|
if (!isCurrentLifecycle() || this.stopping || this.suspended) return;
|
|
2638
2686
|
if (this.pendingStop === chainedPendingStop) this.pendingStop = null;
|
|
2687
|
+
this.transportHasConnected = false;
|
|
2639
2688
|
return Promise.resolve(
|
|
2640
2689
|
this.transport.start(config, {
|
|
2641
2690
|
onMessage: (message) => {
|
|
@@ -2657,6 +2706,7 @@ var CrossTabDataBus = class {
|
|
|
2657
2706
|
this.recoveryGeneration += 1;
|
|
2658
2707
|
this.lastSuccessAt = this.now();
|
|
2659
2708
|
this.transportReady = true;
|
|
2709
|
+
this.releaseRecoveryGate();
|
|
2660
2710
|
}
|
|
2661
2711
|
});
|
|
2662
2712
|
}).catch((error) => {
|
|
@@ -2665,11 +2715,9 @@ var CrossTabDataBus = class {
|
|
|
2665
2715
|
if (!this.pendingStop) {
|
|
2666
2716
|
this.pendingStop = this.createStopPromise();
|
|
2667
2717
|
}
|
|
2718
|
+
this.transportReady = false;
|
|
2668
2719
|
this.updateStatus(WORKER_STATUS.ERROR);
|
|
2669
2720
|
this.reportError(error);
|
|
2670
|
-
this.lastError = error;
|
|
2671
|
-
this.lastErrorAt = this.now();
|
|
2672
|
-
this.transportReady = false;
|
|
2673
2721
|
if (stopClusterOnFailure) {
|
|
2674
2722
|
this.stopping = true;
|
|
2675
2723
|
this.cluster.stop();
|
|
@@ -2683,7 +2731,10 @@ var CrossTabDataBus = class {
|
|
|
2683
2731
|
* Returns a rejected promise when the transport has failed and no start is in
|
|
2684
2732
|
* flight — the caller can retry by calling start() or ready() again. While an
|
|
2685
2733
|
* explicit stop() is settling, this rejects unless a restart is queued behind
|
|
2686
|
-
* it; false readiness during teardown is never reported.
|
|
2734
|
+
* it; false readiness during teardown is never reported. While the tab is
|
|
2735
|
+
* BFCache-suspended (pagehide without a following pageshow), this also
|
|
2736
|
+
* rejects: the suspended start promise is the transport-stop gate, not a
|
|
2737
|
+
* readiness signal.
|
|
2687
2738
|
*/
|
|
2688
2739
|
ready() {
|
|
2689
2740
|
if (this.queuedStart) return this.getQueuedStartReady();
|
|
@@ -2692,6 +2743,11 @@ var CrossTabDataBus = class {
|
|
|
2692
2743
|
"CrossTabDataBus is stopping; ready() cannot report readiness until stop() resolves. Wait for stop() to resolve, then call start() before awaiting ready()."
|
|
2693
2744
|
));
|
|
2694
2745
|
}
|
|
2746
|
+
if (this.suspended) {
|
|
2747
|
+
return Promise.reject(new Error(
|
|
2748
|
+
"CrossTabDataBus is suspended; ready() cannot report readiness until pageshow resumes the transport."
|
|
2749
|
+
));
|
|
2750
|
+
}
|
|
2695
2751
|
if (!this.started && !this.hasInitialConfig && this.lastError !== null) {
|
|
2696
2752
|
return Promise.reject(this.lastError);
|
|
2697
2753
|
}
|
|
@@ -2827,11 +2883,15 @@ var CrossTabDataBus = class {
|
|
|
2827
2883
|
getStatus() {
|
|
2828
2884
|
return this.status;
|
|
2829
2885
|
}
|
|
2830
|
-
/** Return the current automatic transport recovery state. `hasError` means a transport error is currently retained. */
|
|
2831
2886
|
/** Return the current automatic transport recovery state plus diagnostics.
|
|
2832
|
-
* `
|
|
2833
|
-
*
|
|
2834
|
-
*
|
|
2887
|
+
* `hasError`/`errorMessage`/`errorAt` describe the most recent retained
|
|
2888
|
+
* *transport* failure — from a transport open or a runtime `onError`. They
|
|
2889
|
+
* share the lifetime of the unified `lastFailure` ledger: a successful
|
|
2890
|
+
* recovery keeps the last failure visible, and only an explicit `start()`
|
|
2891
|
+
* clears it. `generation` increments on every successful transport open
|
|
2892
|
+
* (initial start and every recovery); `lastSuccessAt` is the timestamp of
|
|
2893
|
+
* the most recent successful open, or `null` until the transport reaches
|
|
2894
|
+
* `ready`. */
|
|
2835
2895
|
getRecoveryStats() {
|
|
2836
2896
|
const errorMessage = this.lastError instanceof Error ? this.lastError.message : this.lastError === null ? null : String(this.lastError);
|
|
2837
2897
|
return {
|
|
@@ -2858,7 +2918,7 @@ var CrossTabDataBus = class {
|
|
|
2858
2918
|
* unified failure ledger and recovery context that explains the verdict. */
|
|
2859
2919
|
getHealthSummary() {
|
|
2860
2920
|
const transport = this.transport;
|
|
2861
|
-
const transportDown =
|
|
2921
|
+
const transportDown = this.status !== WORKER_STATUS.CONNECTED;
|
|
2862
2922
|
const state = !this.started ? HEALTH_STATE.STOPPED : this.suspended ? HEALTH_STATE.SUSPENDED : transportDown ? this.recoveryExhausted ? HEALTH_STATE.DEGRADED : this.status === WORKER_STATUS.CONNECTING && this.recoveryAttempt === 0 ? HEALTH_STATE.STARTING : HEALTH_STATE.RECOVERING : HEALTH_STATE.HEALTHY;
|
|
2863
2923
|
return {
|
|
2864
2924
|
healthy: state === HEALTH_STATE.HEALTHY,
|
|
@@ -2949,6 +3009,7 @@ var CrossTabDataBus = class {
|
|
|
2949
3009
|
async performStop() {
|
|
2950
3010
|
this.lifecycleEpoch += 1;
|
|
2951
3011
|
this.stopping = true;
|
|
3012
|
+
this.cancelScheduledRecovery();
|
|
2952
3013
|
this.replayManager.suspend();
|
|
2953
3014
|
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.STOP });
|
|
2954
3015
|
this.trace.stop();
|
|
@@ -2961,6 +3022,8 @@ var CrossTabDataBus = class {
|
|
|
2961
3022
|
const pendingStop = this.pendingStop;
|
|
2962
3023
|
if (pendingStop) await pendingStop.catch(() => void 0);
|
|
2963
3024
|
else await this.transport.stop();
|
|
3025
|
+
} catch (error) {
|
|
3026
|
+
this.reportError(error);
|
|
2964
3027
|
} finally {
|
|
2965
3028
|
this.transportSubscribedTopics.clear();
|
|
2966
3029
|
this.resetDedup();
|
|
@@ -3026,10 +3089,12 @@ var CrossTabDataBus = class {
|
|
|
3026
3089
|
updateStatus(status) {
|
|
3027
3090
|
const previousStatus = this.status;
|
|
3028
3091
|
this.status = status;
|
|
3092
|
+
if (status === WORKER_STATUS.CONNECTED) this.transportHasConnected = true;
|
|
3029
3093
|
if (previousStatus !== status) this.trace.event({ type: TRACE_EVENT_TYPE.STATUS, status });
|
|
3030
3094
|
this.cluster.setStatus(status);
|
|
3031
3095
|
if (status === WORKER_STATUS.DISCONNECTED || status === WORKER_STATUS.ERROR) this.transportSubscribedTopics.clear();
|
|
3032
3096
|
if (status === WORKER_STATUS.CONNECTED && previousStatus !== WORKER_STATUS.CONNECTED) {
|
|
3097
|
+
if (this.transportReady) this.releaseRecoveryGate();
|
|
3033
3098
|
for (const topic of this.cluster.getSnapshot().assignedTopics) this.subscribeTransport(topic);
|
|
3034
3099
|
}
|
|
3035
3100
|
if (status === WORKER_STATUS.ERROR && this.started && !this.stopping) {
|
|
@@ -3042,23 +3107,49 @@ var CrossTabDataBus = class {
|
|
|
3042
3107
|
this.recoveryExhausted = true;
|
|
3043
3108
|
this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt: this.recoveryMaxAttempts, outcome: RECOVERY_OUTCOME.EXHAUSTED });
|
|
3044
3109
|
}
|
|
3110
|
+
this.releaseRecoveryGate();
|
|
3045
3111
|
return;
|
|
3046
3112
|
}
|
|
3047
3113
|
this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt, outcome: RECOVERY_OUTCOME.SCHEDULED });
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3114
|
+
if (this.recoveryGate === null) {
|
|
3115
|
+
let release;
|
|
3116
|
+
this.recoveryGate = new Promise((resolve) => {
|
|
3117
|
+
release = resolve;
|
|
3118
|
+
});
|
|
3119
|
+
this.recoveryGateRelease = release;
|
|
3120
|
+
}
|
|
3121
|
+
this.recoveryDemandAllowed = false;
|
|
3122
|
+
const timerToken = ++this.recoveryTimerToken;
|
|
3123
|
+
this.recoveryTimer = setTimeout(() => {
|
|
3124
|
+
if (timerToken !== this.recoveryTimerToken) return;
|
|
3125
|
+
this.recoveryTimer = null;
|
|
3126
|
+
if (this.stopping || !this.started || this.suspended || this.status !== WORKER_STATUS.ERROR) {
|
|
3127
|
+
this.releaseRecoveryGate();
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
this.recoveryDemandAllowed = false;
|
|
3131
|
+
const opening = this.reopenTransport(attempt);
|
|
3132
|
+
void opening.then(
|
|
3133
|
+
() => this.releaseRecoveryGate(),
|
|
3134
|
+
() => this.allowDemandRecovery()
|
|
3135
|
+
);
|
|
3052
3136
|
}, this.recoveryCooldownMs);
|
|
3053
3137
|
}
|
|
3138
|
+
} else if (status === WORKER_STATUS.ERROR) {
|
|
3139
|
+
this.releaseRecoveryGate();
|
|
3054
3140
|
}
|
|
3055
3141
|
this.invokeHandlers(this.statusHandlers, (handler) => handler(status));
|
|
3056
3142
|
}
|
|
3057
3143
|
reportError(error, source = FAILURE_SOURCE.TRANSPORT) {
|
|
3144
|
+
const at = this.now();
|
|
3145
|
+
if (source === FAILURE_SOURCE.TRANSPORT) {
|
|
3146
|
+
this.lastError = error;
|
|
3147
|
+
this.lastErrorAt = at;
|
|
3148
|
+
}
|
|
3058
3149
|
this.lastFailure = {
|
|
3059
3150
|
source,
|
|
3060
3151
|
message: error instanceof Error ? error.message : String(error),
|
|
3061
|
-
at
|
|
3152
|
+
at
|
|
3062
3153
|
};
|
|
3063
3154
|
if (source === FAILURE_SOURCE.PERSISTENCE) {
|
|
3064
3155
|
this.persistenceFailureCount += 1;
|
|
@@ -3141,6 +3232,7 @@ var CrossTabDataBus = class {
|
|
|
3141
3232
|
if (this.stopping) return;
|
|
3142
3233
|
this.lifecycleEpoch += 1;
|
|
3143
3234
|
this.suspended = true;
|
|
3235
|
+
this.cancelScheduledRecovery();
|
|
3144
3236
|
this.transportReady = false;
|
|
3145
3237
|
this.transportSubscribedTopics.clear();
|
|
3146
3238
|
this.updateStatus(WORKER_STATUS.DISCONNECTED);
|
|
@@ -3210,7 +3302,24 @@ var CrossTabDataBus = class {
|
|
|
3210
3302
|
*/
|
|
3211
3303
|
runTransport(operation) {
|
|
3212
3304
|
if (this.suspended) return;
|
|
3213
|
-
if (this.
|
|
3305
|
+
if (this.recoveryGate && !this.stopping) {
|
|
3306
|
+
if (this.recoveryDemandAllowed && this.status === WORKER_STATUS.ERROR && !this.suspended) {
|
|
3307
|
+
this.recoveryDemandAllowed = false;
|
|
3308
|
+
const opening = this.reopenTransport();
|
|
3309
|
+
void opening.then(
|
|
3310
|
+
() => this.releaseRecoveryGate(),
|
|
3311
|
+
() => this.allowDemandRecovery()
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
const gate = this.recoveryGate;
|
|
3315
|
+
void gate.then(() => {
|
|
3316
|
+
if (this.stopping || this.suspended) return;
|
|
3317
|
+
this.runTransport(operation);
|
|
3318
|
+
});
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
3321
|
+
const droppedAfterConnect = this.transportHasConnected && this.status === WORKER_STATUS.DISCONNECTED;
|
|
3322
|
+
if (this.transportReady && this.status !== WORKER_STATUS.ERROR && !droppedAfterConnect && !this.stopping) {
|
|
3214
3323
|
try {
|
|
3215
3324
|
void Promise.resolve(operation()).catch((error) => this.reportError(error));
|
|
3216
3325
|
} catch (error) {
|