@q9labsai/chalk-client 5.2.1 → 5.2.3
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/{chunk-RVUN6LW4.js → chunk-4ZUN55Z2.js} +83 -27
- package/dist/{chunk-TVNI2XUZ.js → chunk-IMMT22US.js} +32 -9
- package/dist/connection/diagnostics.d.ts +14 -0
- package/dist/effect.js +2 -2
- package/dist/generated/sync.d.ts +1 -1
- package/dist/generated/whiteboard-v1.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +20 -2
- package/dist/space-client/failure-detail.d.ts +6 -0
- package/dist/space-client/types.d.ts +1 -0
- package/dist/sync/connect-diagnostics.d.ts +16 -0
- package/dist/sync/index.js +1 -1
- package/dist/sync/react-native.d.ts +1 -0
- package/dist/sync/types.d.ts +1 -0
- package/dist/sync/v1-types.d.ts +1 -0
- package/dist/whiteboard/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -18,8 +18,9 @@ import {
|
|
|
18
18
|
createBrowserWebSocketFactory,
|
|
19
19
|
createV1SyncClient,
|
|
20
20
|
formatSyncConnectDiagnostic,
|
|
21
|
-
scheduleSyncDialDeadline
|
|
22
|
-
|
|
21
|
+
scheduleSyncDialDeadline,
|
|
22
|
+
syncConnectFailureCode
|
|
23
|
+
} from "./chunk-IMMT22US.js";
|
|
23
24
|
|
|
24
25
|
// src/access/manager.ts
|
|
25
26
|
import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
|
|
@@ -332,7 +333,8 @@ var WhiteboardV1ProtocolMetadata = {
|
|
|
332
333
|
extensions: {
|
|
333
334
|
presentation: {
|
|
334
335
|
name: "presentation_v1",
|
|
335
|
-
exactFields: ["name"]
|
|
336
|
+
exactFields: ["name"],
|
|
337
|
+
presenterExactFields: ["name", "presenter"]
|
|
336
338
|
},
|
|
337
339
|
view: {
|
|
338
340
|
name: "view_v1",
|
|
@@ -654,7 +656,7 @@ function isExtensions(value) {
|
|
|
654
656
|
const drawPolicyLast = value.length > 0 && isExtension(value[value.length - 1], drawPolicy);
|
|
655
657
|
const base = drawPolicyLast ? value.slice(0, -1) : value;
|
|
656
658
|
if (base.length === 0) return drawPolicyLast;
|
|
657
|
-
return (base.length === 1 || base.length === 2) && isExtension(base[0], presentation) && (base.length === 1 || isExtension(base[1], view) || hasExactKeys(base[1], view.ownerExactFields) && base[1].name === view.name && base[1].origins === true && base[1].owner === true || hasExactKeys(base[1], view.extendedExactFields) && base[1].name === view.name && base[1].origins === true);
|
|
659
|
+
return (base.length === 1 || base.length === 2) && (isExtension(base[0], presentation) || hasExactKeys(base[0], presentation.presenterExactFields) && base[0].name === presentation.name && base[0].presenter === true) && (base.length === 1 || isExtension(base[1], view) || hasExactKeys(base[1], view.ownerExactFields) && base[1].name === view.name && base[1].origins === true && base[1].owner === true || hasExactKeys(base[1], view.extendedExactFields) && base[1].name === view.name && base[1].origins === true);
|
|
658
660
|
}
|
|
659
661
|
function isDrawPolicy(value) {
|
|
660
662
|
return typeof value === "string" && WhiteboardV1ProtocolMetadata.drawPolicies.some((policy) => policy === value);
|
|
@@ -663,6 +665,10 @@ function isDrawGrant(value) {
|
|
|
663
665
|
return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.drawAccess.grantExactFields) && isUuid(value.participant_id) && typeof value.can_draw === "boolean";
|
|
664
666
|
}
|
|
665
667
|
function isWelcome(value) {
|
|
668
|
+
if ("presenter_id" in value) {
|
|
669
|
+
const { presenter_id, ...welcome } = value;
|
|
670
|
+
return (presenter_id === null || isUuid(presenter_id)) && isWelcome(welcome);
|
|
671
|
+
}
|
|
666
672
|
if ("view_owner_id" in value) {
|
|
667
673
|
const { view_owner_id, ...welcome } = value;
|
|
668
674
|
return isUuid(view_owner_id) && isWelcome(welcome);
|
|
@@ -670,6 +676,13 @@ function isWelcome(value) {
|
|
|
670
676
|
const exact = hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.welcome.exactFields) || hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.welcome.extendedExactFields) && typeof value.presenting === "boolean";
|
|
671
677
|
return exact && value.protocol === "whiteboard-v1" && isUuid(value.participant_id) && Number.isSafeInteger(value.participant_generation) && Number(value.participant_generation) > 0 && isCapabilities(value.capabilities) && isCapabilities(value.participant_capabilities) && isUuid(value.scene_id) && isUnsignedDecimal(value.revision) && typeof value.can_draw === "boolean";
|
|
672
678
|
}
|
|
679
|
+
function isPresentationUpdated(value) {
|
|
680
|
+
if ("presenter_id" in value) {
|
|
681
|
+
const { presenter_id, ...frame } = value;
|
|
682
|
+
return (presenter_id === null || isUuid(presenter_id)) && isPresentationUpdated(frame);
|
|
683
|
+
}
|
|
684
|
+
return hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.presentationUpdated.exactFields) && isUuid(value.scene_id) && isUnsignedDecimal(value.revision) && typeof value.presenting === "boolean";
|
|
685
|
+
}
|
|
673
686
|
function isServerFrame(value) {
|
|
674
687
|
if (!isRecord2(value)) return false;
|
|
675
688
|
switch (value.type) {
|
|
@@ -692,7 +705,7 @@ function isServerFrame(value) {
|
|
|
692
705
|
case "draw_access":
|
|
693
706
|
return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.drawAccess.exactFields) && isDrawPolicy(value.policy) && typeof value.can_draw === "boolean" && isCapabilities(value.capabilities) && Array.isArray(value.grants) && value.grants.length <= WhiteboardV1ProtocolLimits.drawAccessMaxGrants && value.grants.every(isDrawGrant);
|
|
694
707
|
case "presentation_updated":
|
|
695
|
-
return
|
|
708
|
+
return isPresentationUpdated(value);
|
|
696
709
|
case "reset_required":
|
|
697
710
|
return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.resetRequired.exactFields) && isUuid(value.scene_id) && typeof value.reason === "string" && WhiteboardV1ProtocolMetadata.resetReasons.some((reason) => reason === value.reason);
|
|
698
711
|
case "operation_error":
|
|
@@ -1065,7 +1078,8 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1065
1078
|
#presentationSupported = false;
|
|
1066
1079
|
#viewsSupported = false;
|
|
1067
1080
|
#presenting = false;
|
|
1068
|
-
#
|
|
1081
|
+
#presenterId = null;
|
|
1082
|
+
#hello = "presenter-view";
|
|
1069
1083
|
#viewSequence = 0;
|
|
1070
1084
|
#viewOwnerId = null;
|
|
1071
1085
|
#viewVersions = new ChalkWhiteboardViewVersions();
|
|
@@ -1118,7 +1132,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1118
1132
|
if (this.#started && this.#startPromise) return this.#startPromise;
|
|
1119
1133
|
this.#started = true;
|
|
1120
1134
|
globalThis.document?.addEventListener("visibilitychange", this.#visibilityChanged);
|
|
1121
|
-
this.#hello = "
|
|
1135
|
+
this.#hello = "presenter-view";
|
|
1122
1136
|
this.#rejectedHelloRetries = 0;
|
|
1123
1137
|
this.#viewsSupported = false;
|
|
1124
1138
|
this.#phase = "connecting";
|
|
@@ -1172,6 +1186,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1172
1186
|
this.#drawPolicy = null;
|
|
1173
1187
|
this.#drawPermissions = [];
|
|
1174
1188
|
this.#presenting = false;
|
|
1189
|
+
this.#presenterId = null;
|
|
1175
1190
|
this.#publishSummary("unsubscribed", null);
|
|
1176
1191
|
}
|
|
1177
1192
|
subscribe(listener) {
|
|
@@ -1488,6 +1503,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1488
1503
|
this.#sceneId = frame.scene_id;
|
|
1489
1504
|
this.#revision = frame.revision;
|
|
1490
1505
|
this.#presenting = frame.presenting;
|
|
1506
|
+
this.#presenterId = frame.presenter_id ?? null;
|
|
1491
1507
|
this.#publishSummary("ready", null);
|
|
1492
1508
|
return;
|
|
1493
1509
|
case "reset_required":
|
|
@@ -1560,7 +1576,13 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1560
1576
|
this.#drawPolicy = null;
|
|
1561
1577
|
this.#drawPermissions = [];
|
|
1562
1578
|
this.#presentationSupported = "presenting" in frame;
|
|
1563
|
-
|
|
1579
|
+
if ("presenting" in frame) {
|
|
1580
|
+
this.#presenting = frame.presenting;
|
|
1581
|
+
this.#presenterId = frame.presenter_id ?? null;
|
|
1582
|
+
} else {
|
|
1583
|
+
this.#presenting = false;
|
|
1584
|
+
this.#presenterId = null;
|
|
1585
|
+
}
|
|
1564
1586
|
const refused = !drawPolicyNegotiated(this.#hello) && this.#dropPolicyChanges();
|
|
1565
1587
|
this.#publishSummary("loading", refused ? policyUnavailable() : null);
|
|
1566
1588
|
this.#clearHeartbeat();
|
|
@@ -1864,7 +1886,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1864
1886
|
if (step === "fail") return false;
|
|
1865
1887
|
if (step === "step-down") return this.#stepDownHello();
|
|
1866
1888
|
if (step === "retry-hello") return this.#retryHello();
|
|
1867
|
-
if (phase === "live") this.#hello = "
|
|
1889
|
+
if (phase === "live") this.#hello = "presenter-view";
|
|
1868
1890
|
return true;
|
|
1869
1891
|
}
|
|
1870
1892
|
#stepDownHello() {
|
|
@@ -1988,6 +2010,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1988
2010
|
drawPolicy: this.#drawPolicy,
|
|
1989
2011
|
drawPermissions: this.#drawPermissions,
|
|
1990
2012
|
presenting: this.#presenting,
|
|
2013
|
+
presenterId: this.#presenterId,
|
|
1991
2014
|
error: this.#summaryError
|
|
1992
2015
|
};
|
|
1993
2016
|
}
|
|
@@ -2192,19 +2215,20 @@ function operationName(frame) {
|
|
|
2192
2215
|
function retryableOperationCode(code) {
|
|
2193
2216
|
return code === "unavailable" || code === "overloaded" || code === "rate_limited" || code === "storage_unavailable";
|
|
2194
2217
|
}
|
|
2195
|
-
var HELLO_LEVELS = ["owner-view", "draw-policy", "versioned-view", "view", "presentation", "legacy"];
|
|
2218
|
+
var HELLO_LEVELS = ["presenter-view", "owner-view", "draw-policy", "versioned-view", "view", "presentation", "legacy"];
|
|
2196
2219
|
function viewsNegotiated(level) {
|
|
2197
|
-
return level === "owner-view" || level === "draw-policy" || level === "versioned-view" || level === "view";
|
|
2220
|
+
return level === "presenter-view" || level === "owner-view" || level === "draw-policy" || level === "versioned-view" || level === "view";
|
|
2198
2221
|
}
|
|
2199
2222
|
function viewOriginsNegotiated(level) {
|
|
2200
|
-
return level === "owner-view" || level === "draw-policy" || level === "versioned-view";
|
|
2223
|
+
return level === "presenter-view" || level === "owner-view" || level === "draw-policy" || level === "versioned-view";
|
|
2201
2224
|
}
|
|
2202
2225
|
function drawPolicyNegotiated(level) {
|
|
2203
|
-
return level === "owner-view" || level === "draw-policy";
|
|
2226
|
+
return level === "presenter-view" || level === "owner-view" || level === "draw-policy";
|
|
2204
2227
|
}
|
|
2205
2228
|
function helloFrame(level, token, cursor) {
|
|
2206
2229
|
const hello = { type: "hello", protocol: "whiteboard-v1", token, cursor };
|
|
2207
2230
|
if (level === "legacy") return hello;
|
|
2231
|
+
if (level === "presenter-view") return { ...hello, extensions: [{ name: "presentation_v1", presenter: true }, { name: "view_v1", origins: true, owner: true }, { name: "draw_policy_v1" }] };
|
|
2208
2232
|
if (level === "presentation") return { ...hello, extensions: [{ name: "presentation_v1" }] };
|
|
2209
2233
|
if (level === "view") return { ...hello, extensions: [{ name: "presentation_v1" }, { name: "view_v1" }] };
|
|
2210
2234
|
if (level === "versioned-view") return { ...hello, extensions: [{ name: "presentation_v1" }, { name: "view_v1", origins: true }] };
|
|
@@ -2517,6 +2541,22 @@ var ConnectionDiagnostics = class {
|
|
|
2517
2541
|
} catch {
|
|
2518
2542
|
}
|
|
2519
2543
|
}
|
|
2544
|
+
recordRecoveryExhausted(input) {
|
|
2545
|
+
const { state, epoch, code, attempts, transport } = input;
|
|
2546
|
+
this.record({
|
|
2547
|
+
event: "recovery_exhausted",
|
|
2548
|
+
state,
|
|
2549
|
+
epoch,
|
|
2550
|
+
code,
|
|
2551
|
+
recovery: {
|
|
2552
|
+
reference: `connection.${code}`,
|
|
2553
|
+
attempts,
|
|
2554
|
+
transport,
|
|
2555
|
+
lastHeartbeatReplyAgoMs: transport?.lastHeartbeatReplyAt == null ? null : Math.max(0, this.#now() - transport.lastHeartbeatReplyAt),
|
|
2556
|
+
visibilityState: typeof document === "undefined" ? null : document.visibilityState
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2520
2560
|
snapshot() {
|
|
2521
2561
|
return Object.freeze([...this.#events]);
|
|
2522
2562
|
}
|
|
@@ -2551,7 +2591,7 @@ var ConnectionDiagnostics = class {
|
|
|
2551
2591
|
|
|
2552
2592
|
// src/connection/lifecycle.ts
|
|
2553
2593
|
var MEDIA_RECOVERY_TIMEOUT_MS = 6e4;
|
|
2554
|
-
var START_TIMEOUT_MS =
|
|
2594
|
+
var START_TIMEOUT_MS = 3e4;
|
|
2555
2595
|
var LEAVE_TIMEOUT_MS = 5e3;
|
|
2556
2596
|
var RECOVERY_BUDGET_MS = 1e4;
|
|
2557
2597
|
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
@@ -2768,7 +2808,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2768
2808
|
const failure2 = snapshot.connection.connectFailure;
|
|
2769
2809
|
if (failure2 && failure2 !== observedConnectFailure) {
|
|
2770
2810
|
observedConnectFailure = failure2;
|
|
2771
|
-
diagnostics.record({ event: "sync_connect_failed", state: model.state, epoch: model.epoch, code:
|
|
2811
|
+
diagnostics.record({ event: "sync_connect_failed", state: model.state, epoch: model.epoch, code: syncConnectFailureCode(failure2), sync: failure2 });
|
|
2772
2812
|
}
|
|
2773
2813
|
const count = snapshot.connection.dialTimeoutCount ?? 0;
|
|
2774
2814
|
if (count > observedDialTimeouts) {
|
|
@@ -2844,7 +2884,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2844
2884
|
const span = diagnostics.startSpan({ step, state: model.state, epoch: model.epoch });
|
|
2845
2885
|
return effect.pipe(
|
|
2846
2886
|
Effect2.tap(() => Effect2.sync(() => span.end({ state: model.state, epoch: model.epoch, outcome: "succeeded" }))),
|
|
2847
|
-
Effect2.tapError(() => Effect2.sync(() => span.end({ state: model.state, epoch: model.epoch, outcome: "failed" })))
|
|
2887
|
+
Effect2.tapError((cause) => Effect2.sync(() => span.end({ state: model.state, epoch: model.epoch, outcome: "failed", code: failureFromCause(cause, "internal_error", "Connection step failed").code })))
|
|
2848
2888
|
);
|
|
2849
2889
|
};
|
|
2850
2890
|
const confirmSyncEntry = (sync) => {
|
|
@@ -2921,7 +2961,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2921
2961
|
Effect2.matchEffect({
|
|
2922
2962
|
onFailure: (cause) => Effect2.gen(function* () {
|
|
2923
2963
|
stopStream(stream);
|
|
2924
|
-
const failure2 = failureFromCause(cause, "
|
|
2964
|
+
const failure2 = failureFromCause(cause, "internal_error", "The Connection could not start");
|
|
2925
2965
|
model.failure = toFailure(failure2);
|
|
2926
2966
|
if (model.joinCancelled) {
|
|
2927
2967
|
span.end({ state: model.state, epoch: model.epoch, outcome: "cancelled", code: "invalid_state" });
|
|
@@ -3048,7 +3088,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3048
3088
|
const syncAttempt = { client: null, expiresAt: null, started: true };
|
|
3049
3089
|
while (model.epoch === epoch && active(model)) {
|
|
3050
3090
|
const plan = yield* recoveryPlan(model.recoveryQueued ?? kind);
|
|
3051
|
-
|
|
3091
|
+
let attempts = 0;
|
|
3092
|
+
const outcome = yield* attemptRecovery(plan, mediaAttempt, syncAttempt, () => attempts++).pipe(
|
|
3052
3093
|
Effect2.match({
|
|
3053
3094
|
onSuccess: (attempt) => ({ attempt, failure: null }),
|
|
3054
3095
|
onFailure: (failure2) => ({ attempt: null, failure: failure2 })
|
|
@@ -3066,9 +3107,18 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3066
3107
|
yield* enqueue(
|
|
3067
3108
|
Effect2.gen(function* () {
|
|
3068
3109
|
if (model.epoch !== epoch || !active(model)) return;
|
|
3110
|
+
if (plan.kind === "sync" && model.sync?.getSnapshot().connection.phase === "live") {
|
|
3111
|
+
model.recoveryQueued = "media";
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3069
3114
|
const code = plan.kind === "media" ? "media_recovery_exhausted" : "sync_recovery_exhausted";
|
|
3115
|
+
const transport = model.sync?.getSnapshot().connection.recoveryDiagnostic ?? null;
|
|
3116
|
+
diagnostics.recordRecoveryExhausted({ state: model.state, epoch, code, attempts, transport });
|
|
3117
|
+
if (plan.kind === "sync") {
|
|
3118
|
+
yield* failForSnapshot(lifecycleFailure(code, true, "Connection lost. Rejoin to continue."));
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3070
3121
|
model.failure = toFailure(lifecycleFailure(code, true, "Connection interrupted. Reconnecting in the background."));
|
|
3071
|
-
diagnostics.record({ event: "recovery_exhausted", state: model.state, epoch, code });
|
|
3072
3122
|
yield* publish();
|
|
3073
3123
|
})
|
|
3074
3124
|
);
|
|
@@ -3089,14 +3139,14 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3089
3139
|
delays: options.recovery?.backoffMs?.length ? options.recovery.backoffMs : [100, 250, 500]
|
|
3090
3140
|
}))
|
|
3091
3141
|
);
|
|
3092
|
-
const attemptRecovery = (plan, mediaAttempt2, syncAttempt) => Effect2.gen(function* () {
|
|
3142
|
+
const attemptRecovery = (plan, mediaAttempt2, syncAttempt, onAttempt) => Effect2.gen(function* () {
|
|
3093
3143
|
for (let attempt = 1; attempt <= plan.attempts; attempt += 1) {
|
|
3094
3144
|
const remaining = plan.deadline - (yield* Clock2.currentTimeMillis);
|
|
3095
3145
|
if (remaining <= 0) return null;
|
|
3146
|
+
onAttempt();
|
|
3096
3147
|
diagnostics.record({ event: "recovery_attempt", state: model.state, epoch: model.epoch, attempt });
|
|
3097
3148
|
const failure2 = yield* recoveryOperation(mediaAttempt2, syncAttempt).pipe(
|
|
3098
|
-
|
|
3099
|
-
Effect2.catchTag("TimeoutError", () => Effect2.fail(lifecycleFailure("sync_start_failed", true, "Connection recovery is still waiting for the transport"))),
|
|
3149
|
+
(effect) => withinForegroundBudget(effect, remaining, lifecycleFailure("sync_start_failed", true, "Connection recovery is still waiting for the transport")),
|
|
3100
3150
|
Effect2.match({ onSuccess: () => null, onFailure: (cause) => cause })
|
|
3101
3151
|
);
|
|
3102
3152
|
if (failure2 === null) return attempt;
|
|
@@ -3584,9 +3634,11 @@ function syncTerminalFailure(snapshot) {
|
|
|
3584
3634
|
return lifecycleFailure("sync_start_failed", true, "The Sync transport needs recovery");
|
|
3585
3635
|
}
|
|
3586
3636
|
function syncStartupFailure(snapshot) {
|
|
3587
|
-
const
|
|
3637
|
+
const evidence = snapshot.connection.connectFailure ?? snapshot.connection.connectAttempt;
|
|
3638
|
+
const attempts = snapshot.connection.connectAttempt?.attempt;
|
|
3639
|
+
const sync = evidence ? { ...evidence, ...attempts === void 0 ? {} : { attempts } } : void 0;
|
|
3588
3640
|
const message = sync?.opened === false ? "Sync could not reach the call service" : "Sync could not connect";
|
|
3589
|
-
return new ConnectionLifecycleFailure({ code:
|
|
3641
|
+
return new ConnectionLifecycleFailure({ code: syncConnectFailureCode(sync), recoverable: true, message: `${message}${sync ? ` ${formatSyncConnectDiagnostic(sync)}` : " before the startup deadline"}`, ...sync ? { sync: Object.freeze({ ...sync }) } : {} });
|
|
3590
3642
|
}
|
|
3591
3643
|
function syncAutoRecoverable(snapshot, failure2) {
|
|
3592
3644
|
return failure2.recoverable && snapshot.connection.terminalReason !== "entry_timeout";
|
|
@@ -3631,6 +3683,7 @@ var INVALID_PAYLOAD_CODE_BY_ACTION = /* @__PURE__ */ new Map([
|
|
|
3631
3683
|
]);
|
|
3632
3684
|
var MEDIA_TIMEOUT_FAILURE = { code: "media.timeout", recoverable: true, message: "Media did not respond in time. Try again." };
|
|
3633
3685
|
var SYNC_FAILURES = /* @__PURE__ */ new Map([
|
|
3686
|
+
["sync_start_failed", { failure: { code: "connection.sync_start_failed", recoverable: true, message: "A connection to the call service could not be opened. Try again." } }],
|
|
3634
3687
|
["sync_hello_timeout", { failure: { code: "connection.sync_hello_timeout", recoverable: true, message: "Sync could not reach the call service. Try again." } }],
|
|
3635
3688
|
["not_live", { failure: { code: "connection.invalid_state", recoverable: true, message: "Wait for the call to connect, then try again." } }],
|
|
3636
3689
|
["signaling_timeout", { failure: MEDIA_TIMEOUT_FAILURE }],
|
|
@@ -6233,6 +6286,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6233
6286
|
drawPolicy: summary.drawPolicy,
|
|
6234
6287
|
drawPermissions: Object.freeze(summary.drawPermissions.map((permission) => Object.freeze({ ...permission }))),
|
|
6235
6288
|
presenting: summary.presenting,
|
|
6289
|
+
presenterId: summary.presenterId ?? null,
|
|
6236
6290
|
error: summary.error ? Object.freeze({ ...summary.error }) : null
|
|
6237
6291
|
});
|
|
6238
6292
|
this.#publish();
|
|
@@ -6251,7 +6305,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6251
6305
|
#publish() {
|
|
6252
6306
|
const slice = Object.freeze({
|
|
6253
6307
|
open: this.#summary.status !== "unsubscribed",
|
|
6254
|
-
engine: Object.freeze({ status: this.#summary.status, sceneId: this.#summary.sceneId, revision: this.#summary.revision, presenting: this.#summary.presenting, error: this.#summary.error }),
|
|
6308
|
+
engine: Object.freeze({ status: this.#summary.status, sceneId: this.#summary.sceneId, revision: this.#summary.revision, presenting: this.#summary.presenting, presenterId: this.#summary.presenterId ?? null, error: this.#summary.error }),
|
|
6255
6309
|
access: Object.freeze({ canDraw: this.#summary.canDraw, policy: this.#summary.drawPolicy, permissions: this.#summary.drawPermissions })
|
|
6256
6310
|
});
|
|
6257
6311
|
const current = this.#store.getSnapshot().whiteboard;
|
|
@@ -6260,7 +6314,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6260
6314
|
}
|
|
6261
6315
|
};
|
|
6262
6316
|
function sameWhiteboard(left, right) {
|
|
6263
|
-
return left.open === right.open && left.engine.status === right.engine.status && left.engine.sceneId === right.engine.sceneId && left.engine.revision === right.engine.revision && left.engine.presenting === right.engine.presenting && sameError2(left.engine.error, right.engine.error) && sameAccess(left.access, right.access);
|
|
6317
|
+
return left.open === right.open && left.engine.status === right.engine.status && left.engine.sceneId === right.engine.sceneId && left.engine.revision === right.engine.revision && left.engine.presenting === right.engine.presenting && left.engine.presenterId === right.engine.presenterId && sameError2(left.engine.error, right.engine.error) && sameAccess(left.access, right.access);
|
|
6264
6318
|
}
|
|
6265
6319
|
function sameAccess(left, right) {
|
|
6266
6320
|
return left.canDraw === right.canDraw && left.policy === right.policy && left.permissions.length === right.permissions.length && left.permissions.every((permission, index) => permission.participantId === right.permissions[index]?.participantId && permission.canDraw === right.permissions[index]?.canDraw);
|
|
@@ -6355,7 +6409,9 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
|
|
|
6355
6409
|
diagnostics: {
|
|
6356
6410
|
onEvent: (event) => {
|
|
6357
6411
|
platform.onConnectionDiagnostic?.(event);
|
|
6358
|
-
|
|
6412
|
+
const attributes = { event: event.event, state: event.state, epoch: event.epoch, ...event.code ? { code: event.code } : {}, ...event.sync ? { sync: event.sync } : {}, ...event.recovery ? { recovery: event.recovery } : {} };
|
|
6413
|
+
if (event.event === "recovery_exhausted") options.logger?.warn?.("SpaceClient connection event", attributes);
|
|
6414
|
+
else options.logger?.debug?.("SpaceClient connection event", attributes);
|
|
6359
6415
|
}
|
|
6360
6416
|
}
|
|
6361
6417
|
});
|
|
@@ -120,7 +120,7 @@ var BrowserSyncSocket = class {
|
|
|
120
120
|
this.#socket = socket;
|
|
121
121
|
this.#socket.addEventListener("open", () => this.onopen?.());
|
|
122
122
|
this.#socket.addEventListener("message", (event) => this.onmessage?.({ data: event.data }));
|
|
123
|
-
this.#socket.addEventListener("close", (event) => this.onclose?.({ code: event.code, reason: event.reason }));
|
|
123
|
+
this.#socket.addEventListener("close", (event) => this.onclose?.({ code: event.code, reason: event.reason, ...typeof event.wasClean === "boolean" ? { wasClean: event.wasClean } : {} }));
|
|
124
124
|
this.#socket.addEventListener("error", () => this.onerror?.());
|
|
125
125
|
}
|
|
126
126
|
send(data) {
|
|
@@ -178,7 +178,7 @@ var ReactNativeSyncSocket = class {
|
|
|
178
178
|
this.#socket = socket;
|
|
179
179
|
this.#socket.onopen = () => this.onopen?.();
|
|
180
180
|
this.#socket.onmessage = (event) => this.onmessage?.({ data: event.data });
|
|
181
|
-
this.#socket.onclose = (event) => this.onclose?.({ code: typeof event.code === "number" ? event.code : 1006, reason: typeof event.reason === "string" ? event.reason : void 0 });
|
|
181
|
+
this.#socket.onclose = (event) => this.onclose?.({ code: typeof event.code === "number" ? event.code : 1006, reason: typeof event.reason === "string" ? event.reason : void 0, ...typeof event.wasClean === "boolean" ? { wasClean: event.wasClean } : {} });
|
|
182
182
|
this.#socket.onerror = () => this.onerror?.();
|
|
183
183
|
}
|
|
184
184
|
send(data) {
|
|
@@ -198,7 +198,7 @@ var BrowserFallbackSyncSocket = class {
|
|
|
198
198
|
this.#socket = socket;
|
|
199
199
|
this.#socket.addEventListener("open", () => this.onopen?.());
|
|
200
200
|
this.#socket.addEventListener("message", (event) => this.onmessage?.({ data: event.data }));
|
|
201
|
-
this.#socket.addEventListener("close", (event) => this.onclose?.({ code: event.code, reason: event.reason }));
|
|
201
|
+
this.#socket.addEventListener("close", (event) => this.onclose?.({ code: event.code, reason: event.reason, ...typeof event.wasClean === "boolean" ? { wasClean: event.wasClean } : {} }));
|
|
202
202
|
this.#socket.addEventListener("error", () => this.onerror?.());
|
|
203
203
|
}
|
|
204
204
|
send(data) {
|
|
@@ -249,8 +249,11 @@ var SAFE_REASONS = /* @__PURE__ */ new Set([
|
|
|
249
249
|
function safeSyncCloseReason(reason) {
|
|
250
250
|
return reason ? SAFE_REASONS.has(reason) ? reason : "redacted" : null;
|
|
251
251
|
}
|
|
252
|
+
function syncConnectFailureCode(diagnostic) {
|
|
253
|
+
return diagnostic?.stage === "upgrade" ? "sync_start_failed" : "sync_hello_timeout";
|
|
254
|
+
}
|
|
252
255
|
function formatSyncConnectDiagnostic(diagnostic) {
|
|
253
|
-
return `[${diagnostic.stage}; open=${diagnostic.opened ? 1 : 0}; close=${diagnostic.closeCode ?? "none"}; reason=${safeSyncCloseReason(diagnostic.closeReason ?? void 0) ?? "none"}; ${Math.round(diagnostic.durationMs)}ms]`;
|
|
256
|
+
return `[${diagnostic.stage}; open=${diagnostic.opened ? 1 : 0}; close=${diagnostic.closeCode ?? "none"}; reason=${safeSyncCloseReason(diagnostic.closeReason ?? void 0) ?? "none"}; ${diagnostic.stage === "upgrade" && diagnostic.closeCode !== null ? "cause=closed_before_open; " : ""}${diagnostic.attempt === void 0 ? "" : `attempt=${diagnostic.attempt}; `}${diagnostic.attempts === void 0 ? "" : `attempts=${diagnostic.attempts}; `}${Math.round(diagnostic.durationMs)}ms]`;
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
// src/sync/v1-health.ts
|
|
@@ -336,7 +339,7 @@ var SyncProtocolMetadata = {
|
|
|
336
339
|
accepts: ["entered", "command", "live_target", "directed_request", "delivery_ack", "request_ack", "reaction_send", "chat_send", "chat_page_request", "chat_read_set", "chat_reaction_set", "ping"],
|
|
337
340
|
heartbeat: {
|
|
338
341
|
intervalMs: 2e4,
|
|
339
|
-
missedDeadlinesBeforeClose:
|
|
342
|
+
missedDeadlinesBeforeClose: 5
|
|
340
343
|
}
|
|
341
344
|
},
|
|
342
345
|
{
|
|
@@ -3024,10 +3027,14 @@ var V1SyncClient = class {
|
|
|
3024
3027
|
#cancelDial;
|
|
3025
3028
|
#dialTimeoutCount = 0;
|
|
3026
3029
|
#connectStartedAt = null;
|
|
3030
|
+
#connectAttemptCount = 0;
|
|
3031
|
+
#socketCloses = [];
|
|
3032
|
+
#omittedSocketCloses = 0;
|
|
3033
|
+
#lastHeartbeatReplyAt = null;
|
|
3027
3034
|
#opened = false;
|
|
3028
3035
|
#connectFailure;
|
|
3029
3036
|
#connectDiagnostic() {
|
|
3030
|
-
return { stage: this.#opened ? "hello" : "upgrade", opened: this.#opened, closeCode: null, closeReason: null, durationMs: Math.max(0, this.#clock().now() - (this.#connectStartedAt ?? this.#clock().now())) };
|
|
3037
|
+
return { stage: this.#opened ? "hello" : "upgrade", opened: this.#opened, closeCode: null, closeReason: null, durationMs: Math.max(0, this.#clock().now() - (this.#connectStartedAt ?? this.#clock().now())), attempt: this.#connectAttemptCount };
|
|
3031
3038
|
}
|
|
3032
3039
|
#reconnectAttempt = 0;
|
|
3033
3040
|
#episodeEndTimer;
|
|
@@ -3090,6 +3097,11 @@ var V1SyncClient = class {
|
|
|
3090
3097
|
async start() {
|
|
3091
3098
|
if (this.#started) return;
|
|
3092
3099
|
this.#started = true;
|
|
3100
|
+
this.#connectAttemptCount = 0;
|
|
3101
|
+
this.#socketCloses = [];
|
|
3102
|
+
this.#omittedSocketCloses = 0;
|
|
3103
|
+
this.#lastHeartbeatReplyAt = null;
|
|
3104
|
+
this.#connectFailure = void 0;
|
|
3093
3105
|
const startupGeneration = ++this.#startupGeneration;
|
|
3094
3106
|
let stored;
|
|
3095
3107
|
try {
|
|
@@ -3144,6 +3156,7 @@ var V1SyncClient = class {
|
|
|
3144
3156
|
return {
|
|
3145
3157
|
connection: {
|
|
3146
3158
|
...this.#phase,
|
|
3159
|
+
recoveryDiagnostic: { socketAttempts: this.#connectAttemptCount, socketCloses: this.#socketCloses.slice(), omittedSocketCloses: this.#omittedSocketCloses, lastHeartbeatReplyAt: this.#lastHeartbeatReplyAt },
|
|
3147
3160
|
...this.#connectStartedAt !== null && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering") ? { connectAttempt: this.#connectDiagnostic() } : {},
|
|
3148
3161
|
...this.#connectFailure && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering") ? { connectFailure: this.#connectFailure } : {},
|
|
3149
3162
|
...this.#phase.phase === "live" ? { noticeUnresponsive: this.#health.health === "recovering", health: this.#health.health } : {},
|
|
@@ -3288,9 +3301,10 @@ var V1SyncClient = class {
|
|
|
3288
3301
|
#connect() {
|
|
3289
3302
|
if (!this.#started || !this.#transportAvailable || this.#socket || this.#phase.phase === "terminal") return;
|
|
3290
3303
|
this.#phase = { phase: "connecting" };
|
|
3304
|
+
this.#connectAttemptCount += 1;
|
|
3291
3305
|
this.#connectStartedAt = this.#clock().now();
|
|
3292
3306
|
this.#opened = false;
|
|
3293
|
-
this.#connectFailure = void 0;
|
|
3307
|
+
if (this.#connectFailure?.opened) this.#connectFailure = void 0;
|
|
3294
3308
|
this.#emit();
|
|
3295
3309
|
this.#entryNegotiated = false;
|
|
3296
3310
|
let socket;
|
|
@@ -3299,7 +3313,7 @@ var V1SyncClient = class {
|
|
|
3299
3313
|
} catch {
|
|
3300
3314
|
this.#connectFailure = this.#connectDiagnostic();
|
|
3301
3315
|
this.#emit();
|
|
3302
|
-
throw new V1SyncError("Sync WebSocket could not be created", "
|
|
3316
|
+
throw new V1SyncError("Sync WebSocket could not be created", "sync_start_failed");
|
|
3303
3317
|
}
|
|
3304
3318
|
const connectionGeneration = ++this.#connectionGeneration;
|
|
3305
3319
|
this.#socket = socket;
|
|
@@ -3324,6 +3338,13 @@ var V1SyncClient = class {
|
|
|
3324
3338
|
this.#inbound = this.#inbound.then(() => this.#receive(socket, event.data));
|
|
3325
3339
|
};
|
|
3326
3340
|
socket.onclose = (event) => {
|
|
3341
|
+
if (this.#started) {
|
|
3342
|
+
this.#socketCloses.push({ code: event.code, wasClean: event.wasClean ?? null });
|
|
3343
|
+
if (this.#socketCloses.length > 50) {
|
|
3344
|
+
this.#socketCloses.shift();
|
|
3345
|
+
this.#omittedSocketCloses += 1;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3327
3348
|
if (socket === this.#socket && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering")) {
|
|
3328
3349
|
this.#connectFailure = { ...this.#connectDiagnostic(), closeCode: event.code, closeReason: safeSyncCloseReason(event.reason) };
|
|
3329
3350
|
this.#emit();
|
|
@@ -3341,7 +3362,7 @@ var V1SyncClient = class {
|
|
|
3341
3362
|
};
|
|
3342
3363
|
socket.onerror = () => {
|
|
3343
3364
|
if (socket === this.#socket && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering")) {
|
|
3344
|
-
this.#connectFailure
|
|
3365
|
+
this.#connectFailure ??= this.#connectDiagnostic();
|
|
3345
3366
|
this.#emit();
|
|
3346
3367
|
}
|
|
3347
3368
|
socket.close(CLIENT_RESTART_CLOSE_CODE, "transport error");
|
|
@@ -3494,6 +3515,7 @@ var V1SyncClient = class {
|
|
|
3494
3515
|
case "pong": {
|
|
3495
3516
|
this.#requireLive();
|
|
3496
3517
|
this.#missedHeartbeats = 0;
|
|
3518
|
+
this.#lastHeartbeatReplyAt = this.#now();
|
|
3497
3519
|
const previous = this.#health.health;
|
|
3498
3520
|
this.#health.observePong(this.#now());
|
|
3499
3521
|
if (previous !== this.#health.health) this.#emit();
|
|
@@ -4046,6 +4068,7 @@ function createV1SyncClient(options) {
|
|
|
4046
4068
|
}
|
|
4047
4069
|
|
|
4048
4070
|
export {
|
|
4071
|
+
syncConnectFailureCode,
|
|
4049
4072
|
formatSyncConnectDiagnostic,
|
|
4050
4073
|
UuidSchema,
|
|
4051
4074
|
ReactionValues,
|
|
@@ -32,6 +32,13 @@ export type ConnectionDiagnostic = {
|
|
|
32
32
|
readonly outcome?: ConnectionJoinTraceOutcome;
|
|
33
33
|
readonly durationMs?: number;
|
|
34
34
|
readonly sync?: import("../sync/connect-diagnostics").SyncConnectDiagnostic;
|
|
35
|
+
readonly recovery?: {
|
|
36
|
+
readonly reference: "connection.sync_recovery_exhausted" | "connection.media_recovery_exhausted";
|
|
37
|
+
readonly attempts: number;
|
|
38
|
+
readonly transport: import("../sync/connect-diagnostics").SyncRecoveryDiagnostic | null;
|
|
39
|
+
readonly lastHeartbeatReplyAgoMs: number | null;
|
|
40
|
+
readonly visibilityState: DocumentVisibilityState | null;
|
|
41
|
+
};
|
|
35
42
|
};
|
|
36
43
|
export declare class ConnectionDiagnostics {
|
|
37
44
|
#private;
|
|
@@ -41,6 +48,13 @@ export declare class ConnectionDiagnostics {
|
|
|
41
48
|
readonly onEvent?: (event: ConnectionDiagnostic) => void;
|
|
42
49
|
});
|
|
43
50
|
record(input: Omit<ConnectionDiagnostic, "timestamp">): void;
|
|
51
|
+
recordRecoveryExhausted(input: {
|
|
52
|
+
readonly state: ConnectionState;
|
|
53
|
+
readonly epoch: number;
|
|
54
|
+
readonly code: "sync_recovery_exhausted" | "media_recovery_exhausted";
|
|
55
|
+
readonly attempts: number;
|
|
56
|
+
readonly transport: import("../sync/connect-diagnostics").SyncRecoveryDiagnostic | null;
|
|
57
|
+
}): void;
|
|
44
58
|
snapshot(): readonly ConnectionDiagnostic[];
|
|
45
59
|
startSpan(input: {
|
|
46
60
|
readonly step: ConnectionJoinTraceStep;
|
package/dist/effect.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
makeSpaceClientCoreLayer,
|
|
18
18
|
makeSpaceClientCoreLayerFromServices,
|
|
19
19
|
makeSpaceStoreLayer
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-4ZUN55Z2.js";
|
|
21
21
|
import "./chunk-DWKTGBHR.js";
|
|
22
22
|
import {
|
|
23
23
|
APIKeyListSchema,
|
|
@@ -829,7 +829,7 @@ import {
|
|
|
829
829
|
WhiteboardStorageUnavailableErrorWireSchema,
|
|
830
830
|
createChalkEffectClient
|
|
831
831
|
} from "./chunk-GA3JDPFI.js";
|
|
832
|
-
import "./chunk-
|
|
832
|
+
import "./chunk-IMMT22US.js";
|
|
833
833
|
|
|
834
834
|
// src/space-client/effect.ts
|
|
835
835
|
import { Context, Effect, Exit, Layer, Scope } from "effect";
|
package/dist/generated/sync.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export declare const SyncProtocolMetadata: {
|
|
|
37
37
|
readonly accepts: readonly ["entered", "command", "live_target", "directed_request", "delivery_ack", "request_ack", "reaction_send", "chat_send", "chat_page_request", "chat_read_set", "chat_reaction_set", "ping"];
|
|
38
38
|
readonly heartbeat: {
|
|
39
39
|
readonly intervalMs: 20000;
|
|
40
|
-
readonly missedDeadlinesBeforeClose:
|
|
40
|
+
readonly missedDeadlinesBeforeClose: 5;
|
|
41
41
|
};
|
|
42
42
|
}, {
|
|
43
43
|
readonly id: "terminal";
|
|
@@ -11,6 +11,7 @@ export declare const WhiteboardV1ProtocolMetadata: {
|
|
|
11
11
|
readonly presentation: {
|
|
12
12
|
readonly name: "presentation_v1";
|
|
13
13
|
readonly exactFields: readonly ["name"];
|
|
14
|
+
readonly presenterExactFields: readonly ["name", "presenter"];
|
|
14
15
|
};
|
|
15
16
|
readonly view: {
|
|
16
17
|
readonly name: "view_v1";
|
|
@@ -258,6 +259,7 @@ export type WhiteboardV1SharedAppState = {
|
|
|
258
259
|
} | null;
|
|
259
260
|
export type WhiteboardV1PresentationExtension = {
|
|
260
261
|
readonly name: "presentation_v1";
|
|
262
|
+
readonly presenter?: true;
|
|
261
263
|
};
|
|
262
264
|
export type WhiteboardV1ViewExtension = {
|
|
263
265
|
readonly name: "view_v1";
|
|
@@ -388,6 +390,7 @@ export type WhiteboardV1ServerFrame = {
|
|
|
388
390
|
readonly revision: string;
|
|
389
391
|
readonly can_draw: boolean;
|
|
390
392
|
readonly presenting: boolean;
|
|
393
|
+
readonly presenter_id?: string | null;
|
|
391
394
|
readonly view_owner_id?: string;
|
|
392
395
|
} | {
|
|
393
396
|
readonly type: "snapshot_page";
|
|
@@ -451,6 +454,7 @@ export type WhiteboardV1ServerFrame = {
|
|
|
451
454
|
readonly scene_id: string;
|
|
452
455
|
readonly revision: string;
|
|
453
456
|
readonly presenting: boolean;
|
|
457
|
+
readonly presenter_id?: string | null;
|
|
454
458
|
} | {
|
|
455
459
|
readonly type: "reset_required";
|
|
456
460
|
readonly scene_id: string;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
createSpaceClient,
|
|
16
16
|
decodeWhiteboardV1ServerFrame,
|
|
17
17
|
encodeWhiteboardV1ClientFrame
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-4ZUN55Z2.js";
|
|
19
19
|
import {
|
|
20
20
|
CloudflareSFUClient,
|
|
21
21
|
CloudflareSFUError,
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
optimisticV1Control,
|
|
70
70
|
reactionFromFrame,
|
|
71
71
|
restoreV1Snapshot
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-IMMT22US.js";
|
|
73
73
|
|
|
74
74
|
// src/space-client/reload-rejoin.ts
|
|
75
75
|
import { Schema } from "effect";
|
|
@@ -115,6 +115,23 @@ function reloadRejoinAccess(access) {
|
|
|
115
115
|
const parsed = parseParsedAccessGrant(access);
|
|
116
116
|
return { tenantId: parsed.subject.tenantId, episodeId: parsed.subject.episodeId, participantId: parsed.subject.participantId, participantGeneration: parsed.subject.participantGeneration, mediaProof: parsed.media.token };
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// src/space-client/failure-detail.ts
|
|
120
|
+
var DEFAULT_FAILURE_DETAIL = "Something went wrong while joining. Try again.";
|
|
121
|
+
var FAILURE_DETAILS = {
|
|
122
|
+
"access.invalid": "Access to this call was rejected. Refresh the page and try again.",
|
|
123
|
+
"access.unavailable": "Couldn\u2019t get access to the call. Try again.",
|
|
124
|
+
"connection.sync_start_failed": "Couldn\u2019t reach the call service. Try again.",
|
|
125
|
+
"connection.sync_hello_timeout": "The call service did not respond in time. Try again.",
|
|
126
|
+
"connection.sync_recovery_exhausted": "You\u2019re disconnected from the call. Rejoin to continue.",
|
|
127
|
+
"connection.media_start_failed": "Audio and video could not connect. Try again.",
|
|
128
|
+
"connection.media_recovery_exhausted": "Audio and video could not reconnect. Try again.",
|
|
129
|
+
"environment.unsupported": "This device cannot start the call. Use an up-to-date browser over HTTPS, or update the app."
|
|
130
|
+
};
|
|
131
|
+
function getConnectionFailureDetail({ code, sync }) {
|
|
132
|
+
if (sync?.stage === "upgrade") return sync.closeCode !== null ? "The connection closed before the call service could be reached. Try again." : "A connection to the call service could not be opened. Try again.";
|
|
133
|
+
return (code && FAILURE_DETAILS[code]) ?? DEFAULT_FAILURE_DETAIL;
|
|
134
|
+
}
|
|
118
135
|
export {
|
|
119
136
|
ArrivalInvalidHandleError,
|
|
120
137
|
ArrivalUnavailableError,
|
|
@@ -166,6 +183,7 @@ export {
|
|
|
166
183
|
encodeV1ClientFrame,
|
|
167
184
|
encodeWhiteboardV1ClientFrame,
|
|
168
185
|
formatSyncConnectDiagnostic,
|
|
186
|
+
getConnectionFailureDetail,
|
|
169
187
|
optimisticV1Control,
|
|
170
188
|
parseCloudflareSFUPublicationID,
|
|
171
189
|
parseInviteCode,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ClientFailure, ErrorCode } from "./types";
|
|
2
|
+
/** Safe, actionable failure copy shared by web and native call screens. */
|
|
3
|
+
export declare function getConnectionFailureDetail({ code, sync }: {
|
|
4
|
+
readonly code?: ErrorCode;
|
|
5
|
+
readonly sync?: ClientFailure["sync"];
|
|
6
|
+
}): string;
|
|
@@ -203,6 +203,7 @@ export type WhiteboardSlice = {
|
|
|
203
203
|
readonly sceneId: string | null;
|
|
204
204
|
readonly revision: string | null;
|
|
205
205
|
readonly presenting: boolean;
|
|
206
|
+
readonly presenterId?: string | null;
|
|
206
207
|
readonly error: ChalkWhiteboardV1Failure | null;
|
|
207
208
|
};
|
|
208
209
|
/** Who can draw, as Sync last told this person. `canDraw` is false until the board subscription is live. */
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/** Transport facts only. No URLs, close reasons, payloads, or identities. */
|
|
2
|
+
export type SyncRecoveryDiagnostic = {
|
|
3
|
+
readonly socketAttempts: number;
|
|
4
|
+
readonly socketCloses: readonly {
|
|
5
|
+
readonly code: number;
|
|
6
|
+
readonly wasClean: boolean | null;
|
|
7
|
+
}[];
|
|
8
|
+
readonly omittedSocketCloses: number;
|
|
9
|
+
readonly lastHeartbeatReplyAt: number | null;
|
|
10
|
+
};
|
|
1
11
|
/** Browsers expose only upgrade/open and hello, never DNS/TCP/TLS progress. */
|
|
2
12
|
export type SyncConnectDiagnostic = {
|
|
3
13
|
readonly stage: "upgrade" | "hello";
|
|
@@ -5,6 +15,12 @@ export type SyncConnectDiagnostic = {
|
|
|
5
15
|
readonly closeCode: number | null;
|
|
6
16
|
readonly closeReason: string | null;
|
|
7
17
|
readonly durationMs: number;
|
|
18
|
+
/** One-based socket attempt since Sync start; absent in older SDK evidence. */
|
|
19
|
+
readonly attempt?: number;
|
|
20
|
+
/** Total sockets started when the join failed, including a pending replacement. */
|
|
21
|
+
readonly attempts?: number;
|
|
8
22
|
};
|
|
9
23
|
export declare function safeSyncCloseReason(reason: string | undefined): string | null;
|
|
24
|
+
/** Keep published error codes stable while separating upgrade from hello failures. */
|
|
25
|
+
export declare function syncConnectFailureCode(diagnostic: SyncConnectDiagnostic | undefined): "sync_start_failed" | "sync_hello_timeout";
|
|
10
26
|
export declare function formatSyncConnectDiagnostic(diagnostic: SyncConnectDiagnostic): string;
|
package/dist/sync/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export type ReactNativeAsyncStorage = {
|
|
|
7
7
|
export type ReactNativeWebSocketCloseEvent = {
|
|
8
8
|
readonly code?: unknown;
|
|
9
9
|
readonly reason?: unknown;
|
|
10
|
+
readonly wasClean?: unknown;
|
|
10
11
|
};
|
|
11
12
|
export type ReactNativeWebSocket = {
|
|
12
13
|
onopen: ((event: unknown) => void) | null;
|
package/dist/sync/types.d.ts
CHANGED
package/dist/sync/v1-types.d.ts
CHANGED
|
@@ -81,6 +81,7 @@ export type V1EpisodeSnapshot = {
|
|
|
81
81
|
readonly dialTimeoutCount?: number;
|
|
82
82
|
readonly connectAttempt?: import("./connect-diagnostics").SyncConnectDiagnostic;
|
|
83
83
|
readonly connectFailure?: import("./connect-diagnostics").SyncConnectDiagnostic;
|
|
84
|
+
readonly recoveryDiagnostic?: import("./connect-diagnostics").SyncRecoveryDiagnostic;
|
|
84
85
|
};
|
|
85
86
|
readonly participantId: string | null;
|
|
86
87
|
readonly participantGeneration: number | null;
|
|
@@ -179,6 +179,8 @@ export type ChalkWhiteboardSummary = {
|
|
|
179
179
|
readonly drawPolicy: ChalkWhiteboardDrawPolicy | null;
|
|
180
180
|
/** The host's per-person choices for people in the call. Empty unless this person is a host. */
|
|
181
181
|
readonly drawPermissions: readonly ChalkWhiteboardDrawPermission[];
|
|
182
|
+
/** The person who opened the current presentation; absent on older servers. */
|
|
183
|
+
readonly presenterId?: string | null;
|
|
182
184
|
readonly presenting: boolean;
|
|
183
185
|
readonly error: ChalkWhiteboardV1Failure | null;
|
|
184
186
|
};
|