@q9labsai/chalk-client 5.1.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/dist/chat-files/types.d.ts +3 -0
- package/dist/chunk-6LNRDKJF.js +162 -0
- package/dist/{chunk-3ZN7IXOZ.js → chunk-GA3JDPFI.js} +252 -2
- package/dist/{chunk-RJRSLXVT.js → chunk-RVUN6LW4.js} +764 -265
- package/dist/{chunk-K7PNW3KI.js → chunk-TVNI2XUZ.js} +172 -18
- package/dist/client.d.ts +205 -0
- package/dist/connection/dependencies.d.ts +3 -0
- package/dist/connection/diagnostics.d.ts +3 -2
- package/dist/connection/index.d.ts +1 -0
- package/dist/connection/lifecycle.d.ts +1 -0
- package/dist/connection/types.d.ts +2 -0
- package/dist/effect.js +62 -4
- package/dist/generated/http-api.d.ts +201 -0
- package/dist/generated/schemas.d.ts +373 -0
- package/dist/generated/sync.d.ts +74 -3
- package/dist/generated/whiteboard-v1.d.ts +71 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +20 -14
- package/dist/public-invites/code.d.ts +1 -0
- package/dist/public-invites/index.d.ts +1 -0
- package/dist/public-invites/index.js +6 -4
- package/dist/public-invites/words.d.ts +2 -0
- package/dist/space-client/reload-rejoin.d.ts +1 -1
- package/dist/space-client/types.d.ts +10 -1
- package/dist/sync/connect-diagnostics.d.ts +10 -0
- package/dist/sync/index.js +1 -1
- package/dist/sync/types.d.ts +1 -0
- package/dist/sync/v1-client.d.ts +2 -0
- package/dist/sync/v1-types.d.ts +8 -0
- package/dist/whiteboard/index.d.ts +2 -5
- package/dist/whiteboard/types.d.ts +26 -3
- package/dist/whiteboard/v1-client.d.ts +3 -1
- package/dist/whiteboard/v1-codec.d.ts +5 -1
- package/dist/whiteboard/v1-multipart.d.ts +1 -1
- package/dist/whiteboard/view-bounds.d.ts +5 -0
- package/dist/whiteboard/view-versions.d.ts +9 -0
- package/package.json +1 -1
- package/dist/chunk-KHHULLBZ.js +0 -142
|
@@ -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 }));
|
|
123
|
+
this.#socket.addEventListener("close", (event) => this.onclose?.({ code: event.code, reason: event.reason }));
|
|
124
124
|
this.#socket.addEventListener("error", () => this.onerror?.());
|
|
125
125
|
}
|
|
126
126
|
send(data) {
|
|
@@ -154,7 +154,9 @@ function createReactNativeWebSocketFactory(WebSocketConstructor) {
|
|
|
154
154
|
function createReactNativeSyncLifecycle(environment) {
|
|
155
155
|
return {
|
|
156
156
|
subscribe(listener) {
|
|
157
|
-
const appState = environment.appState.addEventListener("change", (state) =>
|
|
157
|
+
const appState = environment.appState.addEventListener("change", (state) => {
|
|
158
|
+
if (state !== "inactive") listener(state === "active" ? "active" : "inactive");
|
|
159
|
+
});
|
|
158
160
|
const network = environment.networkInfo?.addEventListener((state) => listener(isNetworkOnline(state) ? "online" : "offline"));
|
|
159
161
|
listener(environment.appState.currentState === "active" ? "active" : "inactive");
|
|
160
162
|
return () => {
|
|
@@ -176,7 +178,7 @@ var ReactNativeSyncSocket = class {
|
|
|
176
178
|
this.#socket = socket;
|
|
177
179
|
this.#socket.onopen = () => this.onopen?.();
|
|
178
180
|
this.#socket.onmessage = (event) => this.onmessage?.({ data: event.data });
|
|
179
|
-
this.#socket.onclose = (event) => this.onclose?.({ code: typeof event.code === "number" ? event.code : 1006 });
|
|
181
|
+
this.#socket.onclose = (event) => this.onclose?.({ code: typeof event.code === "number" ? event.code : 1006, reason: typeof event.reason === "string" ? event.reason : void 0 });
|
|
180
182
|
this.#socket.onerror = () => this.onerror?.();
|
|
181
183
|
}
|
|
182
184
|
send(data) {
|
|
@@ -196,7 +198,7 @@ var BrowserFallbackSyncSocket = class {
|
|
|
196
198
|
this.#socket = socket;
|
|
197
199
|
this.#socket.addEventListener("open", () => this.onopen?.());
|
|
198
200
|
this.#socket.addEventListener("message", (event) => this.onmessage?.({ data: event.data }));
|
|
199
|
-
this.#socket.addEventListener("close", (event) => this.onclose?.({ code: event.code }));
|
|
201
|
+
this.#socket.addEventListener("close", (event) => this.onclose?.({ code: event.code, reason: event.reason }));
|
|
200
202
|
this.#socket.addEventListener("error", () => this.onerror?.());
|
|
201
203
|
}
|
|
202
204
|
send(data) {
|
|
@@ -217,6 +219,40 @@ function unsubscribe(subscription) {
|
|
|
217
219
|
}
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
// src/sync/connect-diagnostics.ts
|
|
223
|
+
var SAFE_REASONS = /* @__PURE__ */ new Set([
|
|
224
|
+
"hello timeout",
|
|
225
|
+
"invalid token",
|
|
226
|
+
"policy violation",
|
|
227
|
+
"dependency unavailable",
|
|
228
|
+
"server draining",
|
|
229
|
+
"heartbeat timeout",
|
|
230
|
+
"normal",
|
|
231
|
+
"transport error",
|
|
232
|
+
"authentication failed",
|
|
233
|
+
"delivery recovery required",
|
|
234
|
+
"space action delivery recovery required",
|
|
235
|
+
"text frames only",
|
|
236
|
+
"unsupported protocol",
|
|
237
|
+
"invalid frame",
|
|
238
|
+
"WebSocket dial timeout",
|
|
239
|
+
"lifecycle transport deadline",
|
|
240
|
+
"lifecycle unavailable",
|
|
241
|
+
"invalid_frame",
|
|
242
|
+
"state_digest_mismatch",
|
|
243
|
+
"state_digest_recovery_failed",
|
|
244
|
+
"terminal event acknowledged",
|
|
245
|
+
"client stopped",
|
|
246
|
+
"participant inactive",
|
|
247
|
+
"terminal recovery"
|
|
248
|
+
]);
|
|
249
|
+
function safeSyncCloseReason(reason) {
|
|
250
|
+
return reason ? SAFE_REASONS.has(reason) ? reason : "redacted" : null;
|
|
251
|
+
}
|
|
252
|
+
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]`;
|
|
254
|
+
}
|
|
255
|
+
|
|
220
256
|
// src/sync/v1-health.ts
|
|
221
257
|
var SYNC_HEALTH_BUDGET = {
|
|
222
258
|
probeIntervalMs: 500,
|
|
@@ -266,7 +302,14 @@ var SyncProtocolMetadata = {
|
|
|
266
302
|
field: "protocol",
|
|
267
303
|
value: 1,
|
|
268
304
|
transport: "websocket-json-text",
|
|
269
|
-
route: "/v1/sync"
|
|
305
|
+
route: "/v1/sync",
|
|
306
|
+
entryGeneration: {
|
|
307
|
+
queryParameter: "entry_generation",
|
|
308
|
+
newEntry: "new",
|
|
309
|
+
resume: "previous welcome entry_generation",
|
|
310
|
+
confirmation: "monotonic compare-and-set",
|
|
311
|
+
scope: "participant"
|
|
312
|
+
}
|
|
270
313
|
},
|
|
271
314
|
directions: {
|
|
272
315
|
clientToServer: "client-to-server",
|
|
@@ -290,7 +333,7 @@ var SyncProtocolMetadata = {
|
|
|
290
333
|
},
|
|
291
334
|
{
|
|
292
335
|
id: "live",
|
|
293
|
-
accepts: ["command", "live_target", "directed_request", "delivery_ack", "request_ack", "reaction_send", "chat_send", "chat_page_request", "chat_read_set", "chat_reaction_set", "ping"],
|
|
336
|
+
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"],
|
|
294
337
|
heartbeat: {
|
|
295
338
|
intervalMs: 2e4,
|
|
296
339
|
missedDeadlinesBeforeClose: 2
|
|
@@ -570,7 +613,7 @@ var SyncProtocolMetadata = {
|
|
|
570
613
|
{
|
|
571
614
|
code: 1008,
|
|
572
615
|
connection: "closed",
|
|
573
|
-
reasons: ["invalid token", "policy violation"]
|
|
616
|
+
reasons: ["invalid token", "policy violation", "entry timeout"]
|
|
574
617
|
},
|
|
575
618
|
{
|
|
576
619
|
code: 1009,
|
|
@@ -742,6 +785,8 @@ function strictClientFrame(value) {
|
|
|
742
785
|
return exactObject(value, ["type", "stream", "revision", "state_digest"]);
|
|
743
786
|
case "recovery_ack":
|
|
744
787
|
return exactObject(value, ["type", "recovery_id", "revision", "state_digest"]);
|
|
788
|
+
case "entered":
|
|
789
|
+
return exactObject(value, ["type"]);
|
|
745
790
|
case "ping":
|
|
746
791
|
return exactObject(value, ["type"]);
|
|
747
792
|
default:
|
|
@@ -824,7 +869,8 @@ function strictWelcome(value) {
|
|
|
824
869
|
const base = ["type", "protocol", "participant_id", "participant_generation", "recovery_id", "head", "mode"];
|
|
825
870
|
const extension = Array.isArray(value.extensions) && value.extensions.length === 1 ? value.extensions[0] : void 0;
|
|
826
871
|
const extensionValid = isObject(extension) && ["collaboration_v1", "collaboration_v2"].includes(String(extension.name)) && exactObject(extension, ["name", "capabilities", "participant_capabilities", "chat_head_sequence", "retained_floor_sequence", "read_receipts"]) && Array.isArray(extension.read_receipts);
|
|
827
|
-
|
|
872
|
+
if (Object.hasOwn(value, "entry_confirmation") && value.entry_confirmation !== true) return false;
|
|
873
|
+
const fields = [...base, ...extensionValid ? ["extensions"] : [], ...Object.hasOwn(value, "entry_confirmation") ? ["entry_confirmation"] : [], ...Object.hasOwn(value, "entry_generation") ? ["entry_generation"] : []];
|
|
828
874
|
if (!strictCursor(value.head)) return false;
|
|
829
875
|
if (value.mode === "snapshot") return exactObjectWithCorrelation(value, [...fields, "snapshot"]) && strictSnapshot(value.snapshot);
|
|
830
876
|
if (value.mode === "terminal") return exactObjectWithCorrelation(value, [...fields, "reason"]);
|
|
@@ -883,6 +929,8 @@ function strictServerFrame(value) {
|
|
|
883
929
|
return exactObjectWithCorrelation(value, ["type", "command_id", "code"]);
|
|
884
930
|
case "error":
|
|
885
931
|
return exactObjectWithCorrelation(value, ["type", "code", "detail"]);
|
|
932
|
+
case "entered_ack":
|
|
933
|
+
return exactObjectWithCorrelation(value, ["type"]);
|
|
886
934
|
case "pong":
|
|
887
935
|
return exactObjectWithCorrelation(value, ["type"]);
|
|
888
936
|
default:
|
|
@@ -1423,7 +1471,16 @@ var CollaborationWelcomeExtensionSchema = Schema.Struct({
|
|
|
1423
1471
|
retained_floor_sequence: Schema.NullOr(UnsignedDecimalSchema),
|
|
1424
1472
|
read_receipts: Schema.Array(ChatReadReceiptSchema).check(Schema.isMaxLength(500))
|
|
1425
1473
|
}).check(exactKeys(["name", "capabilities", "participant_capabilities", "chat_head_sequence", "retained_floor_sequence", "read_receipts"]));
|
|
1426
|
-
var WelcomeBase = {
|
|
1474
|
+
var WelcomeBase = {
|
|
1475
|
+
type: Schema.Literal("welcome"),
|
|
1476
|
+
protocol: Schema.Literal(1),
|
|
1477
|
+
participant_id: UuidSchema,
|
|
1478
|
+
participant_generation: PositiveIntegerSchema,
|
|
1479
|
+
recovery_id: UuidSchema,
|
|
1480
|
+
head: ControlCursorSchema,
|
|
1481
|
+
entry_confirmation: Schema.optionalKey(Schema.Literal(true)),
|
|
1482
|
+
entry_generation: Schema.optionalKey(PositiveIntegerSchema.check(Schema.isLessThanOrEqualTo(9007199254740991)))
|
|
1483
|
+
};
|
|
1427
1484
|
var ExtendedWelcomeBase = { ...WelcomeBase, extensions: Schema.Tuple([CollaborationWelcomeExtensionSchema]) };
|
|
1428
1485
|
var WelcomeSnapshotFrameSchema = Schema.Struct({ ...WelcomeBase, mode: Schema.Literal("snapshot"), snapshot: SnapshotSchema, ...ServerCorrelationFields }).check(boundedServerFrame(SyncProtocolLimits.snapshotEncodedBytes));
|
|
1429
1486
|
var WelcomeReplayFrameSchema = Schema.Struct({ ...WelcomeBase, mode: Schema.Literal("replay"), ...ServerCorrelationFields });
|
|
@@ -1599,6 +1656,8 @@ var RejectedChatReadResultFrameSchema = Schema.Struct({ type: Schema.Literal("ch
|
|
|
1599
1656
|
var ChatReadResultFrameSchema = Schema.Union([AcceptedChatReadResultFrameSchema, RejectedChatReadResultFrameSchema]);
|
|
1600
1657
|
var RetryableErrorFrameSchema = Schema.Struct({ type: Schema.Literal("retryable_error"), command_id: CommandIdSchema, code: RetryableErrorCodeSchema, ...ServerCorrelationFields });
|
|
1601
1658
|
var ErrorFrameSchema = Schema.Struct({ type: Schema.Literal("error"), code: ProtocolErrorCodeSchema, detail: boundedUtf8String(1024), ...ServerCorrelationFields });
|
|
1659
|
+
var EnteredFrameSchema = Schema.Struct({ type: Schema.Literal("entered") });
|
|
1660
|
+
var EnteredAckFrameSchema = Schema.Struct({ type: Schema.Literal("entered_ack"), ...ServerCorrelationFields });
|
|
1602
1661
|
var PingFrameSchema = Schema.Struct({ type: Schema.Literal("ping") });
|
|
1603
1662
|
var PongFrameSchema = Schema.Struct({ type: Schema.Literal("pong"), ...ServerCorrelationFields });
|
|
1604
1663
|
var SyncV1ClientFrameSchema = Schema.Unknown.check(Schema.makeFilter((frame) => strictClientFrame(frame) ? void 0 : { path: [], issue: "must not contain unknown or missing fields" })).pipe(
|
|
@@ -1632,6 +1691,7 @@ var SyncV1ClientFrameSchema = Schema.Unknown.check(Schema.makeFilter((frame) =>
|
|
|
1632
1691
|
ChatReactionSetFrameSchema,
|
|
1633
1692
|
DeliveryAckFrameSchema,
|
|
1634
1693
|
RecoveryAckFrameSchema,
|
|
1694
|
+
EnteredFrameSchema,
|
|
1635
1695
|
PingFrameSchema
|
|
1636
1696
|
])
|
|
1637
1697
|
)
|
|
@@ -1663,6 +1723,7 @@ var SyncV1ServerFrameSchema = Schema.Unknown.check(Schema.makeFilter((frame) =>
|
|
|
1663
1723
|
ChatReactionResultFrameSchema,
|
|
1664
1724
|
RetryableErrorFrameSchema,
|
|
1665
1725
|
ErrorFrameSchema,
|
|
1726
|
+
EnteredAckFrameSchema,
|
|
1666
1727
|
PongFrameSchema
|
|
1667
1728
|
])
|
|
1668
1729
|
)
|
|
@@ -1897,7 +1958,7 @@ function reduceEvent(state, event) {
|
|
|
1897
1958
|
case "episode_started":
|
|
1898
1959
|
return state;
|
|
1899
1960
|
case "episode_ended":
|
|
1900
|
-
return { ...state, status: "ended", participants: [], admissionRequests: [], recording: null };
|
|
1961
|
+
return { ...state, status: "ended", endReason: event.payload.reason, participants: [], admissionRequests: [], recording: null };
|
|
1901
1962
|
case "hand_raised":
|
|
1902
1963
|
case "hand_lowered":
|
|
1903
1964
|
return updateParticipant(state, event.payload.participant_id, (participant) => ({ ...participant, handRaised: event.name === "hand_raised" }));
|
|
@@ -2939,6 +3000,9 @@ var V1SyncClient = class {
|
|
|
2939
3000
|
#snapshotRevision = null;
|
|
2940
3001
|
#controlEvents = /* @__PURE__ */ new Map();
|
|
2941
3002
|
#liveTargets;
|
|
3003
|
+
#entryPending = false;
|
|
3004
|
+
// Older Sync rejects new hello fields, so entry confirmation is requested on the URL and granted in welcome.
|
|
3005
|
+
#entryNegotiated = false;
|
|
2942
3006
|
#requests = /* @__PURE__ */ new Map();
|
|
2943
3007
|
#mediaEventEvidence = /* @__PURE__ */ new Map();
|
|
2944
3008
|
#presenceEventEvidence = /* @__PURE__ */ new Map();
|
|
@@ -2949,6 +3013,7 @@ var V1SyncClient = class {
|
|
|
2949
3013
|
#presence = null;
|
|
2950
3014
|
#participantId = null;
|
|
2951
3015
|
#participantGeneration = null;
|
|
3016
|
+
#entryGeneration = null;
|
|
2952
3017
|
#recovery = null;
|
|
2953
3018
|
#snapshotRecoveryRequested = false;
|
|
2954
3019
|
#lastDigestRecoveryAt = null;
|
|
@@ -2958,7 +3023,14 @@ var V1SyncClient = class {
|
|
|
2958
3023
|
#reconnectTimer;
|
|
2959
3024
|
#cancelDial;
|
|
2960
3025
|
#dialTimeoutCount = 0;
|
|
3026
|
+
#connectStartedAt = null;
|
|
3027
|
+
#opened = false;
|
|
3028
|
+
#connectFailure;
|
|
3029
|
+
#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())) };
|
|
3031
|
+
}
|
|
2961
3032
|
#reconnectAttempt = 0;
|
|
3033
|
+
#episodeEndTimer;
|
|
2962
3034
|
#heartbeatTimer;
|
|
2963
3035
|
#missedHeartbeats = 0;
|
|
2964
3036
|
#noticeTimer;
|
|
@@ -3036,11 +3108,15 @@ var V1SyncClient = class {
|
|
|
3036
3108
|
}
|
|
3037
3109
|
stop() {
|
|
3038
3110
|
this.#started = false;
|
|
3111
|
+
this.#entryPending = false;
|
|
3112
|
+
this.#entryGeneration = null;
|
|
3039
3113
|
this.#startupGeneration += 1;
|
|
3040
3114
|
this.#unsubscribeLifecycle?.();
|
|
3041
3115
|
this.#unsubscribeLifecycle = void 0;
|
|
3042
3116
|
this.#clearReconnect();
|
|
3043
3117
|
this.#clearDial();
|
|
3118
|
+
this.#clock().clearTimeout(this.#episodeEndTimer);
|
|
3119
|
+
this.#episodeEndTimer = void 0;
|
|
3044
3120
|
this.#clearHeartbeat();
|
|
3045
3121
|
this.#clearNoticeProbe();
|
|
3046
3122
|
this.#commandScheduler.stop("client_stopped");
|
|
@@ -3055,11 +3131,24 @@ var V1SyncClient = class {
|
|
|
3055
3131
|
this.#liveTargets.resetLocalMedia();
|
|
3056
3132
|
this.#emit();
|
|
3057
3133
|
}
|
|
3134
|
+
/** Announces completed startup; transport recovery resends until acknowledged. */
|
|
3135
|
+
async confirmEntered() {
|
|
3136
|
+
this.#requireLive();
|
|
3137
|
+
this.#entryPending = true;
|
|
3138
|
+
if (!this.#entryNegotiated) return;
|
|
3139
|
+
this.#send({ type: "entered" });
|
|
3140
|
+
}
|
|
3058
3141
|
getSnapshot() {
|
|
3059
3142
|
const pendingCommands = this.#commandScheduler.pendingCommands;
|
|
3060
3143
|
const optimisticControl = this.#control && this.#participantId ? optimisticV1Control(this.#control, this.#participantId, pendingCommands) : this.#control;
|
|
3061
3144
|
return {
|
|
3062
|
-
connection: {
|
|
3145
|
+
connection: {
|
|
3146
|
+
...this.#phase,
|
|
3147
|
+
...this.#connectStartedAt !== null && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering") ? { connectAttempt: this.#connectDiagnostic() } : {},
|
|
3148
|
+
...this.#connectFailure && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering") ? { connectFailure: this.#connectFailure } : {},
|
|
3149
|
+
...this.#phase.phase === "live" ? { noticeUnresponsive: this.#health.health === "recovering", health: this.#health.health } : {},
|
|
3150
|
+
...this.#dialTimeoutCount ? { dialTimeoutCount: this.#dialTimeoutCount } : {}
|
|
3151
|
+
},
|
|
3063
3152
|
participantId: this.#participantId,
|
|
3064
3153
|
participantGeneration: this.#participantGeneration,
|
|
3065
3154
|
control: this.#control,
|
|
@@ -3199,8 +3288,19 @@ var V1SyncClient = class {
|
|
|
3199
3288
|
#connect() {
|
|
3200
3289
|
if (!this.#started || !this.#transportAvailable || this.#socket || this.#phase.phase === "terminal") return;
|
|
3201
3290
|
this.#phase = { phase: "connecting" };
|
|
3291
|
+
this.#connectStartedAt = this.#clock().now();
|
|
3292
|
+
this.#opened = false;
|
|
3293
|
+
this.#connectFailure = void 0;
|
|
3202
3294
|
this.#emit();
|
|
3203
|
-
|
|
3295
|
+
this.#entryNegotiated = false;
|
|
3296
|
+
let socket;
|
|
3297
|
+
try {
|
|
3298
|
+
socket = this.#options.webSocket.connect(entryUrl(this.#options.url, this.#entryGeneration, this.#options.entryConfirmation === true));
|
|
3299
|
+
} catch {
|
|
3300
|
+
this.#connectFailure = this.#connectDiagnostic();
|
|
3301
|
+
this.#emit();
|
|
3302
|
+
throw new V1SyncError("Sync WebSocket could not be created", "sync_hello_timeout");
|
|
3303
|
+
}
|
|
3204
3304
|
const connectionGeneration = ++this.#connectionGeneration;
|
|
3205
3305
|
this.#socket = socket;
|
|
3206
3306
|
this.#cancelDial = scheduleSyncDialDeadline(
|
|
@@ -3208,6 +3308,7 @@ var V1SyncClient = class {
|
|
|
3208
3308
|
() => {
|
|
3209
3309
|
this.#cancelDial = void 0;
|
|
3210
3310
|
this.#dialTimeoutCount += 1;
|
|
3311
|
+
this.#connectFailure = this.#connectDiagnostic();
|
|
3211
3312
|
this.#recover("WebSocket dial timeout");
|
|
3212
3313
|
},
|
|
3213
3314
|
this.#options.dialTimeoutMs
|
|
@@ -3215,18 +3316,36 @@ var V1SyncClient = class {
|
|
|
3215
3316
|
socket.onopen = () => {
|
|
3216
3317
|
if (socket !== this.#socket) return;
|
|
3217
3318
|
this.#clearDial();
|
|
3319
|
+
this.#opened = true;
|
|
3320
|
+
this.#connectFailure = void 0;
|
|
3218
3321
|
void this.#authenticate(socket);
|
|
3219
3322
|
};
|
|
3220
3323
|
socket.onmessage = (event) => {
|
|
3221
3324
|
this.#inbound = this.#inbound.then(() => this.#receive(socket, event.data));
|
|
3222
3325
|
};
|
|
3223
|
-
socket.onclose = () => {
|
|
3326
|
+
socket.onclose = (event) => {
|
|
3327
|
+
if (socket === this.#socket && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering")) {
|
|
3328
|
+
this.#connectFailure = { ...this.#connectDiagnostic(), closeCode: event.code, closeReason: safeSyncCloseReason(event.reason) };
|
|
3329
|
+
this.#emit();
|
|
3330
|
+
}
|
|
3224
3331
|
this.#clock().setTimeout(() => {
|
|
3225
3332
|
if (connectionGeneration !== this.#connectionGeneration) return;
|
|
3226
|
-
this.#inbound = this.#inbound.then(() =>
|
|
3333
|
+
this.#inbound = this.#inbound.then(() => {
|
|
3334
|
+
if (socket === this.#socket && event.code === 1008 && (event.reason === "" || event.reason === "invalid token" || event.reason === "policy violation" || event.reason === "entry timeout")) {
|
|
3335
|
+
this.#phase = { phase: "terminal", terminalReason: event.reason === "entry timeout" ? "entry_timeout" : "access_rejected" };
|
|
3336
|
+
this.#emit();
|
|
3337
|
+
}
|
|
3338
|
+
this.#disconnected(socket);
|
|
3339
|
+
});
|
|
3227
3340
|
}, 0);
|
|
3228
3341
|
};
|
|
3229
|
-
socket.onerror = () =>
|
|
3342
|
+
socket.onerror = () => {
|
|
3343
|
+
if (socket === this.#socket && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering")) {
|
|
3344
|
+
this.#connectFailure = this.#connectDiagnostic();
|
|
3345
|
+
this.#emit();
|
|
3346
|
+
}
|
|
3347
|
+
socket.close(CLIENT_RESTART_CLOSE_CODE, "transport error");
|
|
3348
|
+
};
|
|
3230
3349
|
}
|
|
3231
3350
|
async #authenticate(socket) {
|
|
3232
3351
|
try {
|
|
@@ -3309,6 +3428,19 @@ var V1SyncClient = class {
|
|
|
3309
3428
|
case "ack":
|
|
3310
3429
|
this.#requireLive();
|
|
3311
3430
|
await this.#commandScheduler.acknowledge(frame);
|
|
3431
|
+
if (frame.outcome === "rejected" && frame.reason === "episode_ended") {
|
|
3432
|
+
if (this.#episodeEndTimer === void 0) {
|
|
3433
|
+
this.#episodeEndTimer = this.#clock().setTimeout(() => {
|
|
3434
|
+
this.#episodeEndTimer = void 0;
|
|
3435
|
+
if (this.#control?.status === "ended" || this.#phase.phase === "terminal") return;
|
|
3436
|
+
this.#phase = { phase: "terminal", terminalReason: "episode_ended" };
|
|
3437
|
+
this.#clearReconnect();
|
|
3438
|
+
this.#socket?.close(1e3, "episode ended");
|
|
3439
|
+
this.#emit();
|
|
3440
|
+
}, 5e3);
|
|
3441
|
+
}
|
|
3442
|
+
this.#recover("episode ended");
|
|
3443
|
+
}
|
|
3312
3444
|
return;
|
|
3313
3445
|
case "retryable_error":
|
|
3314
3446
|
this.#requireLive();
|
|
@@ -3355,6 +3487,10 @@ var V1SyncClient = class {
|
|
|
3355
3487
|
return;
|
|
3356
3488
|
case "error":
|
|
3357
3489
|
throw new V1ReplicaError(frame.code);
|
|
3490
|
+
case "entered_ack":
|
|
3491
|
+
this.#requireLive();
|
|
3492
|
+
this.#entryPending = false;
|
|
3493
|
+
return;
|
|
3358
3494
|
case "pong": {
|
|
3359
3495
|
this.#requireLive();
|
|
3360
3496
|
this.#missedHeartbeats = 0;
|
|
@@ -3368,6 +3504,8 @@ var V1SyncClient = class {
|
|
|
3368
3504
|
async #welcome(frame) {
|
|
3369
3505
|
if (this.#phase.phase !== "recovering" || this.#recovery) throw new V1ReplicaError("unexpected welcome");
|
|
3370
3506
|
this.#collaborationState.acceptWelcome(frame);
|
|
3507
|
+
this.#entryNegotiated = frame.entry_confirmation === true;
|
|
3508
|
+
this.#entryGeneration = frame.entry_generation ?? this.#entryGeneration;
|
|
3371
3509
|
this.#participantId = frame.participant_id;
|
|
3372
3510
|
this.#participantGeneration = frame.participant_generation;
|
|
3373
3511
|
this.#updateLocalMediaStates();
|
|
@@ -3486,8 +3624,11 @@ var V1SyncClient = class {
|
|
|
3486
3624
|
if (!this.#recovery?.controlComplete || !this.#media || !this.#presence) return;
|
|
3487
3625
|
this.#recovery = null;
|
|
3488
3626
|
this.#phase = { phase: "live" };
|
|
3627
|
+
this.#connectStartedAt = null;
|
|
3628
|
+
this.#connectFailure = void 0;
|
|
3489
3629
|
this.#reconnectAttempt = 0;
|
|
3490
3630
|
this.#missedHeartbeats = 0;
|
|
3631
|
+
if (this.#entryPending && this.#entryNegotiated) this.#send({ type: "entered" });
|
|
3491
3632
|
this.#commandScheduler.enterLive();
|
|
3492
3633
|
this.#liveTargets.enterLive();
|
|
3493
3634
|
this.#startHeartbeat();
|
|
@@ -3529,9 +3670,10 @@ var V1SyncClient = class {
|
|
|
3529
3670
|
this.#presence = null;
|
|
3530
3671
|
this.#rejectRequests("disconnected_before_delivery");
|
|
3531
3672
|
this.#collaborationState.disconnect("disconnected_before_delivery");
|
|
3532
|
-
if (!this.#started ||
|
|
3673
|
+
if (!this.#started || this.#phase.phase === "terminal") return;
|
|
3533
3674
|
this.#phase = { phase: "connecting" };
|
|
3534
3675
|
this.#emit();
|
|
3676
|
+
if (!this.#transportAvailable) return;
|
|
3535
3677
|
this.#clearReconnect();
|
|
3536
3678
|
const configuredDelay = this.#options.reconnectDelayMs;
|
|
3537
3679
|
const exponentialAttempt = configuredDelay === void 0 ? Math.max(0, this.#reconnectAttempt - FAST_RECONNECT_ATTEMPTS + 1) : this.#reconnectAttempt;
|
|
@@ -3576,8 +3718,13 @@ var V1SyncClient = class {
|
|
|
3576
3718
|
if (event === "online" || event === "offline") this.#online = event === "online";
|
|
3577
3719
|
else this.#active = event === "active";
|
|
3578
3720
|
this.#transportAvailable = this.#online && this.#active;
|
|
3579
|
-
if (!this.#transportAvailable)
|
|
3580
|
-
|
|
3721
|
+
if (!this.#transportAvailable) {
|
|
3722
|
+
const socket = this.#socket;
|
|
3723
|
+
if (socket) {
|
|
3724
|
+
socket.close(CLIENT_RESTART_CLOSE_CODE, "lifecycle unavailable");
|
|
3725
|
+
this.#disconnected(socket);
|
|
3726
|
+
}
|
|
3727
|
+
} else this.#connect();
|
|
3581
3728
|
}
|
|
3582
3729
|
#rejectEphemeral(code) {
|
|
3583
3730
|
this.#rejectRequests(code);
|
|
@@ -3746,6 +3893,12 @@ function copyPublication(publication) {
|
|
|
3746
3893
|
function validMediaPublication(publication) {
|
|
3747
3894
|
return typeof publication.participantId === "string" && publication.participantId.length > 0 && (publication.source === "microphone" || publication.source === "camera" || publication.source === "screen" || publication.source === "screen_audio") && typeof publication.enabled === "boolean" && (publication.publicationId === null || typeof publication.publicationId === "string") && (!publication.enabled || publication.publicationId !== null);
|
|
3748
3895
|
}
|
|
3896
|
+
function entryUrl(value, generation, confirmation) {
|
|
3897
|
+
const url = new URL(value);
|
|
3898
|
+
url.searchParams.set("entry_generation", generation === null ? "new" : String(generation));
|
|
3899
|
+
if (confirmation) url.searchParams.set("entry_confirmation", "1");
|
|
3900
|
+
return url.toString();
|
|
3901
|
+
}
|
|
3749
3902
|
function assertV1Url(value) {
|
|
3750
3903
|
const url = new URL(value);
|
|
3751
3904
|
if (url.pathname !== "/v1/sync") throw new V1SyncError("SyncEngine v1 websocket URL must use /v1/sync", "invalid_url");
|
|
@@ -3893,6 +4046,7 @@ function createV1SyncClient(options) {
|
|
|
3893
4046
|
}
|
|
3894
4047
|
|
|
3895
4048
|
export {
|
|
4049
|
+
formatSyncConnectDiagnostic,
|
|
3896
4050
|
UuidSchema,
|
|
3897
4051
|
ReactionValues,
|
|
3898
4052
|
ReactionSchema,
|