@q9labsai/chalk-client 5.2.2 → 5.2.4
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/README.md +1 -1
- package/dist/{chunk-DWKTGBHR.js → chunk-3ASKGMD3.js} +6 -1
- package/dist/{chunk-AIJ5Z6HA.js → chunk-5YPQNMJE.js} +30 -4
- package/dist/{chunk-GA3JDPFI.js → chunk-AZONR3Q3.js} +106 -0
- package/dist/{chunk-XPW3OCPO.js → chunk-LDPC2JZB.js} +258 -40
- package/dist/{chunk-6LNRDKJF.js → chunk-SYUGRHZ5.js} +1 -1
- package/dist/client.d.ts +60 -0
- package/dist/connection/dependencies.d.ts +6 -0
- package/dist/connection/diagnostics.d.ts +14 -0
- package/dist/connection/report.d.ts +21 -0
- package/dist/effect.js +14 -4
- package/dist/generated/http-api.d.ts +59 -0
- package/dist/generated/schemas.d.ts +98 -0
- package/dist/generated/sync.d.ts +1 -1
- package/dist/generated/whiteboard-v1.d.ts +4 -0
- package/dist/index.js +6 -6
- package/dist/media/index.js +1 -1
- package/dist/media/types.d.ts +3 -0
- package/dist/public-invites/index.js +2 -2
- 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 +4 -0
- package/dist/transport-diagnostics.d.ts +3 -0
- package/dist/whiteboard/types.d.ts +2 -0
- package/package.json +2 -2
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
CloudflareSFUClient,
|
|
3
3
|
createCloudflareSFUHTTPTransport,
|
|
4
4
|
describeMediaCaptureError
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3ASKGMD3.js";
|
|
6
6
|
import {
|
|
7
7
|
AccessGrantError,
|
|
8
8
|
requireParsedAccessGrant
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-AZONR3Q3.js";
|
|
10
10
|
import {
|
|
11
11
|
ChatAttachmentMimeTypeSchema,
|
|
12
12
|
ChatReactionSetFrameSchema,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
formatSyncConnectDiagnostic,
|
|
21
21
|
scheduleSyncDialDeadline,
|
|
22
22
|
syncConnectFailureCode
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-5YPQNMJE.js";
|
|
24
24
|
|
|
25
25
|
// src/access/manager.ts
|
|
26
26
|
import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
|
|
@@ -59,7 +59,7 @@ var makeConnectionAccessLayer = (provider, refreshWindowMs = DEFAULT_REFRESH_WIN
|
|
|
59
59
|
automaticMediaReplacementListeners.add(listener);
|
|
60
60
|
return () => automaticMediaReplacementListeners.delete(listener);
|
|
61
61
|
}),
|
|
62
|
-
requiresRefresh: millisecondsUntilRefresh.pipe(Effect.map((
|
|
62
|
+
requiresRefresh: millisecondsUntilRefresh.pipe(Effect.map((milliseconds2) => milliseconds2 === null || milliseconds2 === 0)),
|
|
63
63
|
millisecondsUntilRefresh,
|
|
64
64
|
clear: SynchronizedRef.set(state, { access: null })
|
|
65
65
|
};
|
|
@@ -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
|
}
|
|
@@ -2134,7 +2156,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
2134
2156
|
return this.#options.clock ?? {
|
|
2135
2157
|
now: Date.now,
|
|
2136
2158
|
monotonicNow: () => globalThis.performance.now(),
|
|
2137
|
-
setTimeout: (callback,
|
|
2159
|
+
setTimeout: (callback, milliseconds2) => globalThis.setTimeout(callback, milliseconds2),
|
|
2138
2160
|
clearTimeout: (handle) => globalThis.clearTimeout(handle)
|
|
2139
2161
|
};
|
|
2140
2162
|
}
|
|
@@ -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 }] };
|
|
@@ -2265,6 +2288,124 @@ function createChalkWhiteboardV1Client(options) {
|
|
|
2265
2288
|
});
|
|
2266
2289
|
}
|
|
2267
2290
|
|
|
2291
|
+
// src/connection/report.ts
|
|
2292
|
+
var MAX_EVENTS = 64;
|
|
2293
|
+
var MAX_BYTES = 32768;
|
|
2294
|
+
var MAX_MS = 2147483647;
|
|
2295
|
+
var COOLDOWN_MS = 6e4;
|
|
2296
|
+
var participantCooldowns = /* @__PURE__ */ new Map();
|
|
2297
|
+
var milliseconds = (value) => Math.max(0, Math.min(MAX_MS, Math.round(Number.isFinite(value) ? value : 0)));
|
|
2298
|
+
var optionalMilliseconds = (value) => value == null ? void 0 : milliseconds(value);
|
|
2299
|
+
function reserveParticipantReport(access, now) {
|
|
2300
|
+
const key = `${access.subject.tenantId}:${access.subject.participantId}`;
|
|
2301
|
+
for (const [participant, until] of participantCooldowns) if (until <= now) participantCooldowns.delete(participant);
|
|
2302
|
+
if (participantCooldowns.has(key)) return false;
|
|
2303
|
+
if (participantCooldowns.size >= 128) participantCooldowns.delete(participantCooldowns.keys().next().value ?? "");
|
|
2304
|
+
participantCooldowns.set(key, now + COOLDOWN_MS);
|
|
2305
|
+
return true;
|
|
2306
|
+
}
|
|
2307
|
+
var ConnectionReporter = class {
|
|
2308
|
+
#events = [];
|
|
2309
|
+
#startedAt;
|
|
2310
|
+
#options;
|
|
2311
|
+
#pending = null;
|
|
2312
|
+
#unsubscribe;
|
|
2313
|
+
#controller = null;
|
|
2314
|
+
#retryOnReconnect = false;
|
|
2315
|
+
#disposed = false;
|
|
2316
|
+
constructor(options) {
|
|
2317
|
+
this.#options = options;
|
|
2318
|
+
this.#startedAt = options.now();
|
|
2319
|
+
}
|
|
2320
|
+
recordDiagnostic(diagnostic) {
|
|
2321
|
+
this.#record({
|
|
2322
|
+
event: diagnostic.event,
|
|
2323
|
+
elapsed_ms: this.#elapsed(),
|
|
2324
|
+
state: diagnostic.state,
|
|
2325
|
+
step: diagnostic.step,
|
|
2326
|
+
outcome: diagnostic.outcome,
|
|
2327
|
+
code: diagnostic.code,
|
|
2328
|
+
attempt: optionalMilliseconds(diagnostic.attempt),
|
|
2329
|
+
duration_ms: optionalMilliseconds(diagnostic.sync?.durationMs ?? diagnostic.durationMs),
|
|
2330
|
+
close_code: diagnostic.sync?.closeCode ?? void 0,
|
|
2331
|
+
heartbeat_gap_ms: optionalMilliseconds(diagnostic.recovery?.lastHeartbeatReplyAgoMs)
|
|
2332
|
+
});
|
|
2333
|
+
if (diagnostic.event === "recovery_exhausted") this.#report("recovery_exhausted");
|
|
2334
|
+
else if (diagnostic.event === "connection_failed") this.#report(diagnostic.state === "joining" ? "join_failed" : "connection_lost");
|
|
2335
|
+
}
|
|
2336
|
+
recordTransport(event) {
|
|
2337
|
+
this.#record({
|
|
2338
|
+
event: event.event,
|
|
2339
|
+
elapsed_ms: this.#elapsed(),
|
|
2340
|
+
...event.phase ? { phase: event.phase } : {},
|
|
2341
|
+
...event.attempt == null ? {} : { attempt: milliseconds(event.attempt) },
|
|
2342
|
+
...event.duration_ms == null ? {} : { duration_ms: milliseconds(event.duration_ms) },
|
|
2343
|
+
...event.heartbeat_gap_ms == null ? {} : { heartbeat_gap_ms: milliseconds(event.heartbeat_gap_ms) },
|
|
2344
|
+
...event.close_code == null ? {} : { close_code: event.close_code },
|
|
2345
|
+
...event.http_status == null ? {} : { http_status: event.http_status }
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
dispose() {
|
|
2349
|
+
this.#disposed = true;
|
|
2350
|
+
this.#controller?.abort();
|
|
2351
|
+
this.#stopListening();
|
|
2352
|
+
this.#pending = null;
|
|
2353
|
+
this.#events.length = 0;
|
|
2354
|
+
}
|
|
2355
|
+
get traceparent() {
|
|
2356
|
+
return this.#options.traceparent;
|
|
2357
|
+
}
|
|
2358
|
+
#elapsed() {
|
|
2359
|
+
return milliseconds(this.#options.now() - this.#startedAt);
|
|
2360
|
+
}
|
|
2361
|
+
#record(event) {
|
|
2362
|
+
if (this.#disposed) return;
|
|
2363
|
+
this.#events.push(event);
|
|
2364
|
+
if (this.#events.length > MAX_EVENTS) this.#events.shift();
|
|
2365
|
+
}
|
|
2366
|
+
#report(trigger) {
|
|
2367
|
+
if (this.#disposed || this.#pending) return;
|
|
2368
|
+
const access = this.#options.access();
|
|
2369
|
+
if (!access) return;
|
|
2370
|
+
if (!reserveParticipantReport(access, this.#options.now())) return;
|
|
2371
|
+
const events = this.#events.slice();
|
|
2372
|
+
const report = { version: 1, trigger, events };
|
|
2373
|
+
while (events.length && new TextEncoder().encode(JSON.stringify(report)).byteLength > MAX_BYTES) events.shift();
|
|
2374
|
+
if (!events.length) return;
|
|
2375
|
+
this.#pending = { access, report, attempts: 0 };
|
|
2376
|
+
this.#unsubscribe = this.#options.subscribeOnline(() => {
|
|
2377
|
+
this.#retryOnReconnect = true;
|
|
2378
|
+
void this.#flush();
|
|
2379
|
+
});
|
|
2380
|
+
void this.#flush();
|
|
2381
|
+
}
|
|
2382
|
+
async #flush() {
|
|
2383
|
+
const pending = this.#pending;
|
|
2384
|
+
if (!pending || this.#controller || this.#disposed || !this.#options.online()) return;
|
|
2385
|
+
this.#retryOnReconnect = false;
|
|
2386
|
+
const controller = new AbortController();
|
|
2387
|
+
this.#controller = controller;
|
|
2388
|
+
pending.attempts += 1;
|
|
2389
|
+
const timer = globalThis.setTimeout(() => controller.abort(), 5e3);
|
|
2390
|
+
try {
|
|
2391
|
+
await this.#options.send(pending.access, pending.report, controller.signal, this.traceparent);
|
|
2392
|
+
this.#pending = null;
|
|
2393
|
+
} catch {
|
|
2394
|
+
if (pending.attempts >= 2) this.#pending = null;
|
|
2395
|
+
} finally {
|
|
2396
|
+
globalThis.clearTimeout(timer);
|
|
2397
|
+
this.#controller = null;
|
|
2398
|
+
if (!this.#pending) this.#stopListening();
|
|
2399
|
+
if (this.#retryOnReconnect) void this.#flush();
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
#stopListening() {
|
|
2403
|
+
this.#unsubscribe?.();
|
|
2404
|
+
this.#unsubscribe = void 0;
|
|
2405
|
+
this.#retryOnReconnect = false;
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
|
|
2268
2409
|
// src/connection/types.ts
|
|
2269
2410
|
var ConnectionError = class extends Error {
|
|
2270
2411
|
code;
|
|
@@ -2336,9 +2477,34 @@ function createDefaultConnectionDependencies(options) {
|
|
|
2336
2477
|
return {
|
|
2337
2478
|
clock: {
|
|
2338
2479
|
now: () => Date.now(),
|
|
2339
|
-
setTimeout: (callback,
|
|
2480
|
+
setTimeout: (callback, milliseconds2) => globalThis.setTimeout(callback, milliseconds2),
|
|
2340
2481
|
clearTimeout: (handle) => globalThis.clearTimeout(handle)
|
|
2341
2482
|
},
|
|
2483
|
+
createConnectionReporter: (access) => {
|
|
2484
|
+
const random = globalThis.crypto;
|
|
2485
|
+
if (!random?.randomUUID) return void 0;
|
|
2486
|
+
return new ConnectionReporter({
|
|
2487
|
+
traceparent: `00-${random.randomUUID().replaceAll("-", "")}-${random.randomUUID().replaceAll("-", "").slice(0, 16)}-01`,
|
|
2488
|
+
now: () => performance.now(),
|
|
2489
|
+
access,
|
|
2490
|
+
online: () => globalThis.navigator?.onLine !== false,
|
|
2491
|
+
subscribeOnline: (listener) => {
|
|
2492
|
+
globalThis.window?.addEventListener("online", listener);
|
|
2493
|
+
return () => globalThis.window?.removeEventListener("online", listener);
|
|
2494
|
+
},
|
|
2495
|
+
send: async (grant, report, signal, traceparent) => {
|
|
2496
|
+
const response = await fetch(`${options.apiBaseURL.replace(/\/$/, "")}/v1/tenants/${encodeURIComponent(grant.subject.tenantId)}/connection-reports`, {
|
|
2497
|
+
method: "POST",
|
|
2498
|
+
headers: { Authorization: `Bearer ${grant.media.token}`, "Content-Type": "application/json", traceparent },
|
|
2499
|
+
body: JSON.stringify(report),
|
|
2500
|
+
signal,
|
|
2501
|
+
keepalive: true,
|
|
2502
|
+
credentials: "omit"
|
|
2503
|
+
});
|
|
2504
|
+
await response.body?.cancel();
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
},
|
|
2342
2508
|
mediaDevices: createBrowserMediaDevices(),
|
|
2343
2509
|
cancelPendingAdmission: async (access, signal) => {
|
|
2344
2510
|
const { tenantId, spaceId, episodeId, participantId } = access.subject;
|
|
@@ -2403,6 +2569,8 @@ function createMediaClient(apiBaseURL, input) {
|
|
|
2403
2569
|
transport: createCloudflareSFUHTTPTransport({
|
|
2404
2570
|
apiBaseURL,
|
|
2405
2571
|
credential: input.credential,
|
|
2572
|
+
onDiagnostic: input.onTransportDiagnostic,
|
|
2573
|
+
traceparent: input.traceparent,
|
|
2406
2574
|
tenantId: subject.tenantId,
|
|
2407
2575
|
spaceId: subject.spaceId,
|
|
2408
2576
|
episodeId: subject.episodeId,
|
|
@@ -2444,6 +2612,8 @@ function bindConnectionMediaClient(client, restart) {
|
|
|
2444
2612
|
function createSyncClient(syncURL, input) {
|
|
2445
2613
|
return createV1SyncClient({
|
|
2446
2614
|
url: syncURL,
|
|
2615
|
+
onDiagnostic: input.onTransportDiagnostic,
|
|
2616
|
+
traceparent: input.traceparent,
|
|
2447
2617
|
token: input.token,
|
|
2448
2618
|
mediaPlane: input.media,
|
|
2449
2619
|
collaboration: { name: "collaboration_v2", chatCursor: { afterSequence: null, retainedFloorSequence: null } },
|
|
@@ -2475,7 +2645,7 @@ function whiteboardURL(syncURL) {
|
|
|
2475
2645
|
}
|
|
2476
2646
|
|
|
2477
2647
|
// src/foreground-deadline.ts
|
|
2478
|
-
function scheduleForegroundDeadline(clock,
|
|
2648
|
+
function scheduleForegroundDeadline(clock, milliseconds2, expired) {
|
|
2479
2649
|
let timer;
|
|
2480
2650
|
let cancelled = false;
|
|
2481
2651
|
const arm = () => {
|
|
@@ -2483,9 +2653,9 @@ function scheduleForegroundDeadline(clock, milliseconds, expired) {
|
|
|
2483
2653
|
timer = clock.setTimeout(() => {
|
|
2484
2654
|
if (cancelled) return;
|
|
2485
2655
|
const elapsed = clock.now() - started;
|
|
2486
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden" || elapsed >
|
|
2656
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden" || elapsed > milliseconds2 + 2e3 || elapsed < 0) arm();
|
|
2487
2657
|
else expired();
|
|
2488
|
-
},
|
|
2658
|
+
}, milliseconds2);
|
|
2489
2659
|
};
|
|
2490
2660
|
arm();
|
|
2491
2661
|
return () => {
|
|
@@ -2518,6 +2688,22 @@ var ConnectionDiagnostics = class {
|
|
|
2518
2688
|
} catch {
|
|
2519
2689
|
}
|
|
2520
2690
|
}
|
|
2691
|
+
recordRecoveryExhausted(input) {
|
|
2692
|
+
const { state, epoch, code, attempts, transport } = input;
|
|
2693
|
+
this.record({
|
|
2694
|
+
event: "recovery_exhausted",
|
|
2695
|
+
state,
|
|
2696
|
+
epoch,
|
|
2697
|
+
code,
|
|
2698
|
+
recovery: {
|
|
2699
|
+
reference: `connection.${code}`,
|
|
2700
|
+
attempts,
|
|
2701
|
+
transport,
|
|
2702
|
+
lastHeartbeatReplyAgoMs: transport?.lastHeartbeatReplyAt == null ? null : Math.max(0, this.#now() - transport.lastHeartbeatReplyAt),
|
|
2703
|
+
visibilityState: typeof document === "undefined" ? null : document.visibilityState
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2521
2707
|
snapshot() {
|
|
2522
2708
|
return Object.freeze([...this.#events]);
|
|
2523
2709
|
}
|
|
@@ -2555,6 +2741,7 @@ var MEDIA_RECOVERY_TIMEOUT_MS = 6e4;
|
|
|
2555
2741
|
var START_TIMEOUT_MS = 3e4;
|
|
2556
2742
|
var LEAVE_TIMEOUT_MS = 5e3;
|
|
2557
2743
|
var RECOVERY_BUDGET_MS = 1e4;
|
|
2744
|
+
var SYNC_RECOVERY_BUDGET_MS = MEDIA_RECOVERY_TIMEOUT_MS;
|
|
2558
2745
|
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
2559
2746
|
var REFRESH_RETRY_MS = 5e3;
|
|
2560
2747
|
var RECONNECT_GRACE_MS = 250;
|
|
@@ -2595,7 +2782,16 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2595
2782
|
const clock = yield* Clock2.Clock;
|
|
2596
2783
|
const context = yield* Effect2.context();
|
|
2597
2784
|
const serviceScope = yield* Effect2.scope;
|
|
2598
|
-
const
|
|
2785
|
+
const reporter = platform.createConnectionReporter?.(access.currentUnsafe);
|
|
2786
|
+
const diagnostics = new ConnectionDiagnostics({
|
|
2787
|
+
now: () => clock.currentTimeMillisUnsafe(),
|
|
2788
|
+
...options.diagnostics,
|
|
2789
|
+
onEvent: (event) => {
|
|
2790
|
+
reporter?.recordDiagnostic(event);
|
|
2791
|
+
options.diagnostics?.onEvent?.(event);
|
|
2792
|
+
}
|
|
2793
|
+
});
|
|
2794
|
+
const onTransportDiagnostic = (event) => reporter?.recordTransport(event);
|
|
2599
2795
|
const snapshotRef = yield* SubscriptionRef.make(idleSnapshot());
|
|
2600
2796
|
const queue = yield* Queue.unbounded();
|
|
2601
2797
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -2638,10 +2834,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2638
2834
|
};
|
|
2639
2835
|
const mediaAttempt = { expiresAt: null };
|
|
2640
2836
|
const foreign7 = (operation, code = "internal_error", message = "A browser transport operation failed") => Effect2.tryPromise({ try: operation, catch: (cause) => accessRejected(cause) ? lifecycleFailure("invalid_access", false, "Access was rejected", cause) : lifecycleFailure(code, true, message, cause) });
|
|
2641
|
-
const withinForegroundBudget = (effect,
|
|
2837
|
+
const withinForegroundBudget = (effect, milliseconds2, failure2) => Effect2.raceFirst(
|
|
2642
2838
|
effect,
|
|
2643
2839
|
Effect2.callback((resume) => {
|
|
2644
|
-
const cancel = scheduleForegroundDeadline(platform.clock,
|
|
2840
|
+
const cancel = scheduleForegroundDeadline(platform.clock, milliseconds2, () => resume(Effect2.fail(typeof failure2 === "function" ? failure2() : failure2)));
|
|
2645
2841
|
return Effect2.sync(cancel);
|
|
2646
2842
|
})
|
|
2647
2843
|
);
|
|
@@ -2702,10 +2898,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2702
2898
|
const scheduleRefresh = (delay) => Effect2.gen(function* () {
|
|
2703
2899
|
yield* stopRefresh();
|
|
2704
2900
|
if (!active(model)) return;
|
|
2705
|
-
const
|
|
2901
|
+
const milliseconds2 = delay ?? (yield* access.millisecondsUntilRefresh) ?? 0;
|
|
2706
2902
|
const scope = model.activeScope;
|
|
2707
2903
|
if (!scope) return;
|
|
2708
|
-
model.refreshFiber = yield* Effect2.forkIn(Effect2.sleep(Math.max(0,
|
|
2904
|
+
model.refreshFiber = yield* Effect2.forkIn(Effect2.sleep(Math.max(0, milliseconds2)).pipe(Effect2.andThen(enqueue(refreshAccess())), Effect2.ignore), scope);
|
|
2709
2905
|
});
|
|
2710
2906
|
const refreshAccess = () => Effect2.gen(function* () {
|
|
2711
2907
|
const scope = model.activeScope;
|
|
@@ -2884,6 +3080,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2884
3080
|
if (media === model.media) mediaAttempt.expiresAt = platform.clock.now() + MEDIA_RECOVERY_TIMEOUT_MS;
|
|
2885
3081
|
return replacement.media;
|
|
2886
3082
|
}),
|
|
3083
|
+
onTransportDiagnostic,
|
|
3084
|
+
traceparent: reporter?.traceparent,
|
|
2887
3085
|
onFailure: () => enqueueBackground(handleMediaFailure()),
|
|
2888
3086
|
onScreenEnded: () => enqueueBackground(notifyScreenEnded())
|
|
2889
3087
|
}),
|
|
@@ -2894,7 +3092,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2894
3092
|
const sync = yield* trace(
|
|
2895
3093
|
"create_sync_client",
|
|
2896
3094
|
Effect2.try({
|
|
2897
|
-
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media }),
|
|
3095
|
+
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media, onTransportDiagnostic, traceparent: reporter?.traceparent }),
|
|
2898
3096
|
catch: (cause) => accessRejected(cause) ? lifecycleFailure("invalid_access", false, "Access was rejected", cause) : lifecycleFailure("sync_start_failed", true, "The sync layer could not start", cause)
|
|
2899
3097
|
})
|
|
2900
3098
|
);
|
|
@@ -3049,7 +3247,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3049
3247
|
const syncAttempt = { client: null, expiresAt: null, started: true };
|
|
3050
3248
|
while (model.epoch === epoch && active(model)) {
|
|
3051
3249
|
const plan = yield* recoveryPlan(model.recoveryQueued ?? kind);
|
|
3052
|
-
|
|
3250
|
+
let attempts = 0;
|
|
3251
|
+
const outcome = yield* attemptRecovery(plan, mediaAttempt, syncAttempt, () => attempts++).pipe(
|
|
3053
3252
|
Effect2.match({
|
|
3054
3253
|
onSuccess: (attempt) => ({ attempt, failure: null }),
|
|
3055
3254
|
onFailure: (failure2) => ({ attempt: null, failure: failure2 })
|
|
@@ -3067,9 +3266,18 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3067
3266
|
yield* enqueue(
|
|
3068
3267
|
Effect2.gen(function* () {
|
|
3069
3268
|
if (model.epoch !== epoch || !active(model)) return;
|
|
3269
|
+
if (plan.kind === "sync" && model.sync?.getSnapshot().connection.phase === "live") {
|
|
3270
|
+
model.recoveryQueued = "media";
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3070
3273
|
const code = plan.kind === "media" ? "media_recovery_exhausted" : "sync_recovery_exhausted";
|
|
3274
|
+
const transport = model.sync?.getSnapshot().connection.recoveryDiagnostic ?? null;
|
|
3275
|
+
diagnostics.recordRecoveryExhausted({ state: model.state, epoch, code, attempts, transport });
|
|
3276
|
+
if (plan.kind === "sync") {
|
|
3277
|
+
yield* failForSnapshot(lifecycleFailure(code, true, "Connection lost. Rejoin to continue."));
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3071
3280
|
model.failure = toFailure(lifecycleFailure(code, true, "Connection interrupted. Reconnecting in the background."));
|
|
3072
|
-
diagnostics.record({ event: "recovery_exhausted", state: model.state, epoch, code });
|
|
3073
3281
|
yield* publish();
|
|
3074
3282
|
})
|
|
3075
3283
|
);
|
|
@@ -3085,19 +3293,19 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3085
3293
|
const recoveryPlan = (kind) => Clock2.currentTimeMillis.pipe(
|
|
3086
3294
|
Effect2.map((now) => ({
|
|
3087
3295
|
kind,
|
|
3088
|
-
deadline: now + boundedInteger(options.recovery?.budgetMs, RECOVERY_BUDGET_MS, 1, 6e4),
|
|
3296
|
+
deadline: now + boundedInteger(options.recovery?.budgetMs, kind === "sync" ? SYNC_RECOVERY_BUDGET_MS : RECOVERY_BUDGET_MS, 1, 6e4),
|
|
3089
3297
|
attempts: boundedInteger(options.recovery?.maxAttempts, MAX_RECOVERY_ATTEMPTS, 1, 10),
|
|
3090
3298
|
delays: options.recovery?.backoffMs?.length ? options.recovery.backoffMs : [100, 250, 500]
|
|
3091
3299
|
}))
|
|
3092
3300
|
);
|
|
3093
|
-
const attemptRecovery = (plan, mediaAttempt2, syncAttempt) => Effect2.gen(function* () {
|
|
3301
|
+
const attemptRecovery = (plan, mediaAttempt2, syncAttempt, onAttempt) => Effect2.gen(function* () {
|
|
3094
3302
|
for (let attempt = 1; attempt <= plan.attempts; attempt += 1) {
|
|
3095
3303
|
const remaining = plan.deadline - (yield* Clock2.currentTimeMillis);
|
|
3096
3304
|
if (remaining <= 0) return null;
|
|
3305
|
+
onAttempt();
|
|
3097
3306
|
diagnostics.record({ event: "recovery_attempt", state: model.state, epoch: model.epoch, attempt });
|
|
3098
3307
|
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"))),
|
|
3308
|
+
(effect) => withinForegroundBudget(effect, remaining, lifecycleFailure("sync_start_failed", true, "Connection recovery is still waiting for the transport")),
|
|
3101
3309
|
Effect2.match({ onSuccess: () => null, onFailure: (cause) => cause })
|
|
3102
3310
|
);
|
|
3103
3311
|
if (failure2 === null) return attempt;
|
|
@@ -3210,7 +3418,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3210
3418
|
attempt.expiresAt = now + MEDIA_RECOVERY_TIMEOUT_MS;
|
|
3211
3419
|
}
|
|
3212
3420
|
if ((attempt.started || snapshot.connection.phase !== "idle") && (yield* syncNeedsReplacement(snapshot))) return yield* restartSync(attempt);
|
|
3213
|
-
yield* waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs,
|
|
3421
|
+
yield* waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs, SYNC_RECOVERY_BUDGET_MS, 1, 6e4));
|
|
3214
3422
|
});
|
|
3215
3423
|
const restartSync = (attempt) => Effect2.gen(function* () {
|
|
3216
3424
|
const epoch = model.epoch;
|
|
@@ -3224,7 +3432,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3224
3432
|
model.syncBindingCleanup?.();
|
|
3225
3433
|
model.sync = null;
|
|
3226
3434
|
const sync2 = yield* Effect2.try({
|
|
3227
|
-
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media }),
|
|
3435
|
+
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media, onTransportDiagnostic, traceparent: reporter?.traceparent }),
|
|
3228
3436
|
catch: (cause) => lifecycleFailure("sync_start_failed", true, "The sync layer could not start", cause)
|
|
3229
3437
|
});
|
|
3230
3438
|
model.sync = sync2;
|
|
@@ -3241,7 +3449,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3241
3449
|
else if (attempt.client === sync) attempt.started = true;
|
|
3242
3450
|
})
|
|
3243
3451
|
).pipe(
|
|
3244
|
-
Effect2.andThen(waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs,
|
|
3452
|
+
Effect2.andThen(waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs, SYNC_RECOVERY_BUDGET_MS, 1, 6e4))),
|
|
3245
3453
|
Effect2.andThen(Effect2.suspend(() => sync === model.sync && model.epoch === epoch && active(model) ? confirmSyncEntry(sync) : Effect2.fail(lifecycleFailure("invalid_state", false, "Sync recovery was superseded")))),
|
|
3246
3454
|
Effect2.tapError(
|
|
3247
3455
|
() => enqueue(
|
|
@@ -3292,6 +3500,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3292
3500
|
yield* transition("left");
|
|
3293
3501
|
});
|
|
3294
3502
|
const syncNeedsRecovery = (snapshot) => active(model) && (snapshot.connection.phase === "connecting" || snapshot.connection.phase === "recovering" || snapshot.connection.phase === "terminal");
|
|
3503
|
+
const mediaNeedsRecovery = (snapshot) => active(model) && (snapshot.connection.phase === "recovering" || snapshot.connection.phase === "failed" && snapshot.failure?.recoverable === true);
|
|
3295
3504
|
const recordFailure = (failure2) => Effect2.sync(() => {
|
|
3296
3505
|
diagnostics.record({ event: "connection_failed", state: model.state, epoch: model.epoch, code: failure2.code, ...failure2.sync ? { sync: failure2.sync } : {} });
|
|
3297
3506
|
});
|
|
@@ -3304,8 +3513,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3304
3513
|
const handleMediaSnapshot = (media, snapshot) => Effect2.gen(function* () {
|
|
3305
3514
|
if (media !== model.media || media.getSnapshot() !== snapshot) return;
|
|
3306
3515
|
if (snapshot === model.mediaSnapshot) return;
|
|
3516
|
+
const phase = mediaDiagnosticPhase(snapshot);
|
|
3517
|
+
if (phase !== mediaDiagnosticPhase(model.mediaSnapshot)) onTransportDiagnostic({ event: "media_state", phase });
|
|
3307
3518
|
model.mediaSnapshot = snapshot;
|
|
3308
|
-
if (
|
|
3519
|
+
if (mediaNeedsRecovery(snapshot)) yield* recover("media");
|
|
3309
3520
|
yield* publish();
|
|
3310
3521
|
});
|
|
3311
3522
|
const handleMediaFailure = () => model.media ? handleMediaSnapshot(model.media, model.media.getSnapshot()) : Effect2.void;
|
|
@@ -3369,6 +3580,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3369
3580
|
yield* Effect2.addFinalizer(
|
|
3370
3581
|
() => Effect2.gen(function* () {
|
|
3371
3582
|
model.closed = true;
|
|
3583
|
+
reporter?.dispose();
|
|
3372
3584
|
accessReplacementUnsubscribe();
|
|
3373
3585
|
foregroundUnsubscribe?.();
|
|
3374
3586
|
if (activeJoin) yield* Fiber.interrupt(activeJoin);
|
|
@@ -3442,7 +3654,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3442
3654
|
),
|
|
3443
3655
|
runPortCommand: (operation) => withFreshAccess(operation),
|
|
3444
3656
|
nowUnsafe: () => clock.currentTimeMillisUnsafe(),
|
|
3445
|
-
scheduleUnsafe: (callback,
|
|
3657
|
+
scheduleUnsafe: (callback, milliseconds2) => platform.clock.setTimeout(callback, milliseconds2),
|
|
3446
3658
|
cancelScheduleUnsafe: (handle) => platform.clock.clearTimeout(handle),
|
|
3447
3659
|
createId: () => platform.createId?.() ?? `local-${++fallbackIdentifier}`
|
|
3448
3660
|
};
|
|
@@ -3568,6 +3780,9 @@ function syncPhase(phase) {
|
|
|
3568
3780
|
if (phase === "connecting") return "connecting";
|
|
3569
3781
|
return "idle";
|
|
3570
3782
|
}
|
|
3783
|
+
function mediaDiagnosticPhase(snapshot) {
|
|
3784
|
+
return snapshot?.connection.peerConnectionState ?? snapshot?.connection.phase;
|
|
3785
|
+
}
|
|
3571
3786
|
function mediaPhase(phase) {
|
|
3572
3787
|
if (phase === "live") return "healthy";
|
|
3573
3788
|
if (phase === "recovering") return "recovering";
|
|
@@ -6237,6 +6452,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6237
6452
|
drawPolicy: summary.drawPolicy,
|
|
6238
6453
|
drawPermissions: Object.freeze(summary.drawPermissions.map((permission) => Object.freeze({ ...permission }))),
|
|
6239
6454
|
presenting: summary.presenting,
|
|
6455
|
+
presenterId: summary.presenterId ?? null,
|
|
6240
6456
|
error: summary.error ? Object.freeze({ ...summary.error }) : null
|
|
6241
6457
|
});
|
|
6242
6458
|
this.#publish();
|
|
@@ -6255,7 +6471,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6255
6471
|
#publish() {
|
|
6256
6472
|
const slice = Object.freeze({
|
|
6257
6473
|
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 }),
|
|
6474
|
+
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
6475
|
access: Object.freeze({ canDraw: this.#summary.canDraw, policy: this.#summary.drawPolicy, permissions: this.#summary.drawPermissions })
|
|
6260
6476
|
});
|
|
6261
6477
|
const current = this.#store.getSnapshot().whiteboard;
|
|
@@ -6264,7 +6480,7 @@ var WhiteboardControllerRuntime = class {
|
|
|
6264
6480
|
}
|
|
6265
6481
|
};
|
|
6266
6482
|
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);
|
|
6483
|
+
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
6484
|
}
|
|
6269
6485
|
function sameAccess(left, right) {
|
|
6270
6486
|
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 +6575,9 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
|
|
|
6359
6575
|
diagnostics: {
|
|
6360
6576
|
onEvent: (event) => {
|
|
6361
6577
|
platform.onConnectionDiagnostic?.(event);
|
|
6362
|
-
|
|
6578
|
+
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 } : {} };
|
|
6579
|
+
if (event.event === "recovery_exhausted") options.logger?.warn?.("SpaceClient connection event", attributes);
|
|
6580
|
+
else options.logger?.debug?.("SpaceClient connection event", attributes);
|
|
6363
6581
|
}
|
|
6364
6582
|
}
|
|
6365
6583
|
});
|