@q9labsai/chalk-client 5.2.2 → 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-XPW3OCPO.js → chunk-4ZUN55Z2.js} +72 -20
- package/dist/{chunk-AIJ5Z6HA.js → chunk-IMMT22US.js} +19 -4
- 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.js +3 -3
- package/dist/space-client/types.d.ts +1 -0
- package/dist/sync/connect-diagnostics.d.ts +10 -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
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
formatSyncConnectDiagnostic,
|
|
21
21
|
scheduleSyncDialDeadline,
|
|
22
22
|
syncConnectFailureCode
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-IMMT22US.js";
|
|
24
24
|
|
|
25
25
|
// src/access/manager.ts
|
|
26
26
|
import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
|
|
@@ -333,7 +333,8 @@ var WhiteboardV1ProtocolMetadata = {
|
|
|
333
333
|
extensions: {
|
|
334
334
|
presentation: {
|
|
335
335
|
name: "presentation_v1",
|
|
336
|
-
exactFields: ["name"]
|
|
336
|
+
exactFields: ["name"],
|
|
337
|
+
presenterExactFields: ["name", "presenter"]
|
|
337
338
|
},
|
|
338
339
|
view: {
|
|
339
340
|
name: "view_v1",
|
|
@@ -655,7 +656,7 @@ function isExtensions(value) {
|
|
|
655
656
|
const drawPolicyLast = value.length > 0 && isExtension(value[value.length - 1], drawPolicy);
|
|
656
657
|
const base = drawPolicyLast ? value.slice(0, -1) : value;
|
|
657
658
|
if (base.length === 0) return drawPolicyLast;
|
|
658
|
-
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);
|
|
659
660
|
}
|
|
660
661
|
function isDrawPolicy(value) {
|
|
661
662
|
return typeof value === "string" && WhiteboardV1ProtocolMetadata.drawPolicies.some((policy) => policy === value);
|
|
@@ -664,6 +665,10 @@ function isDrawGrant(value) {
|
|
|
664
665
|
return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.drawAccess.grantExactFields) && isUuid(value.participant_id) && typeof value.can_draw === "boolean";
|
|
665
666
|
}
|
|
666
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
|
+
}
|
|
667
672
|
if ("view_owner_id" in value) {
|
|
668
673
|
const { view_owner_id, ...welcome } = value;
|
|
669
674
|
return isUuid(view_owner_id) && isWelcome(welcome);
|
|
@@ -671,6 +676,13 @@ function isWelcome(value) {
|
|
|
671
676
|
const exact = hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.welcome.exactFields) || hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.welcome.extendedExactFields) && typeof value.presenting === "boolean";
|
|
672
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";
|
|
673
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
|
+
}
|
|
674
686
|
function isServerFrame(value) {
|
|
675
687
|
if (!isRecord2(value)) return false;
|
|
676
688
|
switch (value.type) {
|
|
@@ -693,7 +705,7 @@ function isServerFrame(value) {
|
|
|
693
705
|
case "draw_access":
|
|
694
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);
|
|
695
707
|
case "presentation_updated":
|
|
696
|
-
return
|
|
708
|
+
return isPresentationUpdated(value);
|
|
697
709
|
case "reset_required":
|
|
698
710
|
return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.resetRequired.exactFields) && isUuid(value.scene_id) && typeof value.reason === "string" && WhiteboardV1ProtocolMetadata.resetReasons.some((reason) => reason === value.reason);
|
|
699
711
|
case "operation_error":
|
|
@@ -1066,7 +1078,8 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1066
1078
|
#presentationSupported = false;
|
|
1067
1079
|
#viewsSupported = false;
|
|
1068
1080
|
#presenting = false;
|
|
1069
|
-
#
|
|
1081
|
+
#presenterId = null;
|
|
1082
|
+
#hello = "presenter-view";
|
|
1070
1083
|
#viewSequence = 0;
|
|
1071
1084
|
#viewOwnerId = null;
|
|
1072
1085
|
#viewVersions = new ChalkWhiteboardViewVersions();
|
|
@@ -1119,7 +1132,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1119
1132
|
if (this.#started && this.#startPromise) return this.#startPromise;
|
|
1120
1133
|
this.#started = true;
|
|
1121
1134
|
globalThis.document?.addEventListener("visibilitychange", this.#visibilityChanged);
|
|
1122
|
-
this.#hello = "
|
|
1135
|
+
this.#hello = "presenter-view";
|
|
1123
1136
|
this.#rejectedHelloRetries = 0;
|
|
1124
1137
|
this.#viewsSupported = false;
|
|
1125
1138
|
this.#phase = "connecting";
|
|
@@ -1173,6 +1186,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1173
1186
|
this.#drawPolicy = null;
|
|
1174
1187
|
this.#drawPermissions = [];
|
|
1175
1188
|
this.#presenting = false;
|
|
1189
|
+
this.#presenterId = null;
|
|
1176
1190
|
this.#publishSummary("unsubscribed", null);
|
|
1177
1191
|
}
|
|
1178
1192
|
subscribe(listener) {
|
|
@@ -1489,6 +1503,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1489
1503
|
this.#sceneId = frame.scene_id;
|
|
1490
1504
|
this.#revision = frame.revision;
|
|
1491
1505
|
this.#presenting = frame.presenting;
|
|
1506
|
+
this.#presenterId = frame.presenter_id ?? null;
|
|
1492
1507
|
this.#publishSummary("ready", null);
|
|
1493
1508
|
return;
|
|
1494
1509
|
case "reset_required":
|
|
@@ -1561,7 +1576,13 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1561
1576
|
this.#drawPolicy = null;
|
|
1562
1577
|
this.#drawPermissions = [];
|
|
1563
1578
|
this.#presentationSupported = "presenting" in frame;
|
|
1564
|
-
|
|
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
|
+
}
|
|
1565
1586
|
const refused = !drawPolicyNegotiated(this.#hello) && this.#dropPolicyChanges();
|
|
1566
1587
|
this.#publishSummary("loading", refused ? policyUnavailable() : null);
|
|
1567
1588
|
this.#clearHeartbeat();
|
|
@@ -1865,7 +1886,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1865
1886
|
if (step === "fail") return false;
|
|
1866
1887
|
if (step === "step-down") return this.#stepDownHello();
|
|
1867
1888
|
if (step === "retry-hello") return this.#retryHello();
|
|
1868
|
-
if (phase === "live") this.#hello = "
|
|
1889
|
+
if (phase === "live") this.#hello = "presenter-view";
|
|
1869
1890
|
return true;
|
|
1870
1891
|
}
|
|
1871
1892
|
#stepDownHello() {
|
|
@@ -1989,6 +2010,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1989
2010
|
drawPolicy: this.#drawPolicy,
|
|
1990
2011
|
drawPermissions: this.#drawPermissions,
|
|
1991
2012
|
presenting: this.#presenting,
|
|
2013
|
+
presenterId: this.#presenterId,
|
|
1992
2014
|
error: this.#summaryError
|
|
1993
2015
|
};
|
|
1994
2016
|
}
|
|
@@ -2193,19 +2215,20 @@ function operationName(frame) {
|
|
|
2193
2215
|
function retryableOperationCode(code) {
|
|
2194
2216
|
return code === "unavailable" || code === "overloaded" || code === "rate_limited" || code === "storage_unavailable";
|
|
2195
2217
|
}
|
|
2196
|
-
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"];
|
|
2197
2219
|
function viewsNegotiated(level) {
|
|
2198
|
-
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";
|
|
2199
2221
|
}
|
|
2200
2222
|
function viewOriginsNegotiated(level) {
|
|
2201
|
-
return level === "owner-view" || level === "draw-policy" || level === "versioned-view";
|
|
2223
|
+
return level === "presenter-view" || level === "owner-view" || level === "draw-policy" || level === "versioned-view";
|
|
2202
2224
|
}
|
|
2203
2225
|
function drawPolicyNegotiated(level) {
|
|
2204
|
-
return level === "owner-view" || level === "draw-policy";
|
|
2226
|
+
return level === "presenter-view" || level === "owner-view" || level === "draw-policy";
|
|
2205
2227
|
}
|
|
2206
2228
|
function helloFrame(level, token, cursor) {
|
|
2207
2229
|
const hello = { type: "hello", protocol: "whiteboard-v1", token, cursor };
|
|
2208
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" }] };
|
|
2209
2232
|
if (level === "presentation") return { ...hello, extensions: [{ name: "presentation_v1" }] };
|
|
2210
2233
|
if (level === "view") return { ...hello, extensions: [{ name: "presentation_v1" }, { name: "view_v1" }] };
|
|
2211
2234
|
if (level === "versioned-view") return { ...hello, extensions: [{ name: "presentation_v1" }, { name: "view_v1", origins: true }] };
|
|
@@ -2518,6 +2541,22 @@ var ConnectionDiagnostics = class {
|
|
|
2518
2541
|
} catch {
|
|
2519
2542
|
}
|
|
2520
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
|
+
}
|
|
2521
2560
|
snapshot() {
|
|
2522
2561
|
return Object.freeze([...this.#events]);
|
|
2523
2562
|
}
|
|
@@ -3049,7 +3088,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3049
3088
|
const syncAttempt = { client: null, expiresAt: null, started: true };
|
|
3050
3089
|
while (model.epoch === epoch && active(model)) {
|
|
3051
3090
|
const plan = yield* recoveryPlan(model.recoveryQueued ?? kind);
|
|
3052
|
-
|
|
3091
|
+
let attempts = 0;
|
|
3092
|
+
const outcome = yield* attemptRecovery(plan, mediaAttempt, syncAttempt, () => attempts++).pipe(
|
|
3053
3093
|
Effect2.match({
|
|
3054
3094
|
onSuccess: (attempt) => ({ attempt, failure: null }),
|
|
3055
3095
|
onFailure: (failure2) => ({ attempt: null, failure: failure2 })
|
|
@@ -3067,9 +3107,18 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3067
3107
|
yield* enqueue(
|
|
3068
3108
|
Effect2.gen(function* () {
|
|
3069
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
|
+
}
|
|
3070
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
|
+
}
|
|
3071
3121
|
model.failure = toFailure(lifecycleFailure(code, true, "Connection interrupted. Reconnecting in the background."));
|
|
3072
|
-
diagnostics.record({ event: "recovery_exhausted", state: model.state, epoch, code });
|
|
3073
3122
|
yield* publish();
|
|
3074
3123
|
})
|
|
3075
3124
|
);
|
|
@@ -3090,14 +3139,14 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3090
3139
|
delays: options.recovery?.backoffMs?.length ? options.recovery.backoffMs : [100, 250, 500]
|
|
3091
3140
|
}))
|
|
3092
3141
|
);
|
|
3093
|
-
const attemptRecovery = (plan, mediaAttempt2, syncAttempt) => Effect2.gen(function* () {
|
|
3142
|
+
const attemptRecovery = (plan, mediaAttempt2, syncAttempt, onAttempt) => Effect2.gen(function* () {
|
|
3094
3143
|
for (let attempt = 1; attempt <= plan.attempts; attempt += 1) {
|
|
3095
3144
|
const remaining = plan.deadline - (yield* Clock2.currentTimeMillis);
|
|
3096
3145
|
if (remaining <= 0) return null;
|
|
3146
|
+
onAttempt();
|
|
3097
3147
|
diagnostics.record({ event: "recovery_attempt", state: model.state, epoch: model.epoch, attempt });
|
|
3098
3148
|
const failure2 = yield* recoveryOperation(mediaAttempt2, syncAttempt).pipe(
|
|
3099
|
-
|
|
3100
|
-
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")),
|
|
3101
3150
|
Effect2.match({ onSuccess: () => null, onFailure: (cause) => cause })
|
|
3102
3151
|
);
|
|
3103
3152
|
if (failure2 === null) return attempt;
|
|
@@ -6237,6 +6286,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6237
6286
|
drawPolicy: summary.drawPolicy,
|
|
6238
6287
|
drawPermissions: Object.freeze(summary.drawPermissions.map((permission) => Object.freeze({ ...permission }))),
|
|
6239
6288
|
presenting: summary.presenting,
|
|
6289
|
+
presenterId: summary.presenterId ?? null,
|
|
6240
6290
|
error: summary.error ? Object.freeze({ ...summary.error }) : null
|
|
6241
6291
|
});
|
|
6242
6292
|
this.#publish();
|
|
@@ -6255,7 +6305,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6255
6305
|
#publish() {
|
|
6256
6306
|
const slice = Object.freeze({
|
|
6257
6307
|
open: this.#summary.status !== "unsubscribed",
|
|
6258
|
-
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 }),
|
|
6259
6309
|
access: Object.freeze({ canDraw: this.#summary.canDraw, policy: this.#summary.drawPolicy, permissions: this.#summary.drawPermissions })
|
|
6260
6310
|
});
|
|
6261
6311
|
const current = this.#store.getSnapshot().whiteboard;
|
|
@@ -6264,7 +6314,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6264
6314
|
}
|
|
6265
6315
|
};
|
|
6266
6316
|
function sameWhiteboard(left, right) {
|
|
6267
|
-
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);
|
|
6268
6318
|
}
|
|
6269
6319
|
function sameAccess(left, right) {
|
|
6270
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);
|
|
@@ -6359,7 +6409,9 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
|
|
|
6359
6409
|
diagnostics: {
|
|
6360
6410
|
onEvent: (event) => {
|
|
6361
6411
|
platform.onConnectionDiagnostic?.(event);
|
|
6362
|
-
|
|
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);
|
|
6363
6415
|
}
|
|
6364
6416
|
}
|
|
6365
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) {
|
|
@@ -339,7 +339,7 @@ var SyncProtocolMetadata = {
|
|
|
339
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"],
|
|
340
340
|
heartbeat: {
|
|
341
341
|
intervalMs: 2e4,
|
|
342
|
-
missedDeadlinesBeforeClose:
|
|
342
|
+
missedDeadlinesBeforeClose: 5
|
|
343
343
|
}
|
|
344
344
|
},
|
|
345
345
|
{
|
|
@@ -3028,6 +3028,9 @@ var V1SyncClient = class {
|
|
|
3028
3028
|
#dialTimeoutCount = 0;
|
|
3029
3029
|
#connectStartedAt = null;
|
|
3030
3030
|
#connectAttemptCount = 0;
|
|
3031
|
+
#socketCloses = [];
|
|
3032
|
+
#omittedSocketCloses = 0;
|
|
3033
|
+
#lastHeartbeatReplyAt = null;
|
|
3031
3034
|
#opened = false;
|
|
3032
3035
|
#connectFailure;
|
|
3033
3036
|
#connectDiagnostic() {
|
|
@@ -3095,6 +3098,9 @@ var V1SyncClient = class {
|
|
|
3095
3098
|
if (this.#started) return;
|
|
3096
3099
|
this.#started = true;
|
|
3097
3100
|
this.#connectAttemptCount = 0;
|
|
3101
|
+
this.#socketCloses = [];
|
|
3102
|
+
this.#omittedSocketCloses = 0;
|
|
3103
|
+
this.#lastHeartbeatReplyAt = null;
|
|
3098
3104
|
this.#connectFailure = void 0;
|
|
3099
3105
|
const startupGeneration = ++this.#startupGeneration;
|
|
3100
3106
|
let stored;
|
|
@@ -3150,6 +3156,7 @@ var V1SyncClient = class {
|
|
|
3150
3156
|
return {
|
|
3151
3157
|
connection: {
|
|
3152
3158
|
...this.#phase,
|
|
3159
|
+
recoveryDiagnostic: { socketAttempts: this.#connectAttemptCount, socketCloses: this.#socketCloses.slice(), omittedSocketCloses: this.#omittedSocketCloses, lastHeartbeatReplyAt: this.#lastHeartbeatReplyAt },
|
|
3153
3160
|
...this.#connectStartedAt !== null && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering") ? { connectAttempt: this.#connectDiagnostic() } : {},
|
|
3154
3161
|
...this.#connectFailure && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering") ? { connectFailure: this.#connectFailure } : {},
|
|
3155
3162
|
...this.#phase.phase === "live" ? { noticeUnresponsive: this.#health.health === "recovering", health: this.#health.health } : {},
|
|
@@ -3331,6 +3338,13 @@ var V1SyncClient = class {
|
|
|
3331
3338
|
this.#inbound = this.#inbound.then(() => this.#receive(socket, event.data));
|
|
3332
3339
|
};
|
|
3333
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
|
+
}
|
|
3334
3348
|
if (socket === this.#socket && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering")) {
|
|
3335
3349
|
this.#connectFailure = { ...this.#connectDiagnostic(), closeCode: event.code, closeReason: safeSyncCloseReason(event.reason) };
|
|
3336
3350
|
this.#emit();
|
|
@@ -3501,6 +3515,7 @@ var V1SyncClient = class {
|
|
|
3501
3515
|
case "pong": {
|
|
3502
3516
|
this.#requireLive();
|
|
3503
3517
|
this.#missedHeartbeats = 0;
|
|
3518
|
+
this.#lastHeartbeatReplyAt = this.#now();
|
|
3504
3519
|
const previous = this.#health.health;
|
|
3505
3520
|
this.#health.observePong(this.#now());
|
|
3506
3521
|
if (previous !== this.#health.health) this.#emit();
|
|
@@ -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.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";
|
|
@@ -123,7 +123,7 @@ var FAILURE_DETAILS = {
|
|
|
123
123
|
"access.unavailable": "Couldn\u2019t get access to the call. Try again.",
|
|
124
124
|
"connection.sync_start_failed": "Couldn\u2019t reach the call service. Try again.",
|
|
125
125
|
"connection.sync_hello_timeout": "The call service did not respond in time. Try again.",
|
|
126
|
-
"connection.sync_recovery_exhausted": "
|
|
126
|
+
"connection.sync_recovery_exhausted": "You\u2019re disconnected from the call. Rejoin to continue.",
|
|
127
127
|
"connection.media_start_failed": "Audio and video could not connect. Try again.",
|
|
128
128
|
"connection.media_recovery_exhausted": "Audio and video could not reconnect. Try again.",
|
|
129
129
|
"environment.unsupported": "This device cannot start the call. Use an up-to-date browser over HTTPS, or update the app."
|
|
@@ -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";
|
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
|
};
|