@q9labsai/chalk-client 5.2.6 → 5.3.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/README.md +1 -1
- package/dist/access/grant.d.ts +2 -0
- package/dist/{chunk-I7Y4HR2P.js → chunk-2CEMOTZF.js} +333 -64
- package/dist/{chunk-5YPQNMJE.js → chunk-FD7ZSDTM.js} +103 -26
- package/dist/{chunk-CJVLTSIO.js → chunk-MHJ4UPM3.js} +165 -6
- package/dist/chunk-N4IPKRS6.js +76 -0
- package/dist/{chunk-YIDKETOX.js → chunk-O5JB5XVX.js} +840 -38
- package/dist/{chunk-WFRTUBN6.js → chunk-VAJ6XNAA.js} +1 -1
- package/dist/client.d.ts +182 -1
- package/dist/connection/dependencies.d.ts +2 -1
- package/dist/effect.js +29 -4
- package/dist/generated/http-api.d.ts +180 -1
- package/dist/generated/schemas.d.ts +290 -1
- package/dist/generated/sync.d.ts +60 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +33 -15
- package/dist/media/call-stats.d.ts +27 -0
- package/dist/media/client.d.ts +2 -2
- package/dist/media/ice-servers.d.ts +3 -0
- package/dist/media/index.d.ts +2 -0
- package/dist/media/index.js +4 -1
- package/dist/media/plane.d.ts +4 -2
- package/dist/media/receive-stall.d.ts +52 -0
- package/dist/media/types.d.ts +21 -0
- package/dist/media/video-render-size.d.ts +7 -0
- package/dist/media/viewer-layer.d.ts +48 -0
- package/dist/public-invites/index.js +3 -2
- package/dist/server/index.cjs +19 -4
- package/dist/server/index.js +19 -4
- package/dist/server/types.d.cts +2 -0
- package/dist/server/types.d.ts +2 -0
- package/dist/session/index.d.ts +18 -0
- package/dist/space-client/same-identity.d.ts +6 -0
- package/dist/space-client/types.d.ts +6 -0
- package/dist/sync/index.js +2 -1
- package/dist/sync/v1-command-scheduler.d.ts +4 -0
- package/dist/sync/v1-types.d.ts +6 -0
- package/package.json +3 -1
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionBaggage
|
|
3
|
+
} from "./chunk-N4IPKRS6.js";
|
|
4
|
+
|
|
1
5
|
// src/sync/canonical.ts
|
|
2
6
|
var encoder = new TextEncoder();
|
|
3
7
|
function canonicalJson(value) {
|
|
@@ -405,11 +409,20 @@ var SyncProtocolMetadata = {
|
|
|
405
409
|
pattern: "^[a-z][a-z0-9_*\\-/]{0,255}=[\\x21-\\x2b\\x2d-\\x3c\\x3e-\\x7e]{1,256}(,[a-z][a-z0-9_*\\-/]{0,255}=[\\x21-\\x2b\\x2d-\\x3c\\x3e-\\x7e]{1,256})*$"
|
|
406
410
|
}
|
|
407
411
|
},
|
|
412
|
+
helloOnlyFields: {
|
|
413
|
+
baggage: {
|
|
414
|
+
kind: "string",
|
|
415
|
+
minBytes: 47,
|
|
416
|
+
maxBytes: 47,
|
|
417
|
+
format: "w3c-baggage-session-id",
|
|
418
|
+
pattern: "^session\\.id=[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
408
421
|
acceptedOn: {
|
|
409
422
|
hello: "client-to-server",
|
|
410
423
|
serverFrames: "server-to-client"
|
|
411
424
|
},
|
|
412
|
-
upgradeHeaders: ["x-chalk-journey-id", "traceparent", "tracestate"],
|
|
425
|
+
upgradeHeaders: ["x-chalk-journey-id", "traceparent", "tracestate", "baggage"],
|
|
413
426
|
rule: "propagate_from_first_observed_layer_to_every_downstream_frame"
|
|
414
427
|
},
|
|
415
428
|
externalIntents: [
|
|
@@ -712,7 +725,9 @@ function strictCommand(value) {
|
|
|
712
725
|
function strictCollaborationsHelloExtension(value) {
|
|
713
726
|
return exactObject(value, ["name", "chat_cursor"]) && ["collaboration_v1", "collaboration_v2"].includes(String(value.name)) && exactObject(value.chat_cursor, ["after_sequence", "retained_floor_sequence"]);
|
|
714
727
|
}
|
|
715
|
-
function strictHello(
|
|
728
|
+
function strictHello(hello) {
|
|
729
|
+
const { baggage, ...value } = hello;
|
|
730
|
+
if (baggage !== void 0 && !(typeof baggage === "string" && boundedCorrelationString(baggage, 47, 47) && /^session\.id=[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(baggage))) return false;
|
|
716
731
|
const base = exactObjectWithCorrelation(value, ["type", "protocol", "token", "streams"]);
|
|
717
732
|
const extended = exactObjectWithCorrelation(value, ["type", "protocol", "token", "streams", "extensions"]) && Array.isArray(value.extensions) && value.extensions.length === 1 && strictCollaborationsHelloExtension(value.extensions[0]);
|
|
718
733
|
if (!base && !extended || !exactObject(value.streams, ["control", "media", "presence", "requests"])) return false;
|
|
@@ -861,7 +876,8 @@ function strictEvent(value) {
|
|
|
861
876
|
}
|
|
862
877
|
function strictProjectionItem(value, stream) {
|
|
863
878
|
if (stream === "media") return exactObject(value, ["participant_id", "source", "enabled", "publication_id"]);
|
|
864
|
-
if (stream === "presence")
|
|
879
|
+
if (stream === "presence")
|
|
880
|
+
return isObject(value) && exactObject(value, [...["participant_id", "state", "speaking", "active_speaker"], ...Object.hasOwn(value, "identity_id") ? ["identity_id"] : [], ...Object.hasOwn(value, "device_type") ? ["device_type"] : [], ...Object.hasOwn(value, "admission_revision") ? ["admission_revision"] : []]);
|
|
865
881
|
return false;
|
|
866
882
|
}
|
|
867
883
|
function strictProjection(value) {
|
|
@@ -873,7 +889,13 @@ function strictWelcome(value) {
|
|
|
873
889
|
const extension = Array.isArray(value.extensions) && value.extensions.length === 1 ? value.extensions[0] : void 0;
|
|
874
890
|
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);
|
|
875
891
|
if (Object.hasOwn(value, "entry_confirmation") && value.entry_confirmation !== true) return false;
|
|
876
|
-
const fields = [
|
|
892
|
+
const fields = [
|
|
893
|
+
...base,
|
|
894
|
+
...extensionValid ? ["extensions"] : [],
|
|
895
|
+
...Object.hasOwn(value, "entry_confirmation") ? ["entry_confirmation"] : [],
|
|
896
|
+
...Object.hasOwn(value, "entry_generation") ? ["entry_generation"] : [],
|
|
897
|
+
...Object.hasOwn(value, "participant_identity_id") ? ["participant_identity_id"] : []
|
|
898
|
+
];
|
|
877
899
|
if (!strictCursor(value.head)) return false;
|
|
878
900
|
if (value.mode === "snapshot") return exactObjectWithCorrelation(value, [...fields, "snapshot"]) && strictSnapshot(value.snapshot);
|
|
879
901
|
if (value.mode === "terminal") return exactObjectWithCorrelation(value, [...fields, "reason"]);
|
|
@@ -950,9 +972,10 @@ var TraceparentSchema = boundedUtf8String(55, 55).check(
|
|
|
950
972
|
var TracestateSchema = boundedUtf8String(512, 1).check(
|
|
951
973
|
Schema.makeFilter((value) => new RegExp("^[a-z][a-z0-9_*\\-/]{0,255}=[\\x21-\\x2b\\x2d-\\x3c\\x3e-\\x7e]{1,256}(,[a-z][a-z0-9_*\\-/]{0,255}=[\\x21-\\x2b\\x2d-\\x3c\\x3e-\\x7e]{1,256})*$", "u").test(value) ? void 0 : { path: [], issue: "must be a valid bounded W3C tracestate" })
|
|
952
974
|
);
|
|
975
|
+
var BaggageSchema = boundedUtf8String(47, 47).check(Schema.makeFilter((value) => /^session\.id=[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(value) ? void 0 : { path: [], issue: "must be the single W3C baggage member session.id=<lowercase UUID>" }));
|
|
953
976
|
var SyncCorrelationFieldsSchema = Schema.Struct({ journey_id: Schema.optional(JourneyIdSchema), traceparent: Schema.optional(TraceparentSchema), tracestate: Schema.optional(TracestateSchema) });
|
|
954
|
-
var
|
|
955
|
-
var
|
|
977
|
+
var ServerCorrelationFields = { journey_id: Schema.optional(JourneyIdSchema), traceparent: Schema.optional(TraceparentSchema), tracestate: Schema.optional(TracestateSchema) };
|
|
978
|
+
var HelloCorrelationFields = { ...ServerCorrelationFields, baggage: Schema.optional(BaggageSchema) };
|
|
956
979
|
var StateDigestSchema = Schema.String.check(Schema.makeFilter((value) => /^[0-9a-f]{64}$/u.test(value) ? void 0 : { path: [], issue: "must be a lowercase hexadecimal SHA-256 digest" }));
|
|
957
980
|
var UnsignedDecimalSchema = Schema.String.check(Schema.makeFilter((value) => /^(?:0|[1-9][0-9]*)$/u.test(value) ? void 0 : { path: [], issue: "must be an unsigned decimal integer" }));
|
|
958
981
|
var CommandIdSchema = boundedUtf8String(64).check(Schema.makeFilter((value) => textEncoder.encode(value).byteLength >= 16 ? void 0 : { path: [], issue: "must meet the command ID minimum byte length" }));
|
|
@@ -1479,6 +1502,7 @@ var WelcomeBase = {
|
|
|
1479
1502
|
protocol: Schema.Literal(1),
|
|
1480
1503
|
participant_id: UuidSchema,
|
|
1481
1504
|
participant_generation: PositiveIntegerSchema,
|
|
1505
|
+
participant_identity_id: Schema.optionalKey(UuidSchema),
|
|
1482
1506
|
recovery_id: UuidSchema,
|
|
1483
1507
|
head: ControlCursorSchema,
|
|
1484
1508
|
entry_confirmation: Schema.optionalKey(Schema.Literal(true)),
|
|
@@ -1527,9 +1551,15 @@ var CommandIdConflictAckFrameSchema = Schema.Struct({ type: Schema.Literal("ack"
|
|
|
1527
1551
|
var MediaProjectionItemSchema = Schema.Struct({ participant_id: UuidSchema, source: Schema.Union([Schema.Literal("microphone"), Schema.Literal("camera"), Schema.Literal("screen")]), enabled: Schema.Boolean, publication_id: Schema.NullOr(boundedUtf8String(256, 1)) }).check(
|
|
1528
1552
|
Schema.makeFilter((item) => !item.enabled || item.publication_id !== null ? void 0 : { path: [], issue: "enabled requires a publication_id" })
|
|
1529
1553
|
);
|
|
1530
|
-
var PresenceProjectionItemSchema = Schema.Struct({
|
|
1531
|
-
|
|
1532
|
-
)
|
|
1554
|
+
var PresenceProjectionItemSchema = Schema.Struct({
|
|
1555
|
+
participant_id: UuidSchema,
|
|
1556
|
+
state: Schema.Union([Schema.Literal("connected"), Schema.Literal("disconnected")]),
|
|
1557
|
+
speaking: Schema.Boolean,
|
|
1558
|
+
active_speaker: Schema.Boolean,
|
|
1559
|
+
identity_id: Schema.optional(UuidSchema),
|
|
1560
|
+
device_type: Schema.optional(Schema.Literals(["phone", "tablet", "computer"])),
|
|
1561
|
+
admission_revision: Schema.optional(PositiveIntegerSchema)
|
|
1562
|
+
}).check(Schema.makeFilter((item) => item.state !== "disconnected" || !item.speaking && !item.active_speaker ? void 0 : { path: [], issue: "disconnected presence cannot be speaking or active speaker" }));
|
|
1533
1563
|
var MediaProjectionSnapshotFrameSchema = Schema.Struct({
|
|
1534
1564
|
type: Schema.Literal("projection_snapshot"),
|
|
1535
1565
|
stream: Schema.Literal("media"),
|
|
@@ -2514,6 +2544,11 @@ var V1CommandScheduler = class {
|
|
|
2514
2544
|
async acknowledge(frame) {
|
|
2515
2545
|
const deferred = this.#commands.get(frame.command_id);
|
|
2516
2546
|
if (!deferred) return;
|
|
2547
|
+
if (isSatisfiedByInactiveParticipant(frame, deferred.frame)) {
|
|
2548
|
+
const head = this.#options.controlHead();
|
|
2549
|
+
await this.#finishCommand(frame.command_id, { type: "ack", command_id: frame.command_id, delivery: "duplicate", outcome: "satisfied", revision: head.revision, state_digest: head.state_digest });
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2517
2552
|
if (frame.outcome === "rejected" || frame.outcome === "command_id_conflict") {
|
|
2518
2553
|
await this.#finishCommand(frame.command_id, frame);
|
|
2519
2554
|
return;
|
|
@@ -2552,15 +2587,12 @@ var V1CommandScheduler = class {
|
|
|
2552
2587
|
}
|
|
2553
2588
|
}
|
|
2554
2589
|
async settleTerminalRecovery(frame) {
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
} else if (frame.reason === "participant_inactive") {
|
|
2560
|
-
satisfiedOperations.add("participant_leave");
|
|
2561
|
-
}
|
|
2590
|
+
const satisfies = (operation) => {
|
|
2591
|
+
if (frame.reason === "episode_ended") return operation === "end_episode" || operation === "participant_leave";
|
|
2592
|
+
return frame.reason === "participant_inactive" && inactiveParticipantSatisfies(operation);
|
|
2593
|
+
};
|
|
2562
2594
|
for (const [commandId, deferred] of this.#commands) {
|
|
2563
|
-
if (deferred.frame.type !== "operation" || !
|
|
2595
|
+
if (deferred.frame.type !== "operation" || !satisfies(deferred.frame.name)) continue;
|
|
2564
2596
|
await this.#finishCommand(commandId, {
|
|
2565
2597
|
type: "ack",
|
|
2566
2598
|
command_id: commandId,
|
|
@@ -2730,6 +2762,12 @@ function targetFrame(commandId, command) {
|
|
|
2730
2762
|
return { type: "command", command_id: commandId, name: command.name, payload: command.payload };
|
|
2731
2763
|
}
|
|
2732
2764
|
}
|
|
2765
|
+
function inactiveParticipantSatisfies(operation) {
|
|
2766
|
+
return operation === "participant_leave";
|
|
2767
|
+
}
|
|
2768
|
+
function isSatisfiedByInactiveParticipant(result, command) {
|
|
2769
|
+
return result.outcome === "rejected" && result.reason === "participant_inactive" && command.type === "operation" && inactiveParticipantSatisfies(command.name);
|
|
2770
|
+
}
|
|
2733
2771
|
function isTerminalLifecycleOperation(frame) {
|
|
2734
2772
|
return frame.type === "operation" && (frame.name === "participant_leave" || frame.name === "end_episode");
|
|
2735
2773
|
}
|
|
@@ -2988,6 +3026,7 @@ var MAX_REPLAY_EVENTS = SyncProtocolLimits.completeReplayMaxEvents;
|
|
|
2988
3026
|
var MAX_REPLAY_BYTES = SyncProtocolLimits.completeReplayEncodedBytes;
|
|
2989
3027
|
var MAX_PROJECTION_EVENT_EVIDENCE = 256;
|
|
2990
3028
|
var CLIENT_RESTART_CLOSE_CODE = 4e3;
|
|
3029
|
+
var legacyHelloServers = /* @__PURE__ */ new Set();
|
|
2991
3030
|
var DEFAULT_RECONNECT_DELAY_MS = 250;
|
|
2992
3031
|
var MAX_RECONNECT_DELAY_MS = 5e3;
|
|
2993
3032
|
var FAST_RECONNECT_ATTEMPTS = 8;
|
|
@@ -3016,6 +3055,7 @@ var V1SyncClient = class {
|
|
|
3016
3055
|
#presence = null;
|
|
3017
3056
|
#participantId = null;
|
|
3018
3057
|
#participantGeneration = null;
|
|
3058
|
+
#participantIdentityId;
|
|
3019
3059
|
#entryGeneration = null;
|
|
3020
3060
|
#recovery = null;
|
|
3021
3061
|
#snapshotRecoveryRequested = false;
|
|
@@ -3037,6 +3077,8 @@ var V1SyncClient = class {
|
|
|
3037
3077
|
return { stage: this.#opened ? "hello" : "upgrade", opened: this.#opened, closeCode: null, closeReason: null, durationMs: Math.max(0, this.#clock().now() - (this.#connectStartedAt ?? this.#clock().now())), attempt: this.#connectAttemptCount };
|
|
3038
3078
|
}
|
|
3039
3079
|
#reconnectAttempt = 0;
|
|
3080
|
+
/** The socket whose hello carried baggage and has not been answered by a welcome yet. */
|
|
3081
|
+
#baggageHelloSocket = null;
|
|
3040
3082
|
#episodeEndTimer;
|
|
3041
3083
|
#heartbeatTimer;
|
|
3042
3084
|
#missedHeartbeats = 0;
|
|
@@ -3067,7 +3109,11 @@ var V1SyncClient = class {
|
|
|
3067
3109
|
isStarted: () => this.#started,
|
|
3068
3110
|
sendIfLive: (frame) => this.#sendIfLive(frame),
|
|
3069
3111
|
stateChanged: () => this.#emit(),
|
|
3070
|
-
ackHeadIsProven: (ack) => this.#ackHeadIsProven(ack)
|
|
3112
|
+
ackHeadIsProven: (ack) => this.#ackHeadIsProven(ack),
|
|
3113
|
+
controlHead: () => {
|
|
3114
|
+
const control = this.#requireControl();
|
|
3115
|
+
return { revision: control.revision, state_digest: control.stateDigest };
|
|
3116
|
+
}
|
|
3071
3117
|
});
|
|
3072
3118
|
this.#liveTargets = new V1LiveTargetCoordinator({
|
|
3073
3119
|
mediaPlane: options.mediaPlane,
|
|
@@ -3136,6 +3182,7 @@ var V1SyncClient = class {
|
|
|
3136
3182
|
this.#socket?.close(1e3, "client stopped");
|
|
3137
3183
|
this.#socket = null;
|
|
3138
3184
|
this.#phase = { phase: "stopped" };
|
|
3185
|
+
this.#options.mediaPlane?.remotePublicationsChanged?.([], "unavailable");
|
|
3139
3186
|
this.#rejectEphemeral("client_stopped");
|
|
3140
3187
|
this.#collaborationState.disconnect("client_stopped");
|
|
3141
3188
|
this.#localPublications = [];
|
|
@@ -3164,6 +3211,7 @@ var V1SyncClient = class {
|
|
|
3164
3211
|
},
|
|
3165
3212
|
participantId: this.#participantId,
|
|
3166
3213
|
participantGeneration: this.#participantGeneration,
|
|
3214
|
+
participantIdentityId: this.#participantIdentityId,
|
|
3167
3215
|
control: this.#control,
|
|
3168
3216
|
optimisticControl,
|
|
3169
3217
|
media: copyProjection(this.#media),
|
|
@@ -3183,6 +3231,7 @@ var V1SyncClient = class {
|
|
|
3183
3231
|
return () => this.#mediaPauseListeners.delete(listener);
|
|
3184
3232
|
}
|
|
3185
3233
|
#notifyMediaPause(event) {
|
|
3234
|
+
if (event.name === "episode_ended") this.#options.mediaPlane?.remotePublicationsChanged?.([]);
|
|
3186
3235
|
const source = event.name === "participant_microphone_stopped" ? "microphone" : event.name === "participant_camera_stopped" ? "camera" : event.name === "participant_screen_share_stopped" ? "screen" : null;
|
|
3187
3236
|
if (source && "participant_id" in event.payload && event.payload.participant_id === this.#participantId) {
|
|
3188
3237
|
for (const listener of this.#mediaPauseListeners) listener({ source, revision: event.revision });
|
|
@@ -3355,11 +3404,12 @@ var V1SyncClient = class {
|
|
|
3355
3404
|
this.#clock().setTimeout(() => {
|
|
3356
3405
|
if (connectionGeneration !== this.#connectionGeneration) return;
|
|
3357
3406
|
this.#inbound = this.#inbound.then(() => {
|
|
3407
|
+
const legacyHello = event.code === 1009 && this.#legacyHelloRejected(socket);
|
|
3358
3408
|
if (socket === this.#socket && event.code === 1008 && (event.reason === "" || event.reason === "invalid token" || event.reason === "policy violation" || event.reason === "entry timeout")) {
|
|
3359
3409
|
this.#phase = { phase: "terminal", terminalReason: event.reason === "entry timeout" ? "entry_timeout" : "access_rejected" };
|
|
3360
3410
|
this.#emit();
|
|
3361
3411
|
}
|
|
3362
|
-
this.#disconnected(socket);
|
|
3412
|
+
this.#disconnected(socket, legacyHello);
|
|
3363
3413
|
});
|
|
3364
3414
|
}, 0);
|
|
3365
3415
|
};
|
|
@@ -3376,10 +3426,12 @@ var V1SyncClient = class {
|
|
|
3376
3426
|
const token = await this.#options.token();
|
|
3377
3427
|
if (socket !== this.#socket) return;
|
|
3378
3428
|
this.#phase = { phase: "recovering" };
|
|
3429
|
+
const baggage = legacyHelloServers.has(this.#options.url) ? void 0 : sessionBaggage();
|
|
3379
3430
|
const hello = {
|
|
3380
3431
|
type: "hello",
|
|
3381
3432
|
protocol: 1,
|
|
3382
3433
|
...this.#options.traceparent ? { traceparent: this.#options.traceparent } : {},
|
|
3434
|
+
...baggage ? { baggage } : {},
|
|
3383
3435
|
token,
|
|
3384
3436
|
streams: {
|
|
3385
3437
|
control: { cursor: this.#control && { revision: this.#control.revision, state_schema_version: this.#control.stateSchemaVersion, state_digest: this.#control.stateDigest } },
|
|
@@ -3389,6 +3441,7 @@ var V1SyncClient = class {
|
|
|
3389
3441
|
}
|
|
3390
3442
|
};
|
|
3391
3443
|
const collaborationExtension = this.#collaborationState.helloExtension;
|
|
3444
|
+
this.#baggageHelloSocket = baggage ? socket : null;
|
|
3392
3445
|
if (!collaborationExtension) {
|
|
3393
3446
|
this.#send(hello);
|
|
3394
3447
|
} else {
|
|
@@ -3408,7 +3461,7 @@ var V1SyncClient = class {
|
|
|
3408
3461
|
} catch (error) {
|
|
3409
3462
|
if (this.#snapshotRecoveryRequested) this.#failDigestRecovery();
|
|
3410
3463
|
else if (error instanceof V1StateDigestMismatchError) this.#recoverDigestMismatch();
|
|
3411
|
-
else this.#recover("invalid_frame");
|
|
3464
|
+
else this.#recover("invalid_frame", error instanceof V1ReplicaError && error.message === "invalid_frame" && this.#legacyHelloRejected(socket));
|
|
3412
3465
|
}
|
|
3413
3466
|
}
|
|
3414
3467
|
async #handleFrame(frame) {
|
|
@@ -3444,6 +3497,7 @@ var V1SyncClient = class {
|
|
|
3444
3497
|
const control = this.#requireControl();
|
|
3445
3498
|
this.#send({ type: "delivery_ack", stream: "control", revision: control.revision, state_digest: control.stateDigest });
|
|
3446
3499
|
}
|
|
3500
|
+
if (this.#control?.status === "ended" || frame.name === "participant_left" && frame.payload.participant_id === this.#participantId) this.#participantIdentityId = void 0;
|
|
3447
3501
|
if (frame.name === "participant_left" && frame.payload.participant_id === this.#participantId) {
|
|
3448
3502
|
this.#phase = { phase: "terminal", terminalReason: frame.payload.reason === "removed" ? "participant_removed" : "participant_inactive" };
|
|
3449
3503
|
this.#socket?.close(1e3, "participant inactive");
|
|
@@ -3529,11 +3583,13 @@ var V1SyncClient = class {
|
|
|
3529
3583
|
}
|
|
3530
3584
|
async #welcome(frame) {
|
|
3531
3585
|
if (this.#phase.phase !== "recovering" || this.#recovery) throw new V1ReplicaError("unexpected welcome");
|
|
3586
|
+
this.#baggageHelloSocket = null;
|
|
3532
3587
|
this.#collaborationState.acceptWelcome(frame);
|
|
3533
3588
|
this.#entryNegotiated = frame.entry_confirmation === true;
|
|
3534
3589
|
this.#entryGeneration = frame.entry_generation ?? this.#entryGeneration;
|
|
3535
3590
|
this.#participantId = frame.participant_id;
|
|
3536
3591
|
this.#participantGeneration = frame.participant_generation;
|
|
3592
|
+
this.#participantIdentityId = frame.mode === "terminal" ? void 0 : frame.participant_identity_id;
|
|
3537
3593
|
this.#updateLocalMediaStates();
|
|
3538
3594
|
if (frame.mode === "terminal") {
|
|
3539
3595
|
await this.#commandScheduler.settleTerminalRecovery(frame);
|
|
@@ -3547,6 +3603,7 @@ var V1SyncClient = class {
|
|
|
3547
3603
|
const control = await restoreV1Snapshot(frame.snapshot);
|
|
3548
3604
|
if (!sameHead(control, frame.head)) throw new V1ReplicaError("snapshot head mismatch");
|
|
3549
3605
|
this.#control = control;
|
|
3606
|
+
if (control.status === "ended") this.#options.mediaPlane?.remotePublicationsChanged?.([]);
|
|
3550
3607
|
this.#snapshotRevision = control.revision;
|
|
3551
3608
|
this.#snapshotRecoveryRequested = false;
|
|
3552
3609
|
this.#controlHeads.clear();
|
|
@@ -3592,7 +3649,7 @@ var V1SyncClient = class {
|
|
|
3592
3649
|
previous.find((candidate) => mediaKey(candidate) === mediaKey(item)),
|
|
3593
3650
|
item
|
|
3594
3651
|
);
|
|
3595
|
-
if (!this.#mediaEnded()) this.#options.mediaPlane?.remotePublicationsChanged?.();
|
|
3652
|
+
if (!this.#mediaEnded()) this.#options.mediaPlane?.remotePublicationsChanged?.(this.#media?.items ?? []);
|
|
3596
3653
|
} else {
|
|
3597
3654
|
this.#presence = { projectionId: frame.projection_id, sequence: 0, items: frame.items.map(presenceItem) };
|
|
3598
3655
|
this.#presenceEventEvidence.clear();
|
|
@@ -3611,7 +3668,7 @@ var V1SyncClient = class {
|
|
|
3611
3668
|
rememberBoundedEvidence(this.#mediaEventEvidence, frame.sequence, frameSignature(frame), MAX_PROJECTION_EVENT_EVIDENCE);
|
|
3612
3669
|
this.#notifyRemotePublicationResumed(previous, item);
|
|
3613
3670
|
if (!this.#mediaEnded() && item.participantId !== this.#participantId && (previous?.enabled !== item.enabled || previous?.publicationId !== item.publicationId)) {
|
|
3614
|
-
this.#options.mediaPlane?.remotePublicationsChanged?.();
|
|
3671
|
+
this.#options.mediaPlane?.remotePublicationsChanged?.(this.#media?.items ?? []);
|
|
3615
3672
|
}
|
|
3616
3673
|
} else {
|
|
3617
3674
|
if (this.#acceptProjectionDuplicate(this.#presence, this.#presenceEventEvidence, frame)) return;
|
|
@@ -3683,7 +3740,7 @@ var V1SyncClient = class {
|
|
|
3683
3740
|
if (evidence.get(frame.sequence) !== frameSignature(frame)) throw new V1ReplicaError("conflicting or unprovable duplicate projection event");
|
|
3684
3741
|
return true;
|
|
3685
3742
|
}
|
|
3686
|
-
#disconnected(socket) {
|
|
3743
|
+
#disconnected(socket, retryNow = false) {
|
|
3687
3744
|
if (socket !== this.#socket) return;
|
|
3688
3745
|
this.#socket = null;
|
|
3689
3746
|
this.#clearDial();
|
|
@@ -3694,6 +3751,7 @@ var V1SyncClient = class {
|
|
|
3694
3751
|
this.#commandScheduler.disconnect();
|
|
3695
3752
|
this.#media = null;
|
|
3696
3753
|
this.#presence = null;
|
|
3754
|
+
this.#options.mediaPlane?.remotePublicationsChanged?.([], this.#mediaEnded() ? "available" : "unavailable");
|
|
3697
3755
|
this.#rejectRequests("disconnected_before_delivery");
|
|
3698
3756
|
this.#collaborationState.disconnect("disconnected_before_delivery");
|
|
3699
3757
|
if (!this.#started || this.#phase.phase === "terminal") return;
|
|
@@ -3701,6 +3759,13 @@ var V1SyncClient = class {
|
|
|
3701
3759
|
this.#emit();
|
|
3702
3760
|
if (!this.#transportAvailable) return;
|
|
3703
3761
|
this.#clearReconnect();
|
|
3762
|
+
if (retryNow) {
|
|
3763
|
+
this.#reconnectTimer = this.#clock().setTimeout(() => {
|
|
3764
|
+
this.#reconnectTimer = void 0;
|
|
3765
|
+
this.#connect();
|
|
3766
|
+
}, 0);
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3704
3769
|
const configuredDelay = this.#options.reconnectDelayMs;
|
|
3705
3770
|
const exponentialAttempt = configuredDelay === void 0 ? Math.max(0, this.#reconnectAttempt - FAST_RECONNECT_ATTEMPTS + 1) : this.#reconnectAttempt;
|
|
3706
3771
|
const baseDelay = configuredDelay === 0 ? 0 : Math.min(MAX_RECONNECT_DELAY_MS, (configuredDelay ?? DEFAULT_RECONNECT_DELAY_MS) * 2 ** Math.min(exponentialAttempt, 5));
|
|
@@ -3711,11 +3776,21 @@ var V1SyncClient = class {
|
|
|
3711
3776
|
this.#connect();
|
|
3712
3777
|
}, delay);
|
|
3713
3778
|
}
|
|
3714
|
-
#recover(reason) {
|
|
3779
|
+
#recover(reason, retryNow = false) {
|
|
3715
3780
|
const socket = this.#socket;
|
|
3716
3781
|
if (!socket) return;
|
|
3717
3782
|
socket.close(CLIENT_RESTART_CLOSE_CODE, reason);
|
|
3718
|
-
this.#disconnected(socket);
|
|
3783
|
+
this.#disconnected(socket, retryNow);
|
|
3784
|
+
}
|
|
3785
|
+
/**
|
|
3786
|
+
* A Sync that predates the session ID rejects any hello with an unknown `baggage` field before welcome. The first
|
|
3787
|
+
* rejection marks that server as legacy, so the retry and every later reconnect send the hello without it.
|
|
3788
|
+
*/
|
|
3789
|
+
#legacyHelloRejected(socket) {
|
|
3790
|
+
if (this.#baggageHelloSocket !== socket) return false;
|
|
3791
|
+
this.#baggageHelloSocket = null;
|
|
3792
|
+
legacyHelloServers.add(this.#options.url);
|
|
3793
|
+
return true;
|
|
3719
3794
|
}
|
|
3720
3795
|
#recoverDigestMismatch() {
|
|
3721
3796
|
const now = this.#clock().now();
|
|
@@ -3900,7 +3975,7 @@ function mediaItem(item) {
|
|
|
3900
3975
|
return { participantId: item.participant_id, source: item.source, enabled: item.enabled, publicationId: item.publication_id };
|
|
3901
3976
|
}
|
|
3902
3977
|
function presenceItem(item) {
|
|
3903
|
-
return { participantId: item.participant_id, state: item.state, speaking: item.speaking, activeSpeaker: item.active_speaker };
|
|
3978
|
+
return { participantId: item.participant_id, admissionRevision: item.admission_revision, identityId: item.identity_id, deviceType: item.device_type, state: item.state, speaking: item.speaking, activeSpeaker: item.active_speaker };
|
|
3904
3979
|
}
|
|
3905
3980
|
function mediaKey(item) {
|
|
3906
3981
|
return `${item.participantId}:${item.source}`;
|
|
@@ -3928,6 +4003,8 @@ function validMediaPublication(publication) {
|
|
|
3928
4003
|
}
|
|
3929
4004
|
function entryUrl(value, generation, confirmation) {
|
|
3930
4005
|
const url = new URL(value);
|
|
4006
|
+
url.searchParams.set("participant_devices", "1");
|
|
4007
|
+
url.searchParams.set("participant_device_admissions", "2");
|
|
3931
4008
|
url.searchParams.set("entry_generation", generation === null ? "new" : String(generation));
|
|
3932
4009
|
if (confirmation) url.searchParams.set("entry_confirmation", "1");
|
|
3933
4010
|
return url.toString();
|