@q9labsai/chalk-client 4.1.11 → 4.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FJXN6BVS.js → chunk-DV433YIW.js} +51 -22
- package/dist/{chunk-JPNE4K2L.js → chunk-EBACU63B.js} +3 -2
- package/dist/{chunk-DFLTEYWX.js → chunk-IYRVIFWN.js} +3 -3
- package/dist/{chunk-QVHR3RMB.js → chunk-NKOKY2AE.js} +21 -1
- package/dist/{chunk-C4BIONCP.js → chunk-S5OUHFGC.js} +174 -13
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/connection/dependencies.d.ts +3 -1
- package/dist/connection/dependencies.d.ts.map +1 -1
- package/dist/connection/index.d.ts +2 -0
- package/dist/connection/index.d.ts.map +1 -1
- package/dist/connection/lifecycle.d.ts.map +1 -1
- package/dist/effect.js +5 -5
- package/dist/generated/http-api.d.ts +1 -0
- package/dist/generated/http-api.d.ts.map +1 -1
- package/dist/generated/schemas.d.ts +2 -0
- package/dist/generated/schemas.d.ts.map +1 -1
- package/dist/index.js +25 -25
- package/dist/media/client.d.ts.map +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/transport.d.ts.map +1 -1
- package/dist/media/types.d.ts +12 -1
- package/dist/media/types.d.ts.map +1 -1
- package/dist/public-invites/index.js +3 -3
- package/dist/public-invites/types.d.ts +1 -0
- package/dist/public-invites/types.d.ts.map +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/types.d.cts +1 -1
- package/dist/server/types.d.ts +1 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/space-client/core.d.ts +2 -0
- package/dist/space-client/core.d.ts.map +1 -1
- package/dist/space-client/participants-controller.d.ts.map +1 -1
- package/dist/telemetry/attributes.d.ts +1 -1
- package/dist/telemetry/attributes.d.ts.map +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/rtc.d.ts +6 -0
- package/dist/telemetry/rtc.d.ts.map +1 -1
- package/dist/whiteboard/types.d.ts +1 -0
- package/dist/whiteboard/types.d.ts.map +1 -1
- package/dist/whiteboard/v1-client.d.ts +2 -1
- package/dist/whiteboard/v1-client.d.ts.map +1 -1
- package/dist/whiteboard/v1-codec.d.ts +1 -2
- package/dist/whiteboard/v1-codec.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/episode-diagnostic-runtime.test.helpers.d.ts +0 -3
- package/dist/episode-diagnostic-runtime.test.helpers.d.ts.map +0 -1
- package/dist/space-client/controller-parity.test.helpers.d.ts +0 -151
- package/dist/space-client/controller-parity.test.helpers.d.ts.map +0 -1
- package/dist/space-client/media-controller.test.helpers.d.ts +0 -105
- package/dist/space-client/media-controller.test.helpers.d.ts.map +0 -1
|
@@ -2,21 +2,21 @@ import {
|
|
|
2
2
|
CloudflareRTKClient,
|
|
3
3
|
CloudflareSFUClient,
|
|
4
4
|
createCloudflareSFUHTTPTransport
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-S5OUHFGC.js";
|
|
6
6
|
import {
|
|
7
7
|
AccessGrantError,
|
|
8
8
|
requireParsedAccessGrant,
|
|
9
9
|
validEpisodeDiagnosticCredential
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-EBACU63B.js";
|
|
11
|
+
import {
|
|
12
|
+
createTraceContext,
|
|
13
|
+
parseTraceparent
|
|
14
|
+
} from "./chunk-NKOKY2AE.js";
|
|
11
15
|
import {
|
|
12
16
|
createBrowserSyncLifecycle,
|
|
13
17
|
createBrowserWebSocketFactory,
|
|
14
18
|
createV1SyncClient
|
|
15
19
|
} from "./chunk-5VXGLI3T.js";
|
|
16
|
-
import {
|
|
17
|
-
createTraceContext,
|
|
18
|
-
parseTraceparent
|
|
19
|
-
} from "./chunk-QVHR3RMB.js";
|
|
20
20
|
|
|
21
21
|
// src/access/manager.ts
|
|
22
22
|
import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
|
|
@@ -885,6 +885,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
885
885
|
#options;
|
|
886
886
|
#store;
|
|
887
887
|
#listeners = /* @__PURE__ */ new Set();
|
|
888
|
+
#summaryListeners = /* @__PURE__ */ new Set();
|
|
888
889
|
#operations = /* @__PURE__ */ new Map();
|
|
889
890
|
#reservedOperationIds = /* @__PURE__ */ new Set();
|
|
890
891
|
#awaitingOperationIds = /* @__PURE__ */ new Set();
|
|
@@ -989,6 +990,11 @@ var ChalkWhiteboardV1Client = class {
|
|
|
989
990
|
if (this.#snapshots.size === 0 && !this.#waitingForOperations) void this.#requestSnapshot().catch(() => void 0);
|
|
990
991
|
return () => this.#listeners.delete(listener);
|
|
991
992
|
}
|
|
993
|
+
subscribeSummary(listener) {
|
|
994
|
+
this.#summaryListeners.add(listener);
|
|
995
|
+
this.#notifySummaryListener(listener, this.#summary());
|
|
996
|
+
return () => this.#summaryListeners.delete(listener);
|
|
997
|
+
}
|
|
992
998
|
submitUpdate(input) {
|
|
993
999
|
this.#assertLive("submit_update");
|
|
994
1000
|
if (!this.#canDraw || !this.#capabilities.includes("drawWhiteboard")) {
|
|
@@ -1480,7 +1486,15 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1480
1486
|
#publishSummary(status, summaryError) {
|
|
1481
1487
|
this.#summaryStatus = status;
|
|
1482
1488
|
this.#summaryError = summaryError;
|
|
1483
|
-
const summary =
|
|
1489
|
+
const summary = this.#summary();
|
|
1490
|
+
try {
|
|
1491
|
+
this.#options.onSummary?.(summary);
|
|
1492
|
+
} catch {
|
|
1493
|
+
}
|
|
1494
|
+
for (const listener of this.#summaryListeners) this.#notifySummaryListener(listener, summary);
|
|
1495
|
+
}
|
|
1496
|
+
#summary() {
|
|
1497
|
+
return {
|
|
1484
1498
|
status: this.#summaryStatus,
|
|
1485
1499
|
sceneId: this.#sceneId,
|
|
1486
1500
|
revision: this.#revision,
|
|
@@ -1490,8 +1504,10 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1490
1504
|
presenting: this.#presenting,
|
|
1491
1505
|
error: this.#summaryError
|
|
1492
1506
|
};
|
|
1507
|
+
}
|
|
1508
|
+
#notifySummaryListener(listener, summary) {
|
|
1493
1509
|
try {
|
|
1494
|
-
|
|
1510
|
+
listener(summary);
|
|
1495
1511
|
} catch {
|
|
1496
1512
|
}
|
|
1497
1513
|
}
|
|
@@ -1739,9 +1755,15 @@ function createMediaClient(apiBaseURL, input) {
|
|
|
1739
1755
|
);
|
|
1740
1756
|
}
|
|
1741
1757
|
const { subject } = input.access;
|
|
1758
|
+
const replaceMediaConnection = input.replaceMediaConnection;
|
|
1742
1759
|
const client = new CloudflareSFUClient({
|
|
1743
1760
|
bootstrap: input.access.media.clientPayload,
|
|
1744
1761
|
participantId: subject.participantId,
|
|
1762
|
+
replaceMediaConnection: replaceMediaConnection ? async () => {
|
|
1763
|
+
const replacement = await replaceMediaConnection();
|
|
1764
|
+
if (replacement.provider !== "cloudflare_sfu") throw new TypeError("The Cloudflare SFU adapter requires a Cloudflare SFU access grant");
|
|
1765
|
+
return replacement.clientPayload;
|
|
1766
|
+
} : void 0,
|
|
1745
1767
|
transport: createCloudflareSFUHTTPTransport({
|
|
1746
1768
|
apiBaseURL,
|
|
1747
1769
|
credential: input.credential,
|
|
@@ -1751,6 +1773,7 @@ function createMediaClient(apiBaseURL, input) {
|
|
|
1751
1773
|
participantId: subject.participantId
|
|
1752
1774
|
}),
|
|
1753
1775
|
onError: input.onFailure,
|
|
1776
|
+
...input.recordRtcSummary ? { onRtcSummary: input.recordRtcSummary } : {},
|
|
1754
1777
|
onScreenEnded: input.onScreenEnded
|
|
1755
1778
|
});
|
|
1756
1779
|
return createSFUConnectionMediaClient(client);
|
|
@@ -2135,6 +2158,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2135
2158
|
try: () => platform.createMediaClient({
|
|
2136
2159
|
access: grant,
|
|
2137
2160
|
credential: () => toPromise(access.getMediaToken()),
|
|
2161
|
+
replaceMediaConnection: () => toPromise(access.refresh("media_recovery", true)).then((replacement) => replacement.media),
|
|
2162
|
+
...options.recordRtcSummary ? { recordRtcSummary: options.recordRtcSummary } : {},
|
|
2138
2163
|
onFailure: () => enqueueBackground(handleMediaFailure()),
|
|
2139
2164
|
onScreenEnded: () => enqueueBackground(notifyScreenEnded())
|
|
2140
2165
|
}),
|
|
@@ -3480,7 +3505,8 @@ var MediaControllerRuntime = class {
|
|
|
3480
3505
|
if (isAccessInvalid2(input.cause)) return Effect5.fail(input.cause);
|
|
3481
3506
|
input.restoreIntent?.();
|
|
3482
3507
|
const rollback = input.track ? this.#discardCaptured(input.source, input.track, input.media, input.prepared) : input.fallback;
|
|
3483
|
-
const
|
|
3508
|
+
const captureWasUnavailable = input.action === "startScreenShare" && !input.prepared && input.track === null;
|
|
3509
|
+
const failure2 = isPermissionDenied(input.cause) || captureWasUnavailable ? captureError(input.cause, input.action, input.permissionMessage) : input.cause;
|
|
3484
3510
|
return rollback.pipe(Effect5.andThen(Effect5.sync(() => this.#publish())), Effect5.andThen(Effect5.fail(failure2)));
|
|
3485
3511
|
}
|
|
3486
3512
|
#assertActivePorts(ports, action) {
|
|
@@ -3663,7 +3689,7 @@ function sameDeviceList(left, right) {
|
|
|
3663
3689
|
}
|
|
3664
3690
|
function captureError(cause, action, permissionMessage) {
|
|
3665
3691
|
if (isPermissionDenied(cause)) return new ConnectionError({ code: "permission_denied", action, recoverable: true, message: permissionMessage }, { cause });
|
|
3666
|
-
return new ConnectionError({ code: "unsupported_environment", action, recoverable: false, message: "Media capture is unavailable" }, { cause });
|
|
3692
|
+
return new ConnectionError({ code: "unsupported_environment", action, recoverable: false, message: action === "startScreenShare" ? "Screen sharing is unavailable in this browser." : "Media capture is unavailable" }, { cause });
|
|
3667
3693
|
}
|
|
3668
3694
|
function isPermissionDenied(cause) {
|
|
3669
3695
|
return cause instanceof DOMException && (cause.name === "NotAllowedError" || cause.name === "SecurityError");
|
|
@@ -3893,16 +3919,24 @@ function participantPresence(participantId, presence) {
|
|
|
3893
3919
|
return Object.freeze({ state: item.state, speaking: item.speaking, activeSpeaker: item.activeSpeaker });
|
|
3894
3920
|
}
|
|
3895
3921
|
function sameParticipants(left, right) {
|
|
3896
|
-
return sameArray(
|
|
3897
|
-
left.roster,
|
|
3898
|
-
right.roster,
|
|
3899
|
-
(current, next) => current.participantId === next.participantId && current.displayName === next.displayName && current.role === next.role && current.handRaised === next.handRaised && sameValues(current.eligibleRoles, next.eligibleRoles) && sameValues(current.capabilities, next.capabilities) && current.media.microphone === next.media.microphone && current.media.camera === next.media.camera && current.media.screenShare === next.media.screenShare && current.presence.state === next.presence.state && current.presence.speaking === next.presence.speaking && current.presence.activeSpeaker === next.presence.activeSpeaker
|
|
3900
|
-
) && sameArray(
|
|
3922
|
+
return sameArray(left.roster, right.roster, sameParticipant) && sameArray(
|
|
3901
3923
|
left.admissionQueue,
|
|
3902
3924
|
right.admissionQueue,
|
|
3903
3925
|
(current, next) => current.requestId === next.requestId && current.participantId === next.participantId && current.displayName === next.displayName && current.initialRole === next.initialRole && current.expiresAt === next.expiresAt && sameValues(current.eligibleRoles, next.eligibleRoles)
|
|
3904
3926
|
);
|
|
3905
3927
|
}
|
|
3928
|
+
function sameParticipant(left, right) {
|
|
3929
|
+
return sameParticipantProfile(left, right) && sameParticipantMedia(left, right) && sameParticipantPresence(left, right);
|
|
3930
|
+
}
|
|
3931
|
+
function sameParticipantProfile(left, right) {
|
|
3932
|
+
return left.participantId === right.participantId && left.displayName === right.displayName && left.role === right.role && left.handRaised === right.handRaised && sameValues(left.eligibleRoles, right.eligibleRoles) && sameValues(left.capabilities, right.capabilities);
|
|
3933
|
+
}
|
|
3934
|
+
function sameParticipantMedia(left, right) {
|
|
3935
|
+
return left.media.microphone === right.media.microphone && left.media.camera === right.media.camera && left.media.screenShare === right.media.screenShare;
|
|
3936
|
+
}
|
|
3937
|
+
function sameParticipantPresence(left, right) {
|
|
3938
|
+
return left.presence.state === right.presence.state && left.presence.speaking === right.presence.speaking && left.presence.activeSpeaker === right.presence.activeSpeaker;
|
|
3939
|
+
}
|
|
3906
3940
|
function sameSelf(left, right) {
|
|
3907
3941
|
return left.participantId === right.participantId && left.displayName === right.displayName && left.role === right.role && left.handRaised === right.handRaised && sameValues(left.capabilities, right.capabilities);
|
|
3908
3942
|
}
|
|
@@ -4797,7 +4831,7 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
|
|
|
4797
4831
|
now: baseDependencies.clock.now,
|
|
4798
4832
|
setTimeout: baseDependencies.clock.setTimeout,
|
|
4799
4833
|
clearTimeout: baseDependencies.clock.clearTimeout,
|
|
4800
|
-
release: { id: "chalk-client@4.1.
|
|
4834
|
+
release: { id: "chalk-client@4.1.13" }
|
|
4801
4835
|
});
|
|
4802
4836
|
const diagnosticSyncClients = /* @__PURE__ */ new Set();
|
|
4803
4837
|
const dependencies = {
|
|
@@ -4818,6 +4852,7 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
|
|
|
4818
4852
|
initialMicrophoneEnabled: platform.initialMicrophoneEnabled,
|
|
4819
4853
|
initialCameraEnabled: platform.initialCameraEnabled,
|
|
4820
4854
|
telemetry: platform.telemetry,
|
|
4855
|
+
recordRtcSummary: platform.recordRtcSummary,
|
|
4821
4856
|
dependencies,
|
|
4822
4857
|
diagnostics: {
|
|
4823
4858
|
onEvent: (event) => {
|
|
@@ -4861,7 +4896,6 @@ var SpaceClientCore = class {
|
|
|
4861
4896
|
#previous;
|
|
4862
4897
|
#unsubscribeConnection = null;
|
|
4863
4898
|
#unsubscribeStore = null;
|
|
4864
|
-
#unsupportedReported = false;
|
|
4865
4899
|
#leaveOperation;
|
|
4866
4900
|
#participantJoinOperation;
|
|
4867
4901
|
#syncConnectOperation;
|
|
@@ -4998,11 +5032,6 @@ var SpaceClientCore = class {
|
|
|
4998
5032
|
#publishConnectionDiagnostics() {
|
|
4999
5033
|
const snapshot = this.#connection.getSnapshot();
|
|
5000
5034
|
if (!this.#episodeDiagnostics || snapshot.state !== "live") return;
|
|
5001
|
-
if (!this.#unsupportedReported) {
|
|
5002
|
-
this.#unsupportedReported = true;
|
|
5003
|
-
const unsupported = this.#episodeDiagnostics.startOperation("whiteboard.unsupported");
|
|
5004
|
-
unsupported?.succeed({ status: "unsupported" });
|
|
5005
|
-
}
|
|
5006
5035
|
if (!this.#participantJoinOperation) {
|
|
5007
5036
|
this.#participantJoinOperation = this.#episodeDiagnostics.startOperation("participant.join");
|
|
5008
5037
|
this.#participantJoinOperation?.succeed({ status: "live" });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
journeyHeaders
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NKOKY2AE.js";
|
|
4
4
|
|
|
5
5
|
// src/generated/schemas.ts
|
|
6
6
|
import { Schema, SchemaGetter } from "effect";
|
|
@@ -757,7 +757,8 @@ var RecordingReservationSchema = Schema.Struct({
|
|
|
757
757
|
updated_at: DateTimeStringSchema
|
|
758
758
|
});
|
|
759
759
|
var RefreshSpacePublicInviteAccessRequestSchema = Schema.Struct({
|
|
760
|
-
media_proof: Schema.String.check(Schema.isMinLength(1))
|
|
760
|
+
media_proof: Schema.String.check(Schema.isMinLength(1)),
|
|
761
|
+
replace_media_connection: Schema.Boolean
|
|
761
762
|
});
|
|
762
763
|
var RegionsSchema = Schema.Struct({
|
|
763
764
|
regions: Schema.Array(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createChalkEffectClient,
|
|
3
3
|
parseAccessGrant
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EBACU63B.js";
|
|
5
5
|
|
|
6
6
|
// src/public-invites/client.ts
|
|
7
7
|
import { Effect } from "effect";
|
|
@@ -59,7 +59,7 @@ function createChalkPublicClient(options) {
|
|
|
59
59
|
const response = await Effect.runPromise(
|
|
60
60
|
client.spaces.refreshSpacePublicInviteAccess({
|
|
61
61
|
headers: { "X-Chalk-Arrival-Handle": normalized.arrivalHandle },
|
|
62
|
-
payload: { media_proof: required(normalized.mediaProof, "mediaProof") }
|
|
62
|
+
payload: { media_proof: required(normalized.mediaProof, "mediaProof"), replace_media_connection: normalized.replaceMediaConnection ?? false }
|
|
63
63
|
})
|
|
64
64
|
);
|
|
65
65
|
return parseAccessGrant(response);
|
|
@@ -109,7 +109,7 @@ function arrivalReference(reference) {
|
|
|
109
109
|
return { arrivalHandle: required(reference.arrivalHandle, "arrivalHandle"), guestCredential: reference.guestCredential };
|
|
110
110
|
}
|
|
111
111
|
function refreshInput(input, arrival) {
|
|
112
|
-
const values = typeof input === "string" ? { arrivalHandle: arrival?.arrivalHandle, guestCredential: arrival?.guestCredential, mediaProof: input } : { arrivalHandle: input.arrivalHandle ?? arrival?.arrivalHandle, guestCredential: input.guestCredential ?? arrival?.guestCredential, mediaProof: input.mediaProof };
|
|
112
|
+
const values = typeof input === "string" ? { arrivalHandle: arrival?.arrivalHandle, guestCredential: arrival?.guestCredential, mediaProof: input } : { arrivalHandle: input.arrivalHandle ?? arrival?.arrivalHandle, guestCredential: input.guestCredential ?? arrival?.guestCredential, mediaProof: input.mediaProof, replaceMediaConnection: input.replaceMediaConnection };
|
|
113
113
|
return {
|
|
114
114
|
...values,
|
|
115
115
|
arrivalHandle: required(values.arrivalHandle ?? "", "arrivalHandle")
|
|
@@ -22,6 +22,8 @@ var TELEMETRY_BUILT_IN_ATTRIBUTE_KEYS = [
|
|
|
22
22
|
"connection_state",
|
|
23
23
|
"ice_connection_state",
|
|
24
24
|
"signaling_state",
|
|
25
|
+
"dtls_state",
|
|
26
|
+
"selected_candidate_pair_state",
|
|
25
27
|
"bytes_received",
|
|
26
28
|
"bytes_sent",
|
|
27
29
|
"frames_dropped",
|
|
@@ -615,7 +617,9 @@ function summarizeRtcStats(stats) {
|
|
|
615
617
|
packetsReceived: 0,
|
|
616
618
|
packetsSent: 0,
|
|
617
619
|
roundTripTimeSamples: [],
|
|
618
|
-
transportEntries: 0
|
|
620
|
+
transportEntries: 0,
|
|
621
|
+
dtlsStates: [],
|
|
622
|
+
selectedCandidatePairStates: []
|
|
619
623
|
};
|
|
620
624
|
for (const stat of stats) {
|
|
621
625
|
statHandlers[stat.type ?? ""]?.(accumulator, stat);
|
|
@@ -634,6 +638,8 @@ function rtcSummaryAttributes(connection, stats) {
|
|
|
634
638
|
packets_received: summary.packetsReceived,
|
|
635
639
|
packets_sent: summary.packetsSent,
|
|
636
640
|
transport_entries: summary.transportEntries,
|
|
641
|
+
...summary.dtlsState !== void 0 ? { dtls_state: summary.dtlsState } : {},
|
|
642
|
+
...summary.selectedCandidatePairState !== void 0 ? { selected_candidate_pair_state: summary.selectedCandidatePairState } : {},
|
|
637
643
|
...connection.connectionState !== void 0 ? { connection_state: connection.connectionState } : {},
|
|
638
644
|
...connection.iceConnectionState !== void 0 ? { ice_connection_state: connection.iceConnectionState } : {},
|
|
639
645
|
...summary.jitterMs !== void 0 ? { jitter_ms: summary.jitterMs } : {},
|
|
@@ -663,6 +669,13 @@ function accumulateOutbound(accumulator, stat) {
|
|
|
663
669
|
function accumulateTransport(accumulator, stat) {
|
|
664
670
|
accumulator.transportEntries += 1;
|
|
665
671
|
appendFinite(accumulator.roundTripTimeSamples, stat.roundTripTime);
|
|
672
|
+
appendRtcState(accumulator.dtlsStates, stat.dtlsState, ["new", "connecting", "connected", "closed", "failed"]);
|
|
673
|
+
if (stat.type === "candidate-pair" && (stat.selected === true || stat.nominated === true)) {
|
|
674
|
+
appendRtcState(accumulator.selectedCandidatePairStates, stat.state, ["frozen", "waiting", "in-progress", "failed", "succeeded"]);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
function appendRtcState(states, value, allowed) {
|
|
678
|
+
if (value !== void 0 && allowed.includes(value) && !states.includes(value)) states.push(value);
|
|
666
679
|
}
|
|
667
680
|
function appendFinite(samples, value) {
|
|
668
681
|
if (isFiniteNumber(value)) samples.push(value);
|
|
@@ -670,6 +683,8 @@ function appendFinite(samples, value) {
|
|
|
670
683
|
function finalizeAccumulator(accumulator) {
|
|
671
684
|
const jitterMs = averageMilliseconds(accumulator.jitterSamples);
|
|
672
685
|
const roundTripTimeMs = averageMilliseconds(accumulator.roundTripTimeSamples);
|
|
686
|
+
const dtlsState = lastValue(accumulator.dtlsStates);
|
|
687
|
+
const selectedCandidatePairState = lastValue(accumulator.selectedCandidatePairStates);
|
|
673
688
|
return {
|
|
674
689
|
bytesReceived: accumulator.bytesReceived,
|
|
675
690
|
bytesSent: accumulator.bytesSent,
|
|
@@ -680,10 +695,15 @@ function finalizeAccumulator(accumulator) {
|
|
|
680
695
|
packetsReceived: accumulator.packetsReceived,
|
|
681
696
|
packetsSent: accumulator.packetsSent,
|
|
682
697
|
transportEntries: accumulator.transportEntries,
|
|
698
|
+
...dtlsState !== void 0 ? { dtlsState } : {},
|
|
699
|
+
...selectedCandidatePairState !== void 0 ? { selectedCandidatePairState } : {},
|
|
683
700
|
...jitterMs !== void 0 ? { jitterMs } : {},
|
|
684
701
|
...roundTripTimeMs !== void 0 ? { roundTripTimeMs } : {}
|
|
685
702
|
};
|
|
686
703
|
}
|
|
704
|
+
function lastValue(values) {
|
|
705
|
+
return values.at(-1);
|
|
706
|
+
}
|
|
687
707
|
function averageMilliseconds(samples) {
|
|
688
708
|
if (samples.length === 0) return void 0;
|
|
689
709
|
return Math.round(samples.reduce((total, sample) => total + sample, 0) / samples.length * 1e3);
|
|
@@ -25,12 +25,14 @@ function resolveMediaTarget(participantId, stopped, tracks, target) {
|
|
|
25
25
|
|
|
26
26
|
// src/media/types.ts
|
|
27
27
|
var CloudflareSFUError = class extends Error {
|
|
28
|
-
constructor(message, code) {
|
|
28
|
+
constructor(message, code, options = {}) {
|
|
29
29
|
super(message);
|
|
30
30
|
this.code = code;
|
|
31
|
+
this.options = options;
|
|
31
32
|
this.name = "CloudflareSFUError";
|
|
32
33
|
}
|
|
33
34
|
code;
|
|
35
|
+
options;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
// src/media/tracks.ts
|
|
@@ -138,10 +140,12 @@ var CloudflareSFUClient = class {
|
|
|
138
140
|
#localListeners = /* @__PURE__ */ new Set();
|
|
139
141
|
#onError;
|
|
140
142
|
#onRemoteTrack;
|
|
143
|
+
#onRtcSummary;
|
|
141
144
|
#onScreenEnded;
|
|
142
145
|
#participantId;
|
|
143
146
|
#peerConnectionFactory;
|
|
144
147
|
#pollIntervalMs;
|
|
148
|
+
#replaceMediaConnection;
|
|
145
149
|
#remoteListeners = /* @__PURE__ */ new Set();
|
|
146
150
|
#snapshotListeners = /* @__PURE__ */ new Set();
|
|
147
151
|
#localTracks = /* @__PURE__ */ new Map();
|
|
@@ -149,8 +153,11 @@ var CloudflareSFUClient = class {
|
|
|
149
153
|
#remoteTracks = /* @__PURE__ */ new Map();
|
|
150
154
|
#bootstrap;
|
|
151
155
|
#connection;
|
|
156
|
+
#connectionEpoch = 0;
|
|
152
157
|
#cursor = null;
|
|
153
158
|
#generation = 0;
|
|
159
|
+
#negotiatedGeneration = null;
|
|
160
|
+
#replacementAttemptedGeneration = null;
|
|
154
161
|
#polling = false;
|
|
155
162
|
#pollTimer;
|
|
156
163
|
#sdpTail = Promise.resolve();
|
|
@@ -164,8 +171,10 @@ var CloudflareSFUClient = class {
|
|
|
164
171
|
this.#bootstrap = options.bootstrap;
|
|
165
172
|
this.#transport = options.transport;
|
|
166
173
|
this.#pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
174
|
+
this.#replaceMediaConnection = options.replaceMediaConnection;
|
|
167
175
|
this.#onError = options.onError;
|
|
168
176
|
this.#onRemoteTrack = options.onRemoteTrack;
|
|
177
|
+
this.#onRtcSummary = options.onRtcSummary;
|
|
169
178
|
this.#onScreenEnded = options.onScreenEnded;
|
|
170
179
|
this.#peerConnectionFactory = options.peerConnectionFactory;
|
|
171
180
|
this.#connection = this.#createPeerConnection(options.bootstrap);
|
|
@@ -176,7 +185,7 @@ var CloudflareSFUClient = class {
|
|
|
176
185
|
remoteTracks: EMPTY_REMOTE,
|
|
177
186
|
failure: null
|
|
178
187
|
});
|
|
179
|
-
this.#observeConnection(this.#connection, this.#generation);
|
|
188
|
+
this.#observeConnection(this.#connection, this.#generation, this.#connectionEpoch);
|
|
180
189
|
}
|
|
181
190
|
getSnapshot() {
|
|
182
191
|
return this.#snapshot;
|
|
@@ -276,16 +285,19 @@ var CloudflareSFUClient = class {
|
|
|
276
285
|
const options = "connectionId" in input ? { bootstrap: input } : input;
|
|
277
286
|
validateBootstrap(options.bootstrap);
|
|
278
287
|
const generation = ++this.#generation;
|
|
288
|
+
const connectionEpoch = ++this.#connectionEpoch;
|
|
279
289
|
this.#polling = false;
|
|
280
290
|
this.#clearPoll();
|
|
281
291
|
this.#disposeConnection(false);
|
|
282
292
|
this.#reusableLocalTransceivers.clear();
|
|
283
293
|
this.#clearRemoteTracks();
|
|
284
294
|
this.#cursor = null;
|
|
295
|
+
this.#negotiatedGeneration = null;
|
|
296
|
+
this.#replacementAttemptedGeneration = null;
|
|
285
297
|
this.#bootstrap = options.bootstrap;
|
|
286
298
|
if (options.transport) this.#transport = options.transport;
|
|
287
299
|
this.#connection = this.#createPeerConnection(options.bootstrap);
|
|
288
|
-
this.#observeConnection(this.#connection, generation);
|
|
300
|
+
this.#observeConnection(this.#connection, generation, connectionEpoch);
|
|
289
301
|
for (const state of this.#localTracks.values()) {
|
|
290
302
|
state.transceiver = null;
|
|
291
303
|
state.enabled = false;
|
|
@@ -310,6 +322,7 @@ var CloudflareSFUClient = class {
|
|
|
310
322
|
if (this.#stopped) return;
|
|
311
323
|
this.#stopped = true;
|
|
312
324
|
this.#generation++;
|
|
325
|
+
this.#connectionEpoch++;
|
|
313
326
|
this.#clearPoll();
|
|
314
327
|
this.#polling = false;
|
|
315
328
|
this.#disposeConnection(true);
|
|
@@ -367,9 +380,21 @@ var CloudflareSFUClient = class {
|
|
|
367
380
|
}
|
|
368
381
|
async #publishPreparedTracks(states, generation) {
|
|
369
382
|
if (states.length === 0) return;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
383
|
+
await this.#serializeSDP(async () => {
|
|
384
|
+
await this.#replaceDormantConnectionBeforeNegotiation(generation);
|
|
385
|
+
try {
|
|
386
|
+
await this.#publishPreparedTracksSerialized(states, generation, this.#connection, this.#bootstrap.connectionId);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
if (!this.#replaceMediaConnection || !this.#isRetryableConnectionFailure(error) || this.#replacementAttemptedGeneration === generation) throw error;
|
|
389
|
+
await this.#replaceMediaConnectionForRecovery(generation);
|
|
390
|
+
await this.#publishPreparedTracksSerialized(
|
|
391
|
+
[...this.#localTracks.values()].filter((state) => (state.desiredEnabled || states.includes(state)) && state.track.readyState !== "ended"),
|
|
392
|
+
generation,
|
|
393
|
+
this.#connection,
|
|
394
|
+
this.#bootstrap.connectionId
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
373
398
|
}
|
|
374
399
|
async #publishPreparedTracksSerialized(states, generation, connection, connectionId) {
|
|
375
400
|
this.#requireGeneration(generation);
|
|
@@ -382,6 +407,7 @@ var CloudflareSFUClient = class {
|
|
|
382
407
|
const response = await this.#negotiateLocalPublications(connection, connectionId, publications, generation);
|
|
383
408
|
if (!wasLive) await this.#waitForConnection(connection, generation);
|
|
384
409
|
this.#requireGeneration(generation);
|
|
410
|
+
this.#negotiatedGeneration = generation;
|
|
385
411
|
this.#confirmLocalPublications(publications, response.tracks);
|
|
386
412
|
this.#publishSnapshot();
|
|
387
413
|
this.#emitLocal();
|
|
@@ -458,6 +484,7 @@ var CloudflareSFUClient = class {
|
|
|
458
484
|
const desired = desiredRemotePublications(authoritative.publications, this.#participantId);
|
|
459
485
|
const toPull = [...desired].filter(([key, publication]) => this.#remoteTracks.get(key)?.publicationId !== publication.publicationId).map(([, publication]) => publication);
|
|
460
486
|
const pulled = await this.#pull(toPull, generation);
|
|
487
|
+
if (pulled === null) return;
|
|
461
488
|
this.#requireGeneration(generation);
|
|
462
489
|
const next = reconcileRemoteTracks(desired, pulled, this.#remoteTracks);
|
|
463
490
|
stopReplacedRemoteTracks(this.#remoteTracks, next, this.#reportError.bind(this));
|
|
@@ -470,9 +497,9 @@ var CloudflareSFUClient = class {
|
|
|
470
497
|
}
|
|
471
498
|
async #pull(publications, generation) {
|
|
472
499
|
if (publications.length === 0) return [];
|
|
473
|
-
const connection = this.#connection;
|
|
474
|
-
const connectionId = this.#bootstrap.connectionId;
|
|
475
500
|
return this.#serializeSDP(async () => {
|
|
501
|
+
const connection = this.#connection;
|
|
502
|
+
const connectionId = this.#bootstrap.connectionId;
|
|
476
503
|
const requested = publications.map((publication) => {
|
|
477
504
|
const reference = parseCloudflareSFUPublicationID(publication.publicationId);
|
|
478
505
|
return { location: "remote", sessionId: reference.connectionId, trackName: reference.trackName };
|
|
@@ -489,6 +516,7 @@ var CloudflareSFUClient = class {
|
|
|
489
516
|
const responseTracks = response.tracks ?? [];
|
|
490
517
|
await this.#completeRenegotiation(response, connection, connectionId, generation);
|
|
491
518
|
await this.#waitForConnection(connection, generation);
|
|
519
|
+
this.#negotiatedGeneration = generation;
|
|
492
520
|
await waitFor(() => responseTracks.every((track) => track.mid !== void 0 && received.has(track.mid)), 5e3);
|
|
493
521
|
this.#requireGeneration(generation);
|
|
494
522
|
return publications.map((publication, index) => {
|
|
@@ -532,15 +560,78 @@ var CloudflareSFUClient = class {
|
|
|
532
560
|
"Timed out waiting for the Cloudflare SFU peer connection"
|
|
533
561
|
);
|
|
534
562
|
}
|
|
535
|
-
#observeConnection(connection, generation) {
|
|
563
|
+
#observeConnection(connection, generation, connectionEpoch) {
|
|
564
|
+
let disposed = false;
|
|
565
|
+
const dispose = () => {
|
|
566
|
+
if (disposed) return;
|
|
567
|
+
disposed = true;
|
|
568
|
+
connection.removeEventListener("connectionstatechange", capture);
|
|
569
|
+
connection.removeEventListener("iceconnectionstatechange", capture);
|
|
570
|
+
connection.removeEventListener("signalingstatechange", capture);
|
|
571
|
+
};
|
|
536
572
|
const observe = () => {
|
|
537
|
-
if (generation !== this.#generation || this.#stopped) return;
|
|
573
|
+
if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
|
|
538
574
|
const observation = observeConnectionState(connection.connectionState, connection.iceConnectionState, this.#started);
|
|
539
575
|
if (observation) this.#publishSnapshot(observation.phase, observation.failure);
|
|
540
576
|
else this.#publishSnapshot();
|
|
541
577
|
};
|
|
542
|
-
|
|
543
|
-
|
|
578
|
+
const capture = () => {
|
|
579
|
+
if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
|
|
580
|
+
observe();
|
|
581
|
+
const recorder = this.#onRtcSummary;
|
|
582
|
+
if (recorder) {
|
|
583
|
+
let stats;
|
|
584
|
+
try {
|
|
585
|
+
stats = Promise.resolve(connection.getStats());
|
|
586
|
+
} catch {
|
|
587
|
+
stats = Promise.reject(new Error("RTC stats are unavailable"));
|
|
588
|
+
}
|
|
589
|
+
void stats.then((report) => {
|
|
590
|
+
if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
|
|
591
|
+
recorder(rtcConnectionState(connection), rtcStats(report));
|
|
592
|
+
}).catch(() => void 0);
|
|
593
|
+
}
|
|
594
|
+
if (connection.connectionState === "closed") dispose();
|
|
595
|
+
};
|
|
596
|
+
connection.addEventListener("connectionstatechange", capture);
|
|
597
|
+
connection.addEventListener("iceconnectionstatechange", capture);
|
|
598
|
+
connection.addEventListener("signalingstatechange", capture);
|
|
599
|
+
capture();
|
|
600
|
+
}
|
|
601
|
+
async #replaceDormantConnectionBeforeNegotiation(generation) {
|
|
602
|
+
if (!this.#started || this.#negotiatedGeneration === generation || this.#replacementAttemptedGeneration === generation || !this.#replaceMediaConnection) return;
|
|
603
|
+
await this.#replaceMediaConnectionForRecovery(generation);
|
|
604
|
+
}
|
|
605
|
+
async #replaceMediaConnectionForRecovery(generation) {
|
|
606
|
+
this.#requireGeneration(generation);
|
|
607
|
+
if (!this.#replaceMediaConnection || this.#replacementAttemptedGeneration === generation) {
|
|
608
|
+
throw new CloudflareSFUError("A fresh Cloudflare SFU connection is unavailable", "signaling_failed");
|
|
609
|
+
}
|
|
610
|
+
this.#replacementAttemptedGeneration = generation;
|
|
611
|
+
const bootstrap = await this.#replaceMediaConnection();
|
|
612
|
+
this.#requireGeneration(generation);
|
|
613
|
+
validateBootstrap(bootstrap);
|
|
614
|
+
if (bootstrap.connectionId === this.#bootstrap.connectionId) {
|
|
615
|
+
throw new CloudflareSFUError("Participant access did not replace the Cloudflare SFU connection", "signaling_failed");
|
|
616
|
+
}
|
|
617
|
+
const connectionEpoch = ++this.#connectionEpoch;
|
|
618
|
+
this.#disposeConnection(false);
|
|
619
|
+
this.#reusableLocalTransceivers.clear();
|
|
620
|
+
this.#clearRemoteTracks();
|
|
621
|
+
this.#cursor = null;
|
|
622
|
+
this.#bootstrap = bootstrap;
|
|
623
|
+
this.#connection = this.#createPeerConnection(bootstrap);
|
|
624
|
+
this.#negotiatedGeneration = null;
|
|
625
|
+
this.#observeConnection(this.#connection, generation, connectionEpoch);
|
|
626
|
+
for (const state of this.#localTracks.values()) {
|
|
627
|
+
state.transceiver = null;
|
|
628
|
+
state.enabled = false;
|
|
629
|
+
state.providerPublicationId = null;
|
|
630
|
+
}
|
|
631
|
+
this.#setPhase("recovering", null);
|
|
632
|
+
}
|
|
633
|
+
#isRetryableConnectionFailure(error) {
|
|
634
|
+
return error instanceof CloudflareSFUError && error.code === "signaling_failed" && error.options.retryableConnection === true;
|
|
544
635
|
}
|
|
545
636
|
#createPeerConnection(bootstrap) {
|
|
546
637
|
const create = this.#peerConnectionFactory ?? ((configuration) => new RTCPeerConnection(configuration));
|
|
@@ -679,6 +770,71 @@ function validateClientOptions(options) {
|
|
|
679
770
|
throw new CloudflareSFUError("Cloudflare SFU polling interval is invalid", "invalid_bootstrap");
|
|
680
771
|
}
|
|
681
772
|
}
|
|
773
|
+
function rtcConnectionState(connection) {
|
|
774
|
+
return {
|
|
775
|
+
connectionState: connection.connectionState,
|
|
776
|
+
iceConnectionState: connection.iceConnectionState,
|
|
777
|
+
signalingState: connection.signalingState
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function rtcStats(report) {
|
|
781
|
+
const entries = [];
|
|
782
|
+
report.forEach((entry) => {
|
|
783
|
+
if (typeof entry.type !== "string") return;
|
|
784
|
+
entries.push({ type: entry.type, ...rtcTrafficStats(entry), ...rtcQualityStats(entry), ...rtcTransportStats(entry) });
|
|
785
|
+
});
|
|
786
|
+
return entries;
|
|
787
|
+
}
|
|
788
|
+
function rtcTrafficStats(entry) {
|
|
789
|
+
const bytesReceived = rtcNumber(entry, "bytesReceived");
|
|
790
|
+
const bytesSent = rtcNumber(entry, "bytesSent");
|
|
791
|
+
const packetsReceived = rtcNumber(entry, "packetsReceived");
|
|
792
|
+
const packetsSent = rtcNumber(entry, "packetsSent");
|
|
793
|
+
return {
|
|
794
|
+
...bytesReceived !== void 0 ? { bytesReceived } : {},
|
|
795
|
+
...bytesSent !== void 0 ? { bytesSent } : {},
|
|
796
|
+
...packetsReceived !== void 0 ? { packetsReceived } : {},
|
|
797
|
+
...packetsSent !== void 0 ? { packetsSent } : {}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
function rtcQualityStats(entry) {
|
|
801
|
+
const kind = rtcString(entry, "kind");
|
|
802
|
+
const framesDropped = rtcNumber(entry, "framesDropped");
|
|
803
|
+
const jitter = rtcNumber(entry, "jitter");
|
|
804
|
+
const packetsLost = rtcNumber(entry, "packetsLost");
|
|
805
|
+
const roundTripTime = rtcNumber(entry, "roundTripTime");
|
|
806
|
+
return {
|
|
807
|
+
...kind !== void 0 ? { kind } : {},
|
|
808
|
+
...framesDropped !== void 0 ? { framesDropped } : {},
|
|
809
|
+
...jitter !== void 0 ? { jitter } : {},
|
|
810
|
+
...packetsLost !== void 0 ? { packetsLost } : {},
|
|
811
|
+
...roundTripTime !== void 0 ? { roundTripTime } : {}
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function rtcTransportStats(entry) {
|
|
815
|
+
const state = rtcString(entry, "state");
|
|
816
|
+
const selected = rtcBoolean(entry, "selected");
|
|
817
|
+
const nominated = rtcBoolean(entry, "nominated");
|
|
818
|
+
const dtlsState = rtcString(entry, "dtlsState");
|
|
819
|
+
return {
|
|
820
|
+
...state !== void 0 ? { state } : {},
|
|
821
|
+
...selected !== void 0 ? { selected } : {},
|
|
822
|
+
...nominated !== void 0 ? { nominated } : {},
|
|
823
|
+
...dtlsState !== void 0 ? { dtlsState } : {}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function rtcString(value, property) {
|
|
827
|
+
const candidate = Reflect.get(value, property);
|
|
828
|
+
return typeof candidate === "string" ? candidate : void 0;
|
|
829
|
+
}
|
|
830
|
+
function rtcNumber(value, property) {
|
|
831
|
+
const candidate = Reflect.get(value, property);
|
|
832
|
+
return typeof candidate === "number" ? candidate : void 0;
|
|
833
|
+
}
|
|
834
|
+
function rtcBoolean(value, property) {
|
|
835
|
+
const candidate = Reflect.get(value, property);
|
|
836
|
+
return typeof candidate === "boolean" ? candidate : void 0;
|
|
837
|
+
}
|
|
682
838
|
function validateBootstrap(bootstrap) {
|
|
683
839
|
if (!bootstrap.connectionId.trim() || !bootstrap.stunServer.trim()) throw new CloudflareSFUError("Cloudflare SFU bootstrap is incomplete", "invalid_bootstrap");
|
|
684
840
|
}
|
|
@@ -771,7 +927,12 @@ function createCloudflareSFUHTTPTransport(options) {
|
|
|
771
927
|
...init,
|
|
772
928
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers }
|
|
773
929
|
});
|
|
774
|
-
if (!response.ok)
|
|
930
|
+
if (!response.ok) {
|
|
931
|
+
throw new CloudflareSFUError(`Chalk SFU signaling failed with HTTP ${response.status}`, "signaling_failed", {
|
|
932
|
+
status: response.status,
|
|
933
|
+
retryableConnection: response.status === 404 || response.status === 410 || response.status >= 500 && response.status < 600
|
|
934
|
+
});
|
|
935
|
+
}
|
|
775
936
|
return await response.json();
|
|
776
937
|
};
|
|
777
938
|
return {
|
package/dist/client.d.ts
CHANGED
|
@@ -6560,6 +6560,7 @@ export declare const createChalkEffectClient: (options: ChalkEffectClientOptions
|
|
|
6560
6560
|
}>, never, never>>, never, never>, never, never>;
|
|
6561
6561
|
readonly refreshSpacePublicInviteAccess: HttpApiClient.Client.Method<import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"refreshSpacePublicInviteAccess", "POST", "/v1/public/space-invite-arrival/access-grants", never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
6562
6562
|
readonly media_proof: import("effect/Schema").String;
|
|
6563
|
+
readonly replace_media_connection: import("effect/Schema").Boolean;
|
|
6563
6564
|
}>>, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
6564
6565
|
readonly "X-Chalk-Arrival-Handle": import("effect/Schema").String;
|
|
6565
6566
|
}>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|