cross-tab-worker-databus 0.20.87 → 0.20.89
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 +22 -0
- package/dist/centrifuge-session.d.ts +1 -0
- package/dist/centrifuge-session.d.ts.map +1 -1
- package/dist/centrifuge.d.ts.map +1 -1
- package/dist/centrifuge.js +55 -8
- package/dist/centrifuge.js.map +2 -2
- package/dist/centrifuge.shared.worker.js +38 -6
- package/dist/centrifuge.shared.worker.js.map +2 -2
- package/dist/centrifuge.worker.js +38 -6
- package/dist/centrifuge.worker.js.map +2 -2
- package/dist/{chunk-ZNHJ5OMY.js → chunk-77BQELW4.js} +69 -29
- package/dist/{chunk-ZNHJ5OMY.js.map → chunk-77BQELW4.js.map} +3 -3
- package/dist/cjs/centrifuge.cjs +122 -35
- package/dist/cjs/centrifuge.cjs.map +3 -3
- package/dist/cjs/index.cjs +76 -30
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/core/data-bus.d.ts +6 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/replay-manager.d.ts.map +1 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +2 -2
- package/dist/websocket.d.ts.map +1 -1
- package/docs/api.md +4 -4
- package/docs/architecture.md +8 -6
- package/docs/capabilities.md +1 -1
- package/docs/configuration.md +1 -1
- package/docs/roadmap.md +18 -1
- package/docs/zh/api.md +4 -4
- package/docs/zh/architecture.md +8 -6
- package/docs/zh/capabilities.md +1 -1
- package/docs/zh/configuration.md +1 -1
- package/docs/zh/roadmap.md +18 -1
- package/package.json +1 -1
package/dist/cjs/centrifuge.cjs
CHANGED
|
@@ -1661,8 +1661,9 @@ var DataBusTraceReporter = class {
|
|
|
1661
1661
|
/** Start the periodic metrics flush interval. No-op when mode is 'events'
|
|
1662
1662
|
* (no metrics to emit), when disabled, or when already running. */
|
|
1663
1663
|
start() {
|
|
1664
|
-
if (!this.enabled
|
|
1664
|
+
if (!this.enabled) return;
|
|
1665
1665
|
this.stopped = false;
|
|
1666
|
+
if (this.intervalHandle || this.mode === TRACE_MODE.EVENTS) return;
|
|
1666
1667
|
this.intervalStartedAt = this.now();
|
|
1667
1668
|
this.intervalHandle = setInterval(() => this.flush(), this.metricsIntervalMs);
|
|
1668
1669
|
}
|
|
@@ -1675,6 +1676,7 @@ var DataBusTraceReporter = class {
|
|
|
1675
1676
|
}
|
|
1676
1677
|
stop() {
|
|
1677
1678
|
this.stopped = true;
|
|
1679
|
+
this.pendingEvents = [];
|
|
1678
1680
|
this.pause();
|
|
1679
1681
|
}
|
|
1680
1682
|
/** Synchronous sink state for diagnostics: whether delivery is async and how
|
|
@@ -1685,7 +1687,7 @@ var DataBusTraceReporter = class {
|
|
|
1685
1687
|
}
|
|
1686
1688
|
/** Record an instantaneous trace event (lifecycle, status, error, etc.). */
|
|
1687
1689
|
event(event) {
|
|
1688
|
-
if (!this.enabled || this.mode === TRACE_MODE.METRICS) return;
|
|
1690
|
+
if (!this.enabled || this.stopped || this.mode === TRACE_MODE.METRICS) return;
|
|
1689
1691
|
this.emit({ ...event, timestamp: this.now() });
|
|
1690
1692
|
}
|
|
1691
1693
|
/** Synchronous snapshot of the current metrics window without resetting it.
|
|
@@ -1768,7 +1770,7 @@ var DataBusTraceReporter = class {
|
|
|
1768
1770
|
* Extracted so the four record / flush methods share one guard expression
|
|
1769
1771
|
* instead of repeating `!this.enabled || this.mode === 'events'` at each. */
|
|
1770
1772
|
get metricsActive() {
|
|
1771
|
-
return this.enabled && this.mode !== TRACE_MODE.EVENTS;
|
|
1773
|
+
return this.enabled && !this.stopped && this.mode !== TRACE_MODE.EVENTS;
|
|
1772
1774
|
}
|
|
1773
1775
|
/** Emit the accumulated metrics snapshot if the interval is active. */
|
|
1774
1776
|
flush() {
|
|
@@ -2070,6 +2072,8 @@ var ReplayManager = class {
|
|
|
2070
2072
|
* or stopped bus does not keep hammering the store) and stop the sweep. */
|
|
2071
2073
|
suspend() {
|
|
2072
2074
|
this.retryGeneration += 1;
|
|
2075
|
+
this.pendingReplayPersistence = [];
|
|
2076
|
+
this.retentionCutoff = null;
|
|
2073
2077
|
this.stop();
|
|
2074
2078
|
}
|
|
2075
2079
|
/** Drop all in-memory buffers (used on full teardown). */
|
|
@@ -2168,19 +2172,20 @@ var ReplayManager = class {
|
|
|
2168
2172
|
this.retentionCutoff = cutoff;
|
|
2169
2173
|
}
|
|
2170
2174
|
if (this.retentionCleanup) return;
|
|
2175
|
+
const generation = this.retryGeneration;
|
|
2171
2176
|
this.retentionCleanup = (async () => {
|
|
2172
|
-
while (this.retentionCutoff !== null) {
|
|
2177
|
+
while (this.retentionCutoff !== null && generation === this.retryGeneration) {
|
|
2173
2178
|
const nextCutoff = this.retentionCutoff;
|
|
2174
2179
|
this.retentionCutoff = null;
|
|
2175
2180
|
try {
|
|
2176
2181
|
await this.persistence.clearBefore(nextCutoff);
|
|
2177
2182
|
} catch (error) {
|
|
2178
|
-
this.onPersistenceError(error);
|
|
2183
|
+
if (generation === this.retryGeneration) this.onPersistenceError(error);
|
|
2179
2184
|
}
|
|
2180
2185
|
}
|
|
2181
2186
|
})().finally(() => {
|
|
2182
2187
|
this.retentionCleanup = null;
|
|
2183
|
-
if (this.retentionCutoff !== null) {
|
|
2188
|
+
if (this.retentionCutoff !== null && generation === this.retryGeneration) {
|
|
2184
2189
|
this.scheduleRetentionCleanup(this.retentionCutoff);
|
|
2185
2190
|
}
|
|
2186
2191
|
});
|
|
@@ -2197,7 +2202,9 @@ var ReplayManager = class {
|
|
|
2197
2202
|
attempt += 1;
|
|
2198
2203
|
try {
|
|
2199
2204
|
if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();
|
|
2200
|
-
|
|
2205
|
+
const result = await operation();
|
|
2206
|
+
if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();
|
|
2207
|
+
return result;
|
|
2201
2208
|
} catch (error) {
|
|
2202
2209
|
if (error instanceof PersistenceRetryCancelledError || generation !== this.retryGeneration) {
|
|
2203
2210
|
throw new PersistenceRetryCancelledError();
|
|
@@ -2331,7 +2338,7 @@ var DedupManager = class {
|
|
|
2331
2338
|
};
|
|
2332
2339
|
|
|
2333
2340
|
// src/core/version.ts
|
|
2334
|
-
var SDK_VERSION = true ? "0.20.
|
|
2341
|
+
var SDK_VERSION = true ? "0.20.89" : "";
|
|
2335
2342
|
|
|
2336
2343
|
// src/core/data-bus.ts
|
|
2337
2344
|
var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
|
|
@@ -2525,10 +2532,7 @@ var CrossTabDataBus = class {
|
|
|
2525
2532
|
this.suspendTransport();
|
|
2526
2533
|
},
|
|
2527
2534
|
onResume: () => {
|
|
2528
|
-
this.
|
|
2529
|
-
this.trace.start();
|
|
2530
|
-
this.startDedupSweep();
|
|
2531
|
-
this.replayManager.start();
|
|
2535
|
+
this.resumeSuspendedResources();
|
|
2532
2536
|
this.resumeTransport();
|
|
2533
2537
|
},
|
|
2534
2538
|
onDiagnostic: (event) => {
|
|
@@ -2558,15 +2562,19 @@ var CrossTabDataBus = class {
|
|
|
2558
2562
|
if (!transportDown) return Promise.resolve();
|
|
2559
2563
|
this.activeConfig = config;
|
|
2560
2564
|
this.resetFailureState();
|
|
2561
|
-
|
|
2565
|
+
const resumingFromSuspend = this.suspended;
|
|
2566
|
+
if (resumingFromSuspend) this.resumeSuspendedResources();
|
|
2567
|
+
const opening2 = this.reopenTransport();
|
|
2568
|
+
if (resumingFromSuspend) this.cluster.start();
|
|
2569
|
+
return opening2;
|
|
2562
2570
|
}
|
|
2563
2571
|
this.started = true;
|
|
2564
2572
|
this.stopping = false;
|
|
2565
2573
|
this.suspended = false;
|
|
2566
2574
|
this.activeConfig = config;
|
|
2567
2575
|
this.resetFailureState();
|
|
2568
|
-
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });
|
|
2569
2576
|
this.trace.start();
|
|
2577
|
+
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });
|
|
2570
2578
|
this.startDedupSweep();
|
|
2571
2579
|
this.replayManager.start();
|
|
2572
2580
|
this.updateStatus(WORKER_STATUS.CONNECTING);
|
|
@@ -2681,6 +2689,7 @@ var CrossTabDataBus = class {
|
|
|
2681
2689
|
this.transportReady = false;
|
|
2682
2690
|
const chainedPendingStop = this.pendingStop;
|
|
2683
2691
|
const isCurrentLifecycle = () => lifecycleEpoch === this.lifecycleEpoch;
|
|
2692
|
+
let startupInProgress = true;
|
|
2684
2693
|
return before.catch(() => void 0).then(() => {
|
|
2685
2694
|
if (!isCurrentLifecycle() || this.stopping || this.suspended) return;
|
|
2686
2695
|
if (this.pendingStop === chainedPendingStop) this.pendingStop = null;
|
|
@@ -2691,13 +2700,16 @@ var CrossTabDataBus = class {
|
|
|
2691
2700
|
if (isCurrentLifecycle()) this.handleTransportMessage(message);
|
|
2692
2701
|
},
|
|
2693
2702
|
onStatus: (status) => {
|
|
2694
|
-
if (isCurrentLifecycle())
|
|
2703
|
+
if (isCurrentLifecycle()) {
|
|
2704
|
+
this.updateStatus(status, status !== WORKER_STATUS.ERROR || !startupInProgress);
|
|
2705
|
+
}
|
|
2695
2706
|
},
|
|
2696
2707
|
onError: (error) => {
|
|
2697
2708
|
if (isCurrentLifecycle()) this.reportError(error);
|
|
2698
2709
|
}
|
|
2699
2710
|
})
|
|
2700
2711
|
).then(() => {
|
|
2712
|
+
startupInProgress = false;
|
|
2701
2713
|
if (!isCurrentLifecycle()) return;
|
|
2702
2714
|
if (this.status === WORKER_STATUS.ERROR) {
|
|
2703
2715
|
throw new Error("Transport failed during startup.");
|
|
@@ -2711,18 +2723,21 @@ var CrossTabDataBus = class {
|
|
|
2711
2723
|
});
|
|
2712
2724
|
}).catch((error) => {
|
|
2713
2725
|
if (!isCurrentLifecycle()) throw error;
|
|
2726
|
+
startupInProgress = false;
|
|
2714
2727
|
if (stopClusterOnFailure) this.started = false;
|
|
2715
2728
|
if (!this.pendingStop) {
|
|
2716
2729
|
this.pendingStop = this.createStopPromise();
|
|
2717
2730
|
}
|
|
2718
2731
|
this.transportReady = false;
|
|
2719
|
-
this.updateStatus(WORKER_STATUS.ERROR);
|
|
2720
|
-
this.reportError(error);
|
|
2721
2732
|
if (stopClusterOnFailure) {
|
|
2722
2733
|
this.stopping = true;
|
|
2723
2734
|
this.cluster.stop();
|
|
2724
2735
|
this.stopping = false;
|
|
2725
2736
|
}
|
|
2737
|
+
this.recordError(error);
|
|
2738
|
+
this.startPromise = null;
|
|
2739
|
+
this.updateStatus(WORKER_STATUS.ERROR);
|
|
2740
|
+
this.notifyError(error);
|
|
2726
2741
|
throw error;
|
|
2727
2742
|
});
|
|
2728
2743
|
}
|
|
@@ -2919,7 +2934,7 @@ var CrossTabDataBus = class {
|
|
|
2919
2934
|
getHealthSummary() {
|
|
2920
2935
|
const transport = this.transport;
|
|
2921
2936
|
const transportDown = this.status !== WORKER_STATUS.CONNECTED;
|
|
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;
|
|
2937
|
+
const state = !this.started || this.stopping ? 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;
|
|
2923
2938
|
return {
|
|
2924
2939
|
healthy: state === HEALTH_STATE.HEALTHY,
|
|
2925
2940
|
state,
|
|
@@ -2990,7 +3005,7 @@ var CrossTabDataBus = class {
|
|
|
2990
3005
|
this.canceledQueuedStartToken = this.queuedStartToken;
|
|
2991
3006
|
this.queuedStart = null;
|
|
2992
3007
|
}
|
|
2993
|
-
if (this.stopPromise) return this.stopPromise;
|
|
3008
|
+
if (this.stopPromise && this.stopping) return this.stopPromise;
|
|
2994
3009
|
if (!this.started && !this.startPromise && !this.pendingStop && !this.transportReady) {
|
|
2995
3010
|
return Promise.resolve();
|
|
2996
3011
|
}
|
|
@@ -3086,7 +3101,7 @@ var CrossTabDataBus = class {
|
|
|
3086
3101
|
* Propagate a status change to the cluster, trace, and all registered
|
|
3087
3102
|
* status handlers. On reconnect, re-subscribe any topics assigned to us.
|
|
3088
3103
|
*/
|
|
3089
|
-
updateStatus(status) {
|
|
3104
|
+
updateStatus(status, notifyHandlers = true) {
|
|
3090
3105
|
const previousStatus = this.status;
|
|
3091
3106
|
this.status = status;
|
|
3092
3107
|
if (status === WORKER_STATUS.CONNECTED) this.transportHasConnected = true;
|
|
@@ -3138,9 +3153,9 @@ var CrossTabDataBus = class {
|
|
|
3138
3153
|
} else if (status === WORKER_STATUS.ERROR) {
|
|
3139
3154
|
this.releaseRecoveryGate();
|
|
3140
3155
|
}
|
|
3141
|
-
this.invokeHandlers(this.statusHandlers, (handler) => handler(status));
|
|
3156
|
+
if (notifyHandlers) this.invokeHandlers(this.statusHandlers, (handler) => handler(status));
|
|
3142
3157
|
}
|
|
3143
|
-
|
|
3158
|
+
recordError(error, source = FAILURE_SOURCE.TRANSPORT) {
|
|
3144
3159
|
const at = this.now();
|
|
3145
3160
|
if (source === FAILURE_SOURCE.TRANSPORT) {
|
|
3146
3161
|
this.lastError = error;
|
|
@@ -3160,8 +3175,14 @@ var CrossTabDataBus = class {
|
|
|
3160
3175
|
type: TRACE_EVENT_TYPE.ERROR,
|
|
3161
3176
|
source: source === FAILURE_SOURCE.TRANSPORT ? TRACE_ERROR_SOURCE.TRANSPORT : TRACE_ERROR_SOURCE.OPERATION
|
|
3162
3177
|
});
|
|
3178
|
+
}
|
|
3179
|
+
notifyError(error) {
|
|
3163
3180
|
this.invokeHandlers(this.errorHandlers, (handler) => handler(error), INVOKE_LABEL.ERROR_HANDLER);
|
|
3164
3181
|
}
|
|
3182
|
+
reportError(error, source = FAILURE_SOURCE.TRANSPORT) {
|
|
3183
|
+
this.recordError(error, source);
|
|
3184
|
+
this.notifyError(error);
|
|
3185
|
+
}
|
|
3165
3186
|
/** Report a persistence failure to the trace and the unified failure ledger,
|
|
3166
3187
|
* unless it is a {@link PersistenceRetryCancelledError} cancellation from a
|
|
3167
3188
|
* lifecycle transition (teardown should stay quiet). */
|
|
@@ -3224,6 +3245,15 @@ var CrossTabDataBus = class {
|
|
|
3224
3245
|
}
|
|
3225
3246
|
}
|
|
3226
3247
|
}
|
|
3248
|
+
/** Resume the resources paused by a pagehide suspension. Both the native
|
|
3249
|
+
* pageshow path and explicit start() must run this so an explicit resume
|
|
3250
|
+
* cannot leave trace metrics and periodic cleanup timers permanently off. */
|
|
3251
|
+
resumeSuspendedResources() {
|
|
3252
|
+
this.trace.start();
|
|
3253
|
+
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.RESUME });
|
|
3254
|
+
this.startDedupSweep();
|
|
3255
|
+
this.replayManager.start();
|
|
3256
|
+
}
|
|
3227
3257
|
/**
|
|
3228
3258
|
* Suspend the transport when the tab goes hidden. Stops the transport and
|
|
3229
3259
|
* clears subscription state so it will be re-established on resume.
|
|
@@ -3236,8 +3266,11 @@ var CrossTabDataBus = class {
|
|
|
3236
3266
|
this.transportReady = false;
|
|
3237
3267
|
this.transportSubscribedTopics.clear();
|
|
3238
3268
|
this.updateStatus(WORKER_STATUS.DISCONNECTED);
|
|
3239
|
-
if (this.pendingStop)
|
|
3240
|
-
|
|
3269
|
+
if (this.pendingStop && (this.startPromise === null || this.startPromise === this.pendingStop)) {
|
|
3270
|
+
this.startPromise = this.pendingStop;
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
const pending = this.startPromise ?? this.pendingStop ?? Promise.resolve();
|
|
3241
3274
|
const stopping = pending.catch(() => void 0).then(() => this.transport.stop()).catch((error) => this.reportError(error));
|
|
3242
3275
|
this.startPromise = stopping;
|
|
3243
3276
|
this.pendingStop = stopping;
|
|
@@ -3332,10 +3365,17 @@ var CrossTabDataBus = class {
|
|
|
3332
3365
|
ready = this.reopenTransport();
|
|
3333
3366
|
}
|
|
3334
3367
|
if (!ready || this.stopping) return;
|
|
3335
|
-
void ready.then(
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3368
|
+
void ready.then(
|
|
3369
|
+
() => {
|
|
3370
|
+
if (!this.started || this.stopping || this.suspended) return;
|
|
3371
|
+
return operation();
|
|
3372
|
+
},
|
|
3373
|
+
// The opening promise reports its own lifecycle failure through
|
|
3374
|
+
// openTransport(). Swallowing it here prevents a stale startup
|
|
3375
|
+
// rejection from being recorded again after an onStatus/onError
|
|
3376
|
+
// callback has already started and reset the ledger for a retry.
|
|
3377
|
+
() => void 0
|
|
3378
|
+
).catch((error) => this.reportError(error));
|
|
3339
3379
|
}
|
|
3340
3380
|
/**
|
|
3341
3381
|
* Publications started after teardown begins cannot reach any transport.
|
|
@@ -3441,6 +3481,10 @@ var CentrifugeSession = class {
|
|
|
3441
3481
|
transferable = false;
|
|
3442
3482
|
tokenBridge = false;
|
|
3443
3483
|
nextRequestId = 1;
|
|
3484
|
+
// Bumped on every initialize/stop. Async client callbacks capture the value
|
|
3485
|
+
// from the connection that created them so a stopped client cannot emit into
|
|
3486
|
+
// a later session instance (including the main-thread local fallback).
|
|
3487
|
+
lifecycle = 0;
|
|
3444
3488
|
pendingTokenRequests = /* @__PURE__ */ new Map();
|
|
3445
3489
|
/** Dispatch an incoming Worker message to the matching operation.
|
|
3446
3490
|
* Unknown message types are ignored rather than thrown, so a future protocol
|
|
@@ -3476,6 +3520,7 @@ var CentrifugeSession = class {
|
|
|
3476
3520
|
/** Create the Centrifuge client, wire up lifecycle listeners, and connect. */
|
|
3477
3521
|
initialize(url, config, transferable, tokenBridge) {
|
|
3478
3522
|
if (this.client) return;
|
|
3523
|
+
const lifecycle = ++this.lifecycle;
|
|
3479
3524
|
this.transferable = transferable;
|
|
3480
3525
|
this.tokenBridge = tokenBridge;
|
|
3481
3526
|
const clientOptions = tokenBridge ? {
|
|
@@ -3489,12 +3534,23 @@ var CentrifugeSession = class {
|
|
|
3489
3534
|
const client = new import_centrifuge.Centrifuge(url, clientOptions);
|
|
3490
3535
|
this.client = client;
|
|
3491
3536
|
client.on("state", (context) => {
|
|
3537
|
+
if (this.lifecycle !== lifecycle) return;
|
|
3492
3538
|
this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: normalizeStatus(context.newState) });
|
|
3493
3539
|
});
|
|
3494
|
-
client.on("connected", () =>
|
|
3495
|
-
|
|
3496
|
-
|
|
3540
|
+
client.on("connected", () => {
|
|
3541
|
+
if (this.lifecycle !== lifecycle) return;
|
|
3542
|
+
this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.CONNECTED });
|
|
3543
|
+
});
|
|
3544
|
+
client.on("disconnected", () => {
|
|
3545
|
+
if (this.lifecycle !== lifecycle) return;
|
|
3546
|
+
this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.DISCONNECTED });
|
|
3547
|
+
});
|
|
3548
|
+
client.on("error", (context) => {
|
|
3549
|
+
if (this.lifecycle !== lifecycle) return;
|
|
3550
|
+
this.postError(context);
|
|
3551
|
+
});
|
|
3497
3552
|
client.on("publication", (context) => {
|
|
3553
|
+
if (this.lifecycle !== lifecycle) return;
|
|
3498
3554
|
const topic = context.channel || getPayloadTopic(context.data);
|
|
3499
3555
|
if (!topic || this.subscriptions.has(topic)) return;
|
|
3500
3556
|
this.postPublication(topic, context.data);
|
|
@@ -3507,6 +3563,7 @@ var CentrifugeSession = class {
|
|
|
3507
3563
|
* avoiding the removeAllListeners + re-on churn on every duplicate message. */
|
|
3508
3564
|
subscribe(topic) {
|
|
3509
3565
|
if (!this.client) return this.postError(new Error("Centrifuge client is not initialized."));
|
|
3566
|
+
const lifecycle = this.lifecycle;
|
|
3510
3567
|
const existing = this.subscriptions.get(topic);
|
|
3511
3568
|
if (existing) {
|
|
3512
3569
|
existing.subscribe();
|
|
@@ -3519,10 +3576,16 @@ var CentrifugeSession = class {
|
|
|
3519
3576
|
subscription.removeAllListeners("unsubscribed");
|
|
3520
3577
|
this.subscriptions.set(topic, subscription);
|
|
3521
3578
|
subscription.on("publication", (context) => {
|
|
3579
|
+
if (this.lifecycle !== lifecycle) return;
|
|
3522
3580
|
this.postPublication(topic, context.data);
|
|
3523
3581
|
});
|
|
3524
|
-
subscription.on("error", (context) =>
|
|
3525
|
-
|
|
3582
|
+
subscription.on("error", (context) => {
|
|
3583
|
+
if (this.lifecycle !== lifecycle) return;
|
|
3584
|
+
this.postError(context);
|
|
3585
|
+
});
|
|
3586
|
+
subscription.on("unsubscribed", () => {
|
|
3587
|
+
if (this.lifecycle === lifecycle) this.subscriptions.delete(topic);
|
|
3588
|
+
});
|
|
3526
3589
|
subscription.subscribe();
|
|
3527
3590
|
}
|
|
3528
3591
|
/** Unsubscribe from a Centrifuge channel and clean up the local reference.
|
|
@@ -3540,13 +3603,16 @@ var CentrifugeSession = class {
|
|
|
3540
3603
|
/** Publish a message to the Centrifuge channel. */
|
|
3541
3604
|
publish(topic, data, messageId, timestamp) {
|
|
3542
3605
|
if (!this.client) return this.postError(new Error("Centrifuge client is not initialized."));
|
|
3606
|
+
const lifecycle = this.lifecycle;
|
|
3543
3607
|
const hasMetadata = messageId !== void 0 || timestamp !== void 0;
|
|
3544
3608
|
const payload = hasMetadata ? {
|
|
3545
3609
|
data,
|
|
3546
3610
|
...messageId === void 0 ? {} : { messageId },
|
|
3547
3611
|
...timestamp === void 0 ? {} : { timestamp }
|
|
3548
3612
|
} : data;
|
|
3549
|
-
void this.client.publish(topic, payload).catch((error) =>
|
|
3613
|
+
void this.client.publish(topic, payload).catch((error) => {
|
|
3614
|
+
if (this.lifecycle === lifecycle) this.postError(error);
|
|
3615
|
+
});
|
|
3550
3616
|
}
|
|
3551
3617
|
/** Forward a publication to the transport. Binary payloads take the
|
|
3552
3618
|
* zero-copy `MESSAGE_BIN` path when `transferable` is enabled; everything
|
|
@@ -3569,6 +3635,7 @@ var CentrifugeSession = class {
|
|
|
3569
3635
|
}
|
|
3570
3636
|
/** Disconnect the client and clear all subscriptions. */
|
|
3571
3637
|
stop() {
|
|
3638
|
+
this.lifecycle += 1;
|
|
3572
3639
|
for (const [, pending] of this.pendingTokenRequests) {
|
|
3573
3640
|
pending.reject(new Error("Centrifuge session stopped before the credential was resolved."));
|
|
3574
3641
|
}
|
|
@@ -3582,9 +3649,14 @@ var CentrifugeSession = class {
|
|
|
3582
3649
|
* Used as Centrifuge's `getToken` / `getChannelToken` when token bridging is
|
|
3583
3650
|
* enabled; resolved or rejected by a matching TOKEN_RESPONSE / TOKEN_ERROR. */
|
|
3584
3651
|
requestToken(kind, channel) {
|
|
3652
|
+
const lifecycle = this.lifecycle;
|
|
3585
3653
|
const requestId = this.nextRequestId;
|
|
3586
3654
|
this.nextRequestId += 1;
|
|
3587
3655
|
return new Promise((resolve, reject) => {
|
|
3656
|
+
if (this.lifecycle !== lifecycle) {
|
|
3657
|
+
reject(new Error("Centrifuge session stopped before the credential was requested."));
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3588
3660
|
this.pendingTokenRequests.set(requestId, { resolve, reject });
|
|
3589
3661
|
this.post({
|
|
3590
3662
|
type: CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST,
|
|
@@ -3805,10 +3877,24 @@ var CentrifugeWorkerTransport = class {
|
|
|
3805
3877
|
* credentialProvider, then post the fresh token (or a serialized failure)
|
|
3806
3878
|
* back to the session that issued the request. */
|
|
3807
3879
|
resolveTokenRequest(requestId, kind, channel) {
|
|
3880
|
+
const generation = this.generation;
|
|
3881
|
+
const worker = this.worker;
|
|
3882
|
+
const port = this.port;
|
|
3883
|
+
const localSession = this.localSession;
|
|
3884
|
+
const isCurrentBackend = () => this.generation === generation && this.worker === worker && this.port === port && this.localSession === localSession;
|
|
3808
3885
|
const provider = this.credentialProvider;
|
|
3809
|
-
|
|
3886
|
+
let value;
|
|
3887
|
+
try {
|
|
3888
|
+
value = kind === "channelToken" && provider?.getChannelToken ? provider.getChannelToken(channel ?? "") : provider?.getToken?.();
|
|
3889
|
+
} catch (error) {
|
|
3890
|
+
if (isCurrentBackend()) {
|
|
3891
|
+
this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR, requestId, error: serializeError(error) });
|
|
3892
|
+
}
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3810
3895
|
Promise.resolve(value).then(
|
|
3811
3896
|
(token) => {
|
|
3897
|
+
if (!isCurrentBackend()) return;
|
|
3812
3898
|
if (typeof token !== "string" || token.length === 0) {
|
|
3813
3899
|
this.post({
|
|
3814
3900
|
type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR,
|
|
@@ -3820,6 +3906,7 @@ var CentrifugeWorkerTransport = class {
|
|
|
3820
3906
|
this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE, requestId, token });
|
|
3821
3907
|
},
|
|
3822
3908
|
(error) => {
|
|
3909
|
+
if (!isCurrentBackend()) return;
|
|
3823
3910
|
this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR, requestId, error: serializeError(error) });
|
|
3824
3911
|
}
|
|
3825
3912
|
);
|