@q9labsai/chalk-client 5.0.0 → 5.1.0
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 +34 -0
- package/README.md +39 -0
- package/dist/access/base64-url.d.ts +0 -1
- package/dist/access/grant.d.ts +0 -1
- package/dist/access/manager.d.ts +0 -1
- package/dist/chat-files/http-transport.d.ts +0 -1
- package/dist/chat-files/index.d.ts +0 -1
- package/dist/chat-files/types.d.ts +0 -1
- package/dist/{chunk-2HJIJVE2.js → chunk-3ZN7IXOZ.js} +341 -3
- package/dist/{chunk-O2NOZKJP.js → chunk-DWKTGBHR.js} +20 -8
- package/dist/{chunk-AVXJ4HQU.js → chunk-K7PNW3KI.js} +156 -35
- package/dist/{chunk-CMVO7USN.js → chunk-KHHULLBZ.js} +1 -1
- package/dist/{chunk-IPAN4LUZ.js → chunk-RJRSLXVT.js} +863 -409
- package/dist/client.d.ts +76 -1
- package/dist/collaboration/index.d.ts +0 -1
- package/dist/collaboration/types.d.ts +0 -1
- package/dist/collaboration/wire.d.ts +0 -1
- package/dist/connection/dependencies.d.ts +5 -3
- package/dist/connection/diagnostics.d.ts +1 -2
- package/dist/connection/index.d.ts +1 -1
- package/dist/connection/lifecycle.d.ts +2 -3
- package/dist/connection/media-devices.d.ts +0 -1
- package/dist/connection/production.d.ts +0 -1
- package/dist/connection/types.d.ts +2 -2
- package/dist/effect.d.ts +0 -1
- package/dist/effect.js +88 -4
- package/dist/foreground-deadline.d.ts +0 -1
- package/dist/generated/http-api.d.ts +76 -1
- package/dist/generated/schemas.d.ts +366 -7
- package/dist/generated/sync.d.ts +0 -1
- package/dist/generated/whiteboard-v1.d.ts +50 -7
- package/dist/index.d.ts +0 -1
- package/dist/index.js +7 -5
- package/dist/media/camera-uplink.d.ts +0 -1
- package/dist/media/capture-error.d.ts +0 -1
- package/dist/media/client.d.ts +3 -2
- package/dist/media/cloudflare-sfu.d.ts +0 -1
- package/dist/media/index.d.ts +0 -1
- package/dist/media/index.js +1 -1
- package/dist/media/observers.d.ts +0 -1
- package/dist/media/plane.d.ts +0 -1
- package/dist/media/remote-track-response.d.ts +0 -1
- package/dist/media/target.d.ts +0 -1
- package/dist/media/tracks.d.ts +0 -1
- package/dist/media/transport.d.ts +0 -1
- package/dist/media/types.d.ts +0 -1
- package/dist/public-invites/client.d.ts +0 -1
- package/dist/public-invites/index.d.ts +0 -1
- package/dist/public-invites/index.js +2 -2
- package/dist/public-invites/types.d.ts +0 -1
- package/dist/server/access-refresh.d.ts +0 -1
- package/dist/server/{chunk-C4XITY5U.js → chunk-XQOQTCUL.js} +22 -0
- package/dist/server/client.d.ts +0 -1
- package/dist/server/errors.d.cts +8 -1
- package/dist/server/errors.d.ts +8 -2
- package/dist/server/index.cjs +31 -7
- package/dist/server/index.d.cts +1 -1
- package/dist/server/index.d.ts +1 -2
- package/dist/server/index.js +10 -7
- package/dist/server/server-only.d.ts +0 -1
- package/dist/server/server-only.js +1 -1
- package/dist/server/transport.d.ts +0 -1
- package/dist/server/types.d.ts +0 -1
- package/dist/space-client/chat-controller-helpers.d.ts +0 -1
- package/dist/space-client/chat-controller.d.ts +0 -1
- package/dist/space-client/controller-effects.d.ts +0 -1
- package/dist/space-client/core.d.ts +0 -1
- package/dist/space-client/effect.d.ts +0 -1
- package/dist/space-client/errors.d.ts +4 -2
- package/dist/space-client/index.d.ts +1 -2
- package/dist/space-client/media-controller.d.ts +3 -2
- package/dist/space-client/media-device-selection.d.ts +0 -1
- package/dist/space-client/offline-actions-storage.d.ts +0 -1
- package/dist/space-client/offline-actions.d.ts +0 -1
- package/dist/space-client/participants-controller.d.ts +0 -1
- package/dist/space-client/promise-facade.d.ts +0 -1
- package/dist/space-client/reactions-controller.d.ts +0 -1
- package/dist/space-client/recording-controller.d.ts +0 -1
- package/dist/space-client/reload-rejoin.d.ts +0 -1
- package/dist/space-client/space-client.d.ts +0 -1
- package/dist/space-client/store.d.ts +0 -1
- package/dist/space-client/types.d.ts +7 -2
- package/dist/space-client/whiteboard-controller.d.ts +0 -1
- package/dist/sync/browser.d.ts +0 -1
- package/dist/sync/canonical.d.ts +0 -1
- package/dist/sync/dial-deadline.d.ts +3 -0
- package/dist/sync/errors.d.ts +0 -1
- package/dist/sync/index.d.ts +0 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/react-native.d.ts +0 -1
- package/dist/sync/types.d.ts +0 -1
- package/dist/sync/v1-client.d.ts +2 -2
- package/dist/sync/v1-codec.d.ts +0 -1
- package/dist/sync/v1-collaboration-state.d.ts +1 -1
- package/dist/sync/v1-command-scheduler.d.ts +0 -1
- package/dist/sync/v1-create.d.ts +0 -1
- package/dist/sync/v1-deferred.d.ts +0 -1
- package/dist/sync/v1-error.d.ts +0 -1
- package/dist/sync/v1-frame-signature.d.ts +0 -1
- package/dist/sync/v1-health.d.ts +15 -0
- package/dist/sync/v1-live-target-coordinator.d.ts +0 -1
- package/dist/sync/v1-persistence.d.ts +0 -1
- package/dist/sync/v1-platform-persistence.d.ts +0 -1
- package/dist/sync/v1-reducer.d.ts +3 -1
- package/dist/sync/v1-types.d.ts +10 -1
- package/dist/webhooks/errors.d.ts +0 -1
- package/dist/webhooks/generated/event-v1.d.ts +0 -1
- package/dist/webhooks/generated/fixtures-v1.d.ts +0 -1
- package/dist/webhooks/index.d.ts +0 -1
- package/dist/webhooks/processor.d.ts +0 -1
- package/dist/webhooks/server-only.d.ts +0 -1
- package/dist/webhooks/test.d.ts +0 -1
- package/dist/webhooks/types.d.ts +0 -1
- package/dist/webhooks/validate.d.ts +0 -1
- package/dist/webhooks/verify.d.ts +0 -1
- package/dist/whiteboard/file-download-transport.d.ts +0 -1
- package/dist/whiteboard/index.d.ts +5 -1
- package/dist/whiteboard/types.d.ts +28 -2
- package/dist/whiteboard/v1-client.d.ts +3 -2
- package/dist/whiteboard/v1-codec.d.ts +0 -1
- package/dist/whiteboard/v1-create.d.ts +0 -1
- package/dist/whiteboard/v1-multipart.d.ts +0 -1
- package/dist/whiteboard/v1-persistence.d.ts +0 -1
- package/package.json +4 -2
- package/dist/access/base64-url.d.ts.map +0 -1
- package/dist/access/grant.d.ts.map +0 -1
- package/dist/access/grant.test.helpers.d.ts +0 -10
- package/dist/access/grant.test.helpers.d.ts.map +0 -1
- package/dist/access/manager.d.ts.map +0 -1
- package/dist/chat-files/http-transport.d.ts.map +0 -1
- package/dist/chat-files/index.d.ts.map +0 -1
- package/dist/chat-files/types.d.ts.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/collaboration/index.d.ts.map +0 -1
- package/dist/collaboration/types.d.ts.map +0 -1
- package/dist/collaboration/wire.d.ts.map +0 -1
- package/dist/connection/dependencies.d.ts.map +0 -1
- package/dist/connection/diagnostics.d.ts.map +0 -1
- package/dist/connection/index.d.ts.map +0 -1
- package/dist/connection/lifecycle.d.ts.map +0 -1
- package/dist/connection/media-devices.d.ts.map +0 -1
- package/dist/connection/production.d.ts.map +0 -1
- package/dist/connection/types.d.ts.map +0 -1
- package/dist/effect.d.ts.map +0 -1
- package/dist/foreground-deadline.d.ts.map +0 -1
- package/dist/generated/http-api.d.ts.map +0 -1
- package/dist/generated/schemas.d.ts.map +0 -1
- package/dist/generated/sync.d.ts.map +0 -1
- package/dist/generated/whiteboard-v1.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/media/camera-uplink.d.ts.map +0 -1
- package/dist/media/capture-error.d.ts.map +0 -1
- package/dist/media/client.d.ts.map +0 -1
- package/dist/media/cloudflare-sfu.d.ts.map +0 -1
- package/dist/media/index.d.ts.map +0 -1
- package/dist/media/observers.d.ts.map +0 -1
- package/dist/media/plane.d.ts.map +0 -1
- package/dist/media/remote-track-response.d.ts.map +0 -1
- package/dist/media/target.d.ts.map +0 -1
- package/dist/media/tracks.d.ts.map +0 -1
- package/dist/media/transport.d.ts.map +0 -1
- package/dist/media/types.d.ts.map +0 -1
- package/dist/public-invites/client.d.ts.map +0 -1
- package/dist/public-invites/index.d.ts.map +0 -1
- package/dist/public-invites/types.d.ts.map +0 -1
- package/dist/server/access-refresh.d.ts.map +0 -1
- package/dist/server/client.d.ts.map +0 -1
- package/dist/server/errors.d.ts.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/server-only.d.ts.map +0 -1
- package/dist/server/transport.d.ts.map +0 -1
- package/dist/server/types.d.ts.map +0 -1
- package/dist/space-client/chat-controller-helpers.d.ts.map +0 -1
- package/dist/space-client/chat-controller.d.ts.map +0 -1
- package/dist/space-client/controller-effects.d.ts.map +0 -1
- package/dist/space-client/core.d.ts.map +0 -1
- package/dist/space-client/core.test.helpers.d.ts +0 -15
- package/dist/space-client/core.test.helpers.d.ts.map +0 -1
- package/dist/space-client/effect.d.ts.map +0 -1
- package/dist/space-client/errors.d.ts.map +0 -1
- package/dist/space-client/index.d.ts.map +0 -1
- package/dist/space-client/media-controller.d.ts.map +0 -1
- package/dist/space-client/media-device-selection.d.ts.map +0 -1
- package/dist/space-client/offline-actions-storage.d.ts.map +0 -1
- package/dist/space-client/offline-actions.d.ts.map +0 -1
- package/dist/space-client/participants-controller.d.ts.map +0 -1
- package/dist/space-client/promise-facade.d.ts.map +0 -1
- package/dist/space-client/reactions-controller.d.ts.map +0 -1
- package/dist/space-client/recording-controller.d.ts.map +0 -1
- package/dist/space-client/reload-rejoin.d.ts.map +0 -1
- package/dist/space-client/space-client.d.ts.map +0 -1
- package/dist/space-client/store.d.ts.map +0 -1
- package/dist/space-client/terminal.test.helpers.d.ts +0 -136
- package/dist/space-client/terminal.test.helpers.d.ts.map +0 -1
- package/dist/space-client/types.d.ts.map +0 -1
- package/dist/space-client/whiteboard-controller.d.ts.map +0 -1
- package/dist/sync/browser.d.ts.map +0 -1
- package/dist/sync/canonical.d.ts.map +0 -1
- package/dist/sync/errors.d.ts.map +0 -1
- package/dist/sync/index.d.ts.map +0 -1
- package/dist/sync/react-native.d.ts.map +0 -1
- package/dist/sync/types.d.ts.map +0 -1
- package/dist/sync/v1-client.d.ts.map +0 -1
- package/dist/sync/v1-codec.d.ts.map +0 -1
- package/dist/sync/v1-collaboration-state.d.ts.map +0 -1
- package/dist/sync/v1-command-scheduler.d.ts.map +0 -1
- package/dist/sync/v1-create.d.ts.map +0 -1
- package/dist/sync/v1-deferred.d.ts.map +0 -1
- package/dist/sync/v1-error.d.ts.map +0 -1
- package/dist/sync/v1-frame-signature.d.ts.map +0 -1
- package/dist/sync/v1-live-target-coordinator.d.ts.map +0 -1
- package/dist/sync/v1-persistence.d.ts.map +0 -1
- package/dist/sync/v1-platform-persistence.d.ts.map +0 -1
- package/dist/sync/v1-reducer.d.ts.map +0 -1
- package/dist/sync/v1-types.d.ts.map +0 -1
- package/dist/webhooks/errors.d.ts.map +0 -1
- package/dist/webhooks/generated/event-v1.d.ts.map +0 -1
- package/dist/webhooks/generated/fixtures-v1.d.ts.map +0 -1
- package/dist/webhooks/index.d.ts.map +0 -1
- package/dist/webhooks/processor.d.ts.map +0 -1
- package/dist/webhooks/server-only.d.ts.map +0 -1
- package/dist/webhooks/test.d.ts.map +0 -1
- package/dist/webhooks/types.d.ts.map +0 -1
- package/dist/webhooks/validate.d.ts.map +0 -1
- package/dist/webhooks/verify.d.ts.map +0 -1
- package/dist/whiteboard/file-download-transport.d.ts.map +0 -1
- package/dist/whiteboard/index.d.ts.map +0 -1
- package/dist/whiteboard/types.d.ts.map +0 -1
- package/dist/whiteboard/v1-client.d.ts.map +0 -1
- package/dist/whiteboard/v1-codec.d.ts.map +0 -1
- package/dist/whiteboard/v1-create.d.ts.map +0 -1
- package/dist/whiteboard/v1-multipart.d.ts.map +0 -1
- package/dist/whiteboard/v1-persistence.d.ts.map +0 -1
|
@@ -217,6 +217,47 @@ function unsubscribe(subscription) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
// src/sync/v1-health.ts
|
|
221
|
+
var SYNC_HEALTH_BUDGET = {
|
|
222
|
+
probeIntervalMs: 500,
|
|
223
|
+
degradedAfterMs: 750,
|
|
224
|
+
recoveringAfterMs: 4e3,
|
|
225
|
+
stablePongsForMs: 2e3
|
|
226
|
+
};
|
|
227
|
+
var V1SyncHealth = class {
|
|
228
|
+
health = "healthy";
|
|
229
|
+
#lastPongAt;
|
|
230
|
+
#stableSince = null;
|
|
231
|
+
constructor(now) {
|
|
232
|
+
this.#lastPongAt = now;
|
|
233
|
+
}
|
|
234
|
+
observePong(now) {
|
|
235
|
+
this.check(now);
|
|
236
|
+
const gap = now - this.#lastPongAt;
|
|
237
|
+
this.#lastPongAt = now;
|
|
238
|
+
if (this.health === "healthy") return;
|
|
239
|
+
if (gap > SYNC_HEALTH_BUDGET.degradedAfterMs || this.#stableSince === null) this.#stableSince = now;
|
|
240
|
+
if (now - this.#stableSince >= SYNC_HEALTH_BUDGET.stablePongsForMs) {
|
|
241
|
+
this.health = "healthy";
|
|
242
|
+
this.#stableSince = null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
check(now) {
|
|
246
|
+
const silence = now - this.#lastPongAt;
|
|
247
|
+
if (silence >= SYNC_HEALTH_BUDGET.degradedAfterMs) this.#stableSince = null;
|
|
248
|
+
if (silence >= SYNC_HEALTH_BUDGET.recoveringAfterMs) this.health = "recovering";
|
|
249
|
+
else if (silence >= SYNC_HEALTH_BUDGET.degradedAfterMs && this.health === "healthy") this.health = "degraded";
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/sync/dial-deadline.ts
|
|
254
|
+
var SYNC_DIAL_TIMEOUT_MS = 1e4;
|
|
255
|
+
function scheduleSyncDialDeadline(clock, onTimeout, timeoutMs = SYNC_DIAL_TIMEOUT_MS) {
|
|
256
|
+
const allowance = Number.isFinite(timeoutMs) ? Math.min(6e4, Math.max(SYNC_DIAL_TIMEOUT_MS, timeoutMs)) : SYNC_DIAL_TIMEOUT_MS;
|
|
257
|
+
const timer = clock.setTimeout(onTimeout, allowance);
|
|
258
|
+
return () => clock.clearTimeout(timer);
|
|
259
|
+
}
|
|
260
|
+
|
|
220
261
|
// src/generated/sync.ts
|
|
221
262
|
import { Schema } from "effect";
|
|
222
263
|
var SyncProtocolMetadata = {
|
|
@@ -1719,6 +1760,12 @@ var V1ReplicaError = class extends Error {
|
|
|
1719
1760
|
this.name = "V1ReplicaError";
|
|
1720
1761
|
}
|
|
1721
1762
|
};
|
|
1763
|
+
var V1StateDigestMismatchError = class extends V1ReplicaError {
|
|
1764
|
+
constructor() {
|
|
1765
|
+
super("control state digest mismatch");
|
|
1766
|
+
this.name = "V1StateDigestMismatchError";
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1722
1769
|
async function restoreV1Snapshot(snapshot) {
|
|
1723
1770
|
const state = snapshotToState(snapshot);
|
|
1724
1771
|
assertV1ControlSemantics(state);
|
|
@@ -1832,7 +1879,7 @@ function durableProjection(state) {
|
|
|
1832
1879
|
};
|
|
1833
1880
|
}
|
|
1834
1881
|
async function assertDigest(state) {
|
|
1835
|
-
if (await computeV1StateDigest(state) !== state.stateDigest) throw new
|
|
1882
|
+
if (await computeV1StateDigest(state) !== state.stateDigest) throw new V1StateDigestMismatchError();
|
|
1836
1883
|
}
|
|
1837
1884
|
function reduceEvent(state, event) {
|
|
1838
1885
|
switch (event.name) {
|
|
@@ -1971,9 +2018,11 @@ function sameStrings2(left, right) {
|
|
|
1971
2018
|
|
|
1972
2019
|
// src/sync/v1-collaboration-state.ts
|
|
1973
2020
|
var MAX_COLLABORATION_REQUESTS_IN_FLIGHT = 64;
|
|
2021
|
+
var REACTION_ACK_TIMEOUT_MS = 5e3;
|
|
1974
2022
|
var V1CollaborationState = class {
|
|
1975
2023
|
#options;
|
|
1976
2024
|
#listeners = /* @__PURE__ */ new Set();
|
|
2025
|
+
#reactionTimers = /* @__PURE__ */ new Map();
|
|
1977
2026
|
#reactions = /* @__PURE__ */ new Map();
|
|
1978
2027
|
#chatSends = /* @__PURE__ */ new Map();
|
|
1979
2028
|
#chatPages = /* @__PURE__ */ new Map();
|
|
@@ -2044,7 +2093,18 @@ var V1CollaborationState = class {
|
|
|
2044
2093
|
this.#assertCapacity();
|
|
2045
2094
|
const operationId = this.#nextRequestId();
|
|
2046
2095
|
const frame = { type: "reaction_send", operation_id: operationId, reaction };
|
|
2047
|
-
|
|
2096
|
+
const pending = this.#sendTrackedRequest(operationId, frame, this.#reactions);
|
|
2097
|
+
this.#reactionTimers.set(
|
|
2098
|
+
operationId,
|
|
2099
|
+
this.#options.clock().setTimeout(() => {
|
|
2100
|
+
this.#reactionTimers.delete(operationId);
|
|
2101
|
+
const deferred = this.#reactions.get(operationId);
|
|
2102
|
+
if (!deferred) return;
|
|
2103
|
+
this.#reactions.delete(operationId);
|
|
2104
|
+
rejectV1Deferred(deferred, new V1SyncError("Reaction acknowledgement timed out", "reaction_ack_timeout"));
|
|
2105
|
+
}, REACTION_ACK_TIMEOUT_MS)
|
|
2106
|
+
);
|
|
2107
|
+
return pending;
|
|
2048
2108
|
}
|
|
2049
2109
|
sendChatMessage(input) {
|
|
2050
2110
|
this.#assertReady("sendChat");
|
|
@@ -2108,6 +2168,8 @@ var V1CollaborationState = class {
|
|
|
2108
2168
|
this.#requireNegotiated();
|
|
2109
2169
|
const deferred = this.#reactions.get(frame.operation_id);
|
|
2110
2170
|
if (!deferred) return;
|
|
2171
|
+
this.#options.clock().clearTimeout(this.#reactionTimers.get(frame.operation_id));
|
|
2172
|
+
this.#reactionTimers.delete(frame.operation_id);
|
|
2111
2173
|
this.#reactions.delete(frame.operation_id);
|
|
2112
2174
|
if (frame.outcome === "accepted") resolveV1Deferred(deferred, reactionFromFrame(frame.reaction));
|
|
2113
2175
|
else rejectV1Deferred(deferred, new V1SyncError(frame.error_code, frame.error_code));
|
|
@@ -2178,6 +2240,8 @@ var V1CollaborationState = class {
|
|
|
2178
2240
|
for (const deferred of this.#chatSends.values()) rejectV1Deferred(deferred, new V1SyncError(code, code));
|
|
2179
2241
|
for (const deferred of this.#chatPages.values()) rejectV1Deferred(deferred, new V1SyncError(code, code));
|
|
2180
2242
|
for (const deferred of this.#chatReads.values()) rejectV1Deferred(deferred, new V1SyncError(code, code));
|
|
2243
|
+
for (const timer of this.#reactionTimers.values()) this.#options.clock().clearTimeout(timer);
|
|
2244
|
+
this.#reactionTimers.clear();
|
|
2181
2245
|
this.#reactions.clear();
|
|
2182
2246
|
this.#chatSends.clear();
|
|
2183
2247
|
this.#chatPages.clear();
|
|
@@ -2863,15 +2927,16 @@ var CLIENT_RESTART_CLOSE_CODE = 4e3;
|
|
|
2863
2927
|
var DEFAULT_RECONNECT_DELAY_MS = 250;
|
|
2864
2928
|
var MAX_RECONNECT_DELAY_MS = 5e3;
|
|
2865
2929
|
var FAST_RECONNECT_ATTEMPTS = 8;
|
|
2866
|
-
var
|
|
2867
|
-
var NOTICE_SILENCE_MS = 750;
|
|
2930
|
+
var DIGEST_RECOVERY_COOLDOWN_MS = 6e4;
|
|
2868
2931
|
var V1SyncClient = class {
|
|
2869
2932
|
#options;
|
|
2870
2933
|
#commandScheduler;
|
|
2871
2934
|
#listeners = /* @__PURE__ */ new Set();
|
|
2935
|
+
#mediaPauseListeners = /* @__PURE__ */ new Set();
|
|
2872
2936
|
#requestListeners = /* @__PURE__ */ new Set();
|
|
2873
2937
|
#collaborationState;
|
|
2874
2938
|
#controlHeads = /* @__PURE__ */ new Map();
|
|
2939
|
+
#snapshotRevision = null;
|
|
2875
2940
|
#controlEvents = /* @__PURE__ */ new Map();
|
|
2876
2941
|
#liveTargets;
|
|
2877
2942
|
#requests = /* @__PURE__ */ new Map();
|
|
@@ -2885,17 +2950,20 @@ var V1SyncClient = class {
|
|
|
2885
2950
|
#participantId = null;
|
|
2886
2951
|
#participantGeneration = null;
|
|
2887
2952
|
#recovery = null;
|
|
2953
|
+
#snapshotRecoveryRequested = false;
|
|
2954
|
+
#lastDigestRecoveryAt = null;
|
|
2888
2955
|
#started = false;
|
|
2889
2956
|
#startupGeneration = 0;
|
|
2890
2957
|
#connectionGeneration = 0;
|
|
2891
2958
|
#reconnectTimer;
|
|
2959
|
+
#cancelDial;
|
|
2960
|
+
#dialTimeoutCount = 0;
|
|
2892
2961
|
#reconnectAttempt = 0;
|
|
2893
2962
|
#heartbeatTimer;
|
|
2894
2963
|
#missedHeartbeats = 0;
|
|
2895
2964
|
#noticeTimer;
|
|
2896
2965
|
#noticeProbeTimer;
|
|
2897
|
-
#
|
|
2898
|
-
#noticeUnresponsive = false;
|
|
2966
|
+
#health = new V1SyncHealth(0);
|
|
2899
2967
|
#unsubscribeLifecycle;
|
|
2900
2968
|
#inbound = Promise.resolve();
|
|
2901
2969
|
#transportAvailable = true;
|
|
@@ -2935,6 +3003,7 @@ var V1SyncClient = class {
|
|
|
2935
3003
|
stateChanged: () => this.#emit()
|
|
2936
3004
|
});
|
|
2937
3005
|
this.#collaborationState = new V1CollaborationState({
|
|
3006
|
+
clock: () => this.#clock(),
|
|
2938
3007
|
request: options.collaboration,
|
|
2939
3008
|
requestIds: options.requestIds,
|
|
2940
3009
|
maxPendingRequests: options.maxPendingCollaborationRequests,
|
|
@@ -2971,6 +3040,7 @@ var V1SyncClient = class {
|
|
|
2971
3040
|
this.#unsubscribeLifecycle?.();
|
|
2972
3041
|
this.#unsubscribeLifecycle = void 0;
|
|
2973
3042
|
this.#clearReconnect();
|
|
3043
|
+
this.#clearDial();
|
|
2974
3044
|
this.#clearHeartbeat();
|
|
2975
3045
|
this.#clearNoticeProbe();
|
|
2976
3046
|
this.#commandScheduler.stop("client_stopped");
|
|
@@ -2989,7 +3059,7 @@ var V1SyncClient = class {
|
|
|
2989
3059
|
const pendingCommands = this.#commandScheduler.pendingCommands;
|
|
2990
3060
|
const optimisticControl = this.#control && this.#participantId ? optimisticV1Control(this.#control, this.#participantId, pendingCommands) : this.#control;
|
|
2991
3061
|
return {
|
|
2992
|
-
connection: this.#phase.phase === "live" ? {
|
|
3062
|
+
connection: { ...this.#phase, ...this.#phase.phase === "live" ? { noticeUnresponsive: this.#health.health === "recovering", health: this.#health.health } : {}, ...this.#dialTimeoutCount ? { dialTimeoutCount: this.#dialTimeoutCount } : {} },
|
|
2993
3063
|
participantId: this.#participantId,
|
|
2994
3064
|
participantGeneration: this.#participantGeneration,
|
|
2995
3065
|
control: this.#control,
|
|
@@ -3006,6 +3076,16 @@ var V1SyncClient = class {
|
|
|
3006
3076
|
listener(this.getSnapshot());
|
|
3007
3077
|
return () => this.#listeners.delete(listener);
|
|
3008
3078
|
}
|
|
3079
|
+
onMediaPause(listener) {
|
|
3080
|
+
this.#mediaPauseListeners.add(listener);
|
|
3081
|
+
return () => this.#mediaPauseListeners.delete(listener);
|
|
3082
|
+
}
|
|
3083
|
+
#notifyMediaPause(event) {
|
|
3084
|
+
const source = event.name === "participant_microphone_stopped" ? "microphone" : event.name === "participant_camera_stopped" ? "camera" : event.name === "participant_screen_share_stopped" ? "screen" : null;
|
|
3085
|
+
if (source && "participant_id" in event.payload && event.payload.participant_id === this.#participantId) {
|
|
3086
|
+
for (const listener of this.#mediaPauseListeners) listener({ source, revision: event.revision });
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3009
3089
|
onDirectedRequest(listener) {
|
|
3010
3090
|
this.#requestListeners.add(listener);
|
|
3011
3091
|
return () => this.#requestListeners.delete(listener);
|
|
@@ -3117,13 +3197,24 @@ var V1SyncClient = class {
|
|
|
3117
3197
|
return promise;
|
|
3118
3198
|
}
|
|
3119
3199
|
#connect() {
|
|
3120
|
-
if (!this.#started || !this.#transportAvailable || this.#socket) return;
|
|
3200
|
+
if (!this.#started || !this.#transportAvailable || this.#socket || this.#phase.phase === "terminal") return;
|
|
3121
3201
|
this.#phase = { phase: "connecting" };
|
|
3122
3202
|
this.#emit();
|
|
3123
3203
|
const socket = this.#options.webSocket.connect(this.#options.url);
|
|
3124
3204
|
const connectionGeneration = ++this.#connectionGeneration;
|
|
3125
3205
|
this.#socket = socket;
|
|
3206
|
+
this.#cancelDial = scheduleSyncDialDeadline(
|
|
3207
|
+
this.#clock(),
|
|
3208
|
+
() => {
|
|
3209
|
+
this.#cancelDial = void 0;
|
|
3210
|
+
this.#dialTimeoutCount += 1;
|
|
3211
|
+
this.#recover("WebSocket dial timeout");
|
|
3212
|
+
},
|
|
3213
|
+
this.#options.dialTimeoutMs
|
|
3214
|
+
);
|
|
3126
3215
|
socket.onopen = () => {
|
|
3216
|
+
if (socket !== this.#socket) return;
|
|
3217
|
+
this.#clearDial();
|
|
3127
3218
|
void this.#authenticate(socket);
|
|
3128
3219
|
};
|
|
3129
3220
|
socket.onmessage = (event) => {
|
|
@@ -3169,17 +3260,11 @@ var V1SyncClient = class {
|
|
|
3169
3260
|
try {
|
|
3170
3261
|
if (typeof data !== "string" || encoder3.encode(data).byteLength > SyncProtocolLimits.snapshotEncodedBytes) throw new V1ReplicaError("invalid inbound frame size");
|
|
3171
3262
|
const frame = decodeV1ServerFrame(data);
|
|
3172
|
-
if (this.#phase.phase === "live") {
|
|
3173
|
-
this.#noticeLastInboundAt = this.#now();
|
|
3174
|
-
this.#scheduleNoticeCheck();
|
|
3175
|
-
if (this.#noticeUnresponsive) {
|
|
3176
|
-
this.#noticeUnresponsive = false;
|
|
3177
|
-
this.#emit();
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
3263
|
await this.#handleFrame(frame);
|
|
3181
|
-
} catch {
|
|
3182
|
-
this.#
|
|
3264
|
+
} catch (error) {
|
|
3265
|
+
if (this.#snapshotRecoveryRequested) this.#failDigestRecovery();
|
|
3266
|
+
else if (error instanceof V1StateDigestMismatchError) this.#recoverDigestMismatch();
|
|
3267
|
+
else this.#recover("invalid_frame");
|
|
3183
3268
|
}
|
|
3184
3269
|
}
|
|
3185
3270
|
async #handleFrame(frame) {
|
|
@@ -3207,6 +3292,7 @@ var V1SyncClient = class {
|
|
|
3207
3292
|
this.#control = await applyV1Event(this.#requireControl(), frame);
|
|
3208
3293
|
this.#rememberControlHead(this.#control.revision, this.#control.stateDigest);
|
|
3209
3294
|
this.#rememberControlEvent(frame);
|
|
3295
|
+
this.#notifyMediaPause(frame);
|
|
3210
3296
|
}
|
|
3211
3297
|
await this.#commandScheduler.settleTerminalLifecycle(frame, this.#participantId);
|
|
3212
3298
|
await this.#commandScheduler.settleProven();
|
|
@@ -3269,10 +3355,14 @@ var V1SyncClient = class {
|
|
|
3269
3355
|
return;
|
|
3270
3356
|
case "error":
|
|
3271
3357
|
throw new V1ReplicaError(frame.code);
|
|
3272
|
-
case "pong":
|
|
3358
|
+
case "pong": {
|
|
3273
3359
|
this.#requireLive();
|
|
3274
3360
|
this.#missedHeartbeats = 0;
|
|
3361
|
+
const previous = this.#health.health;
|
|
3362
|
+
this.#health.observePong(this.#now());
|
|
3363
|
+
if (previous !== this.#health.health) this.#emit();
|
|
3275
3364
|
return;
|
|
3365
|
+
}
|
|
3276
3366
|
}
|
|
3277
3367
|
}
|
|
3278
3368
|
async #welcome(frame) {
|
|
@@ -3288,15 +3378,20 @@ var V1SyncClient = class {
|
|
|
3288
3378
|
this.#emit();
|
|
3289
3379
|
return;
|
|
3290
3380
|
}
|
|
3381
|
+
if (this.#snapshotRecoveryRequested && frame.mode !== "snapshot") throw new V1ReplicaError("digest recovery requires a full snapshot");
|
|
3291
3382
|
if (frame.mode === "snapshot") {
|
|
3292
|
-
|
|
3383
|
+
const control = await restoreV1Snapshot(frame.snapshot);
|
|
3384
|
+
if (!sameHead(control, frame.head)) throw new V1ReplicaError("snapshot head mismatch");
|
|
3385
|
+
this.#control = control;
|
|
3386
|
+
this.#snapshotRevision = control.revision;
|
|
3387
|
+
this.#snapshotRecoveryRequested = false;
|
|
3293
3388
|
this.#controlHeads.clear();
|
|
3294
3389
|
this.#controlEvents.clear();
|
|
3295
3390
|
this.#rememberControlHead(this.#control.revision, this.#control.stateDigest);
|
|
3391
|
+
await this.#commandScheduler.settleProven();
|
|
3296
3392
|
}
|
|
3297
3393
|
if (frame.mode === "replay" && (!this.#control || this.#control.revision >= frame.head.revision)) throw new V1ReplicaError("invalid replay welcome");
|
|
3298
3394
|
if (frame.mode === "up_to_date" && !sameHead(this.#control, frame.head)) throw new V1ReplicaError("up-to-date head mismatch");
|
|
3299
|
-
if (frame.mode === "snapshot" && !sameHead(this.#control, frame.head)) throw new V1ReplicaError("snapshot head mismatch");
|
|
3300
3395
|
this.#recovery = { id: frame.recovery_id, head: frame.head, replayEvents: 0, replayBytes: 0, controlComplete: false };
|
|
3301
3396
|
if (frame.mode === "snapshot") this.#ackRecovery();
|
|
3302
3397
|
this.#emit();
|
|
@@ -3311,6 +3406,7 @@ var V1SyncClient = class {
|
|
|
3311
3406
|
this.#control = await applyV1Event(this.#requireControl(), event);
|
|
3312
3407
|
this.#rememberControlHead(this.#control.revision, this.#control.stateDigest);
|
|
3313
3408
|
this.#rememberControlEvent(event);
|
|
3409
|
+
this.#notifyMediaPause(event);
|
|
3314
3410
|
}
|
|
3315
3411
|
this.#ackRecovery();
|
|
3316
3412
|
this.#emit();
|
|
@@ -3404,7 +3500,7 @@ var V1SyncClient = class {
|
|
|
3404
3500
|
this.#send({ type: "recovery_ack", recovery_id: recovery.id, revision: control.revision, state_digest: control.stateDigest });
|
|
3405
3501
|
}
|
|
3406
3502
|
#ackHeadIsProven(ack) {
|
|
3407
|
-
return this.#controlHeads.get(ack.revision) === ack.state_digest;
|
|
3503
|
+
return this.#snapshotRevision !== null && ack.revision < this.#snapshotRevision || this.#controlHeads.get(ack.revision) === ack.state_digest;
|
|
3408
3504
|
}
|
|
3409
3505
|
#rememberControlHead(revision, digest) {
|
|
3410
3506
|
this.#controlHeads.set(revision, digest);
|
|
@@ -3423,6 +3519,7 @@ var V1SyncClient = class {
|
|
|
3423
3519
|
#disconnected(socket) {
|
|
3424
3520
|
if (socket !== this.#socket) return;
|
|
3425
3521
|
this.#socket = null;
|
|
3522
|
+
this.#clearDial();
|
|
3426
3523
|
this.#recovery = null;
|
|
3427
3524
|
this.#clearHeartbeat();
|
|
3428
3525
|
this.#clearNoticeProbe();
|
|
@@ -3452,6 +3549,29 @@ var V1SyncClient = class {
|
|
|
3452
3549
|
socket.close(CLIENT_RESTART_CLOSE_CODE, reason);
|
|
3453
3550
|
this.#disconnected(socket);
|
|
3454
3551
|
}
|
|
3552
|
+
#recoverDigestMismatch() {
|
|
3553
|
+
const now = this.#clock().now();
|
|
3554
|
+
if (this.#lastDigestRecoveryAt !== null && now - this.#lastDigestRecoveryAt < DIGEST_RECOVERY_COOLDOWN_MS) {
|
|
3555
|
+
this.#failDigestRecovery();
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
this.#lastDigestRecoveryAt = now;
|
|
3559
|
+
this.#snapshotRecoveryRequested = true;
|
|
3560
|
+
this.#control = null;
|
|
3561
|
+
this.#snapshotRevision = null;
|
|
3562
|
+
this.#controlHeads.clear();
|
|
3563
|
+
this.#controlEvents.clear();
|
|
3564
|
+
this.#recover("state_digest_mismatch");
|
|
3565
|
+
}
|
|
3566
|
+
#failDigestRecovery() {
|
|
3567
|
+
this.#phase = { phase: "terminal", terminalReason: "state_digest_recovery_failed" };
|
|
3568
|
+
this.#clearReconnect();
|
|
3569
|
+
this.#commandScheduler.stop("state_digest_recovery_failed");
|
|
3570
|
+
const socket = this.#socket;
|
|
3571
|
+
socket?.close(CLIENT_RESTART_CLOSE_CODE, "state_digest_recovery_failed");
|
|
3572
|
+
if (socket) this.#disconnected(socket);
|
|
3573
|
+
this.#emit();
|
|
3574
|
+
}
|
|
3455
3575
|
#handleLifecycle(event) {
|
|
3456
3576
|
if (event === "online" || event === "offline") this.#online = event === "online";
|
|
3457
3577
|
else this.#active = event === "active";
|
|
@@ -3500,6 +3620,10 @@ var V1SyncClient = class {
|
|
|
3500
3620
|
#now() {
|
|
3501
3621
|
return this.#clock().now();
|
|
3502
3622
|
}
|
|
3623
|
+
#clearDial() {
|
|
3624
|
+
this.#cancelDial?.();
|
|
3625
|
+
this.#cancelDial = void 0;
|
|
3626
|
+
}
|
|
3503
3627
|
#clearReconnect() {
|
|
3504
3628
|
if (this.#reconnectTimer === void 0) return;
|
|
3505
3629
|
this.#clock().clearTimeout(this.#reconnectTimer);
|
|
@@ -3557,33 +3681,29 @@ var V1SyncClient = class {
|
|
|
3557
3681
|
}
|
|
3558
3682
|
#startNoticeProbe() {
|
|
3559
3683
|
this.#clearNoticeProbe();
|
|
3560
|
-
this.#
|
|
3561
|
-
this.#
|
|
3684
|
+
this.#send({ type: "ping" });
|
|
3685
|
+
this.#health = new V1SyncHealth(this.#now());
|
|
3562
3686
|
const tick = () => {
|
|
3563
3687
|
this.#noticeProbeTimer = void 0;
|
|
3564
3688
|
if (this.#phase.phase !== "live") return;
|
|
3689
|
+
const previous = this.#health.health;
|
|
3690
|
+
this.#health.check(this.#now());
|
|
3691
|
+
if (previous !== this.#health.health) this.#emit();
|
|
3565
3692
|
this.#send({ type: "ping" });
|
|
3566
|
-
this.#noticeProbeTimer = this.#clock().setTimeout(tick,
|
|
3693
|
+
this.#noticeProbeTimer = this.#clock().setTimeout(tick, SYNC_HEALTH_BUDGET.probeIntervalMs);
|
|
3567
3694
|
};
|
|
3568
|
-
this.#noticeProbeTimer = this.#clock().setTimeout(tick,
|
|
3569
|
-
}
|
|
3570
|
-
#scheduleNoticeCheck() {
|
|
3571
|
-
if (this.#noticeTimer !== void 0) this.#clock().clearTimeout(this.#noticeTimer);
|
|
3572
|
-
const remaining = Math.max(0, NOTICE_SILENCE_MS - (this.#now() - this.#noticeLastInboundAt));
|
|
3695
|
+
this.#noticeProbeTimer = this.#clock().setTimeout(tick, SYNC_HEALTH_BUDGET.probeIntervalMs);
|
|
3573
3696
|
this.#noticeTimer = this.#clock().setTimeout(() => {
|
|
3574
3697
|
this.#noticeTimer = void 0;
|
|
3575
|
-
|
|
3576
|
-
if (this.#now() - this.#noticeLastInboundAt < NOTICE_SILENCE_MS) return this.#scheduleNoticeCheck();
|
|
3577
|
-
this.#noticeUnresponsive = true;
|
|
3698
|
+
this.#health.check(this.#now());
|
|
3578
3699
|
this.#emit();
|
|
3579
|
-
},
|
|
3700
|
+
}, SYNC_HEALTH_BUDGET.degradedAfterMs);
|
|
3580
3701
|
}
|
|
3581
3702
|
#clearNoticeProbe() {
|
|
3582
3703
|
if (this.#noticeTimer !== void 0) this.#clock().clearTimeout(this.#noticeTimer);
|
|
3583
3704
|
if (this.#noticeProbeTimer !== void 0) this.#clock().clearTimeout(this.#noticeProbeTimer);
|
|
3584
3705
|
this.#noticeTimer = void 0;
|
|
3585
3706
|
this.#noticeProbeTimer = void 0;
|
|
3586
|
-
this.#noticeUnresponsive = false;
|
|
3587
3707
|
}
|
|
3588
3708
|
#emit() {
|
|
3589
3709
|
const snapshot = this.getSnapshot();
|
|
@@ -3786,6 +3906,7 @@ export {
|
|
|
3786
3906
|
createBrowserSyncLifecycle,
|
|
3787
3907
|
createReactNativeWebSocketFactory,
|
|
3788
3908
|
createReactNativeSyncLifecycle,
|
|
3909
|
+
scheduleSyncDialDeadline,
|
|
3789
3910
|
encodeV1ClientFrame,
|
|
3790
3911
|
decodeV1ServerFrame,
|
|
3791
3912
|
decodeV1ClientFrame,
|