@q9labsai/chalk-client 4.1.13 → 4.1.15
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/access/grant.d.ts +1 -0
- package/dist/access/grant.d.ts.map +1 -1
- package/dist/access/manager.d.ts +2 -0
- package/dist/access/manager.d.ts.map +1 -1
- package/dist/{chunk-IYRVIFWN.js → chunk-6ONSFCIY.js} +1 -1
- package/dist/{chunk-DV433YIW.js → chunk-LQSXEWLL.js} +113 -44
- package/dist/{chunk-EBACU63B.js → chunk-LSDDADCN.js} +56 -0
- package/dist/{chunk-S5OUHFGC.js → chunk-PQNSA7ZN.js} +63 -3
- package/dist/{chunk-5VXGLI3T.js → chunk-PWUU6T3R.js} +8 -1
- package/dist/client.d.ts +19 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/connection/lifecycle.d.ts.map +1 -1
- package/dist/effect.js +16 -4
- package/dist/generated/http-api.d.ts +19 -1
- package/dist/generated/http-api.d.ts.map +1 -1
- package/dist/generated/schemas.d.ts +63 -1
- package/dist/generated/schemas.d.ts.map +1 -1
- package/dist/index.js +32 -32
- package/dist/media/client.d.ts.map +1 -1
- package/dist/media/index.js +1 -1
- package/dist/public-invites/index.js +2 -2
- package/dist/server/index.cjs +8 -0
- package/dist/server/index.js +8 -0
- package/dist/space-client/episode-diagnostic-runtime.d.ts.map +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/v1-client.d.ts.map +1 -1
- package/dist/whiteboard/v1-client.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/access/grant.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type ParticipantMediaAccess = {
|
|
|
38
38
|
};
|
|
39
39
|
export type ParsedAccessGrant = {
|
|
40
40
|
readonly subject: AccessSubject;
|
|
41
|
+
readonly episodeStartedAt?: string | null;
|
|
41
42
|
readonly sync: ParticipantSyncAccess;
|
|
42
43
|
readonly media: ParticipantMediaAccess;
|
|
43
44
|
readonly diagnostics?: EpisodeDiagnosticCredential | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant.d.ts","sourceRoot":"","sources":["../../src/access/grant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAGrH,OAAO,CAAC,MAAM,mBAAmB,EAAE,OAAO,MAAM,CAAC;AACjD,OAAO,CAAC,MAAM,oBAAoB,EAAE,OAAO,MAAM,CAAC;AAClD,QAAA,MAAM,gBAAgB,EAAE,OAAO,MAA8B,CAAC;AAE9D,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,mBAAmB,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC;AAClG,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,oBAAoB,CAAC,EAAE,aAAa,CAAA;CAAE,CAAC;AAErG,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,KAAK,EAAE,yBAAyB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IACE,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE;QACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE;QACtB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;KACxB,CAAC;CACH,CAAC;AAEN,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;CAC3D,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,aAAa,CAAA;CAAE,CAAC;AAEzE,MAAM,MAAM,mBAAmB,GAAG,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEvF,qBAAa,gBAAiB,SAAQ,SAAS;IAC7C,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;IAE1C,YAAY,OAAO,SAA4B,EAG9C;CACF;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"grant.d.ts","sourceRoot":"","sources":["../../src/access/grant.ts"],"names":[],"mappings":"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"}
|
package/dist/access/manager.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class ConnectionAccessFailure extends ConnectionAccessFailure_bas
|
|
|
10
10
|
}> {
|
|
11
11
|
}
|
|
12
12
|
export type ConnectionAccessEffectProvider = (request: ConnectionAccessRequest) => Effect.Effect<ParsedAccessGrant, ConnectionAccessFailure>;
|
|
13
|
+
export type AutomaticMediaReplacementListener = (access: ParsedAccessGrant) => void;
|
|
13
14
|
export type ConnectionAccessEffectService = {
|
|
14
15
|
readonly current: Effect.Effect<ParsedAccessGrant | null>;
|
|
15
16
|
readonly currentUnsafe: () => ParsedAccessGrant | null;
|
|
@@ -19,6 +20,7 @@ export type ConnectionAccessEffectService = {
|
|
|
19
20
|
readonly refreshAfterRejection: () => Effect.Effect<ParsedAccessGrant, ConnectionAccessFailure>;
|
|
20
21
|
readonly getSyncToken: (reason?: ConnectionAccessReason) => Effect.Effect<ParticipantSyncCredential, ConnectionAccessFailure>;
|
|
21
22
|
readonly getMediaToken: () => Effect.Effect<ParticipantMediaCredential, ConnectionAccessFailure>;
|
|
23
|
+
readonly subscribeAutomaticMediaReplacement: (listener: AutomaticMediaReplacementListener) => Effect.Effect<() => void>;
|
|
22
24
|
readonly requiresRefresh: Effect.Effect<boolean>;
|
|
23
25
|
readonly millisecondsUntilRefresh: Effect.Effect<number | null>;
|
|
24
26
|
readonly clear: Effect.Effect<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/access/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,OAAO,EAAQ,MAAM,EAAE,KAAK,EAAmB,MAAM,QAAQ,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AACxG,OAAO,KAAK,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;;;;AAIlG,qBAAa,uBAAwB,SAAQ,6BAA4C;IACvF,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,oBAAoB,CAAC;IACvD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;CAAG;AAEL,MAAM,MAAM,8BAA8B,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/access/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,OAAO,EAAQ,MAAM,EAAE,KAAK,EAAmB,MAAM,QAAQ,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AACxG,OAAO,KAAK,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;;;;AAIlG,qBAAa,uBAAwB,SAAQ,6BAA4C;IACvF,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,oBAAoB,CAAC;IACvD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;CAAG;AAEL,MAAM,MAAM,8BAA8B,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;AAC7I,MAAM,MAAM,iCAAiC,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;AAEpF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC1D,QAAQ,CAAC,aAAa,EAAE,MAAM,iBAAiB,GAAG,IAAI,CAAC;IACvD,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,cAAc,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;IACrH,QAAQ,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,EAAE,sBAAsB,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;IACrH,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,EAAE,MAAM,CAAC,EAAE,sBAAsB,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;IAClK,QAAQ,CAAC,qBAAqB,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;IAChG,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,sBAAsB,KAAK,MAAM,CAAC,MAAM,CAAC,yBAAyB,EAAE,uBAAuB,CAAC,CAAC;IAC9H,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,0BAA0B,EAAE,uBAAuB,CAAC,CAAC;IACjG,QAAQ,CAAC,kCAAkC,EAAE,CAAC,QAAQ,EAAE,iCAAiC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACxH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjD,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC;;AAEF,qBAAa,uBAAwB,SAAQ,4BAA2G;CAAG;AAY3J;;;GAGG;AACH,eAAO,MAAM,yBAAyB,aAAc,8BAA8B,iFAuC/E,CAAC;AAEJ,eAAO,MAAM,6BAA6B,aAzCU,8BAA8B,iFAyCZ,CAAC"}
|
|
@@ -2,21 +2,21 @@ import {
|
|
|
2
2
|
CloudflareRTKClient,
|
|
3
3
|
CloudflareSFUClient,
|
|
4
4
|
createCloudflareSFUHTTPTransport
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PQNSA7ZN.js";
|
|
6
|
+
import {
|
|
7
|
+
createBrowserSyncLifecycle,
|
|
8
|
+
createBrowserWebSocketFactory,
|
|
9
|
+
createV1SyncClient
|
|
10
|
+
} from "./chunk-PWUU6T3R.js";
|
|
6
11
|
import {
|
|
7
12
|
AccessGrantError,
|
|
8
13
|
requireParsedAccessGrant,
|
|
9
14
|
validEpisodeDiagnosticCredential
|
|
10
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-LSDDADCN.js";
|
|
11
16
|
import {
|
|
12
17
|
createTraceContext,
|
|
13
18
|
parseTraceparent
|
|
14
19
|
} from "./chunk-NKOKY2AE.js";
|
|
15
|
-
import {
|
|
16
|
-
createBrowserSyncLifecycle,
|
|
17
|
-
createBrowserWebSocketFactory,
|
|
18
|
-
createV1SyncClient
|
|
19
|
-
} from "./chunk-5VXGLI3T.js";
|
|
20
20
|
|
|
21
21
|
// src/access/manager.ts
|
|
22
22
|
import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
|
|
@@ -29,28 +29,10 @@ var makeConnectionAccessLayer = (provider, refreshWindowMs = DEFAULT_REFRESH_WIN
|
|
|
29
29
|
ConnectionAccessService,
|
|
30
30
|
Effect.gen(function* () {
|
|
31
31
|
const state = yield* SynchronizedRef.make({ access: null });
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
(
|
|
35
|
-
|
|
36
|
-
if (!force && current.access && millisecondsUntil(current.access, now, refreshWindowMs) > 0) return [current.access, current];
|
|
37
|
-
const request = Object.freeze({
|
|
38
|
-
reason,
|
|
39
|
-
replaceMediaConnection,
|
|
40
|
-
...current.access ? {
|
|
41
|
-
currentMediaToken: current.access.media.token,
|
|
42
|
-
expectedParticipantGeneration: current.access.subject.participantGeneration
|
|
43
|
-
} : {}
|
|
44
|
-
});
|
|
45
|
-
const next = yield* provider(request);
|
|
46
|
-
try {
|
|
47
|
-
validateExpiration(next, now);
|
|
48
|
-
if (current.access) validateRefresh(current.access, next, replaceMediaConnection);
|
|
49
|
-
} catch (cause) {
|
|
50
|
-
return yield* Effect.fail(new ConnectionAccessFailure({ code: "access.invalid", cause }));
|
|
51
|
-
}
|
|
52
|
-
return [next, { access: next }];
|
|
53
|
-
})
|
|
32
|
+
const automaticMediaReplacementListeners = /* @__PURE__ */ new Set();
|
|
33
|
+
const fetch = (reason, replaceMediaConnection, force) => SynchronizedRef.modifyEffect(state, (current) => fetchAccessGrant(provider, { current, reason, replaceMediaConnection, force, refreshWindowMs })).pipe(
|
|
34
|
+
Effect.tap((result) => Effect.sync(() => notifyAutomaticMediaReplacement(automaticMediaReplacementListeners, result))),
|
|
35
|
+
Effect.map((result) => result.access)
|
|
54
36
|
);
|
|
55
37
|
const initialize = (reason = "join") => fetch(reason, false, false).pipe(Effect.catchTag("ConnectionAccessFailure", (failure2) => failure2.code === "access.invalid" && SynchronizedRef.getUnsafe(state).access === null ? fetch(reason, false, true) : Effect.fail(failure2)));
|
|
56
38
|
const millisecondsUntilRefresh = Effect.gen(function* () {
|
|
@@ -68,6 +50,10 @@ var makeConnectionAccessLayer = (provider, refreshWindowMs = DEFAULT_REFRESH_WIN
|
|
|
68
50
|
refreshAfterRejection: () => fetch("access_retry", true, true),
|
|
69
51
|
getSyncToken: (reason = "sync_recovery") => ensureFresh(reason).pipe(Effect.map((access) => access.sync.token)),
|
|
70
52
|
getMediaToken: () => ensureFresh("scheduled_refresh").pipe(Effect.map((access) => access.media.token)),
|
|
53
|
+
subscribeAutomaticMediaReplacement: (listener) => Effect.sync(() => {
|
|
54
|
+
automaticMediaReplacementListeners.add(listener);
|
|
55
|
+
return () => automaticMediaReplacementListeners.delete(listener);
|
|
56
|
+
}),
|
|
71
57
|
requiresRefresh: millisecondsUntilRefresh.pipe(Effect.map((milliseconds) => milliseconds === null || milliseconds === 0)),
|
|
72
58
|
millisecondsUntilRefresh,
|
|
73
59
|
clear: SynchronizedRef.set(state, { access: null })
|
|
@@ -75,6 +61,46 @@ var makeConnectionAccessLayer = (provider, refreshWindowMs = DEFAULT_REFRESH_WIN
|
|
|
75
61
|
})
|
|
76
62
|
);
|
|
77
63
|
var makeFakeConnectionAccessLayer = makeConnectionAccessLayer;
|
|
64
|
+
function fetchAccessGrant(provider, input) {
|
|
65
|
+
return Effect.gen(function* () {
|
|
66
|
+
const now = yield* Clock.currentTimeMillis;
|
|
67
|
+
const currentAccess = input.current.access;
|
|
68
|
+
if (!input.force && currentAccess && millisecondsUntil(currentAccess, now, input.refreshWindowMs) > 0) {
|
|
69
|
+
return [{ access: currentAccess, automaticMediaReplacement: false }, input.current];
|
|
70
|
+
}
|
|
71
|
+
const mediaExpired = currentAccess !== null && expiresAt(currentAccess.media.expiresAt) <= now;
|
|
72
|
+
const shouldReplaceMediaConnection = input.replaceMediaConnection || mediaExpired;
|
|
73
|
+
const next = yield* provider(connectionAccessRequest(input, shouldReplaceMediaConnection));
|
|
74
|
+
yield* Effect.try({
|
|
75
|
+
try: () => validateFetchedAccess(currentAccess, next, now, shouldReplaceMediaConnection),
|
|
76
|
+
catch: (cause) => new ConnectionAccessFailure({ code: "access.invalid", cause })
|
|
77
|
+
});
|
|
78
|
+
return [{ access: next, automaticMediaReplacement: mediaExpired && !input.replaceMediaConnection }, { access: next }];
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function connectionAccessRequest(input, replaceMediaConnection) {
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
reason: input.reason,
|
|
84
|
+
replaceMediaConnection,
|
|
85
|
+
...input.current.access ? {
|
|
86
|
+
currentMediaToken: input.current.access.media.token,
|
|
87
|
+
expectedParticipantGeneration: input.current.access.subject.participantGeneration
|
|
88
|
+
} : {}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function validateFetchedAccess(previous, next, now, replaceMediaConnection) {
|
|
92
|
+
validateExpiration(next, now);
|
|
93
|
+
if (previous) validateRefresh(previous, next, replaceMediaConnection);
|
|
94
|
+
}
|
|
95
|
+
function notifyAutomaticMediaReplacement(listeners, result) {
|
|
96
|
+
if (!result.automaticMediaReplacement) return;
|
|
97
|
+
for (const listener of listeners) {
|
|
98
|
+
try {
|
|
99
|
+
listener(result.access);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
78
104
|
function millisecondsUntil(access, now, refreshWindowMs) {
|
|
79
105
|
return Math.max(0, Math.min(expiresAt(access.sync.expiresAt), expiresAt(access.media.expiresAt)) - now - refreshWindowMs);
|
|
80
106
|
}
|
|
@@ -82,12 +108,15 @@ function validateExpiration(access, now) {
|
|
|
82
108
|
if (expiresAt(access.sync.expiresAt) <= now || expiresAt(access.media.expiresAt) <= now) throw new TypeError("Access grant is expired");
|
|
83
109
|
}
|
|
84
110
|
function validateRefresh(previous, next, replaceMediaConnection) {
|
|
111
|
+
if (!hasSameSubject(previous, next)) throw new TypeError("Access grant refresh changed its subject");
|
|
112
|
+
const mediaConnectionChanged = previous.media.provider !== next.media.provider || mediaBinding(previous) !== mediaBinding(next);
|
|
113
|
+
if (replaceMediaConnection === mediaConnectionChanged) return;
|
|
114
|
+
throw new TypeError(replaceMediaConnection ? "Access grant replacement did not change its media binding" : "Access grant refresh unexpectedly replaced its media connection");
|
|
115
|
+
}
|
|
116
|
+
function hasSameSubject(previous, next) {
|
|
85
117
|
const left = previous.subject;
|
|
86
118
|
const right = next.subject;
|
|
87
|
-
|
|
88
|
-
throw new TypeError("Access grant refresh changed its subject");
|
|
89
|
-
}
|
|
90
|
-
if (!replaceMediaConnection && (previous.media.provider !== next.media.provider || mediaBinding(previous) !== mediaBinding(next))) throw new TypeError("Access grant refresh unexpectedly replaced its media connection");
|
|
119
|
+
return left.tenantId === right.tenantId && left.spaceId === right.spaceId && left.episodeId === right.episodeId && left.participantId === right.participantId && left.participantGeneration === right.participantGeneration;
|
|
91
120
|
}
|
|
92
121
|
function mediaBinding(access) {
|
|
93
122
|
switch (access.media.provider) {
|
|
@@ -879,6 +908,8 @@ var CLIENT_RESTART_CLOSE_CODE = 4e3;
|
|
|
879
908
|
var DEPENDENCY_UNAVAILABLE_CLOSE_CODE = 1012;
|
|
880
909
|
var HEARTBEAT_INTERVAL_MS = 2e4;
|
|
881
910
|
var MAX_MISSED_HEARTBEATS = 2;
|
|
911
|
+
var DEFAULT_RECONNECT_DELAY_MS = 250;
|
|
912
|
+
var MAX_RECONNECT_DELAY_MS = 5e3;
|
|
882
913
|
var encoder2 = new TextEncoder();
|
|
883
914
|
var ChalkWhiteboardV1Client = class {
|
|
884
915
|
files;
|
|
@@ -914,6 +945,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
914
945
|
#stopPromise = null;
|
|
915
946
|
#waitingForOperations = false;
|
|
916
947
|
#reconnectTimer;
|
|
948
|
+
#reconnectAttempt = 0;
|
|
917
949
|
#heartbeatTimer;
|
|
918
950
|
#missedHeartbeats = 0;
|
|
919
951
|
#lastCursorAt = Number.NEGATIVE_INFINITY;
|
|
@@ -1119,7 +1151,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1119
1151
|
const cursor = this.#sceneId && this.#revision ? { scene_id: this.#sceneId, revision: this.#revision } : null;
|
|
1120
1152
|
this.#send(this.#useLegacyHello ? { type: "hello", protocol: "whiteboard-v1", token, cursor } : { type: "hello", protocol: "whiteboard-v1", token, cursor, extensions: [{ name: "presentation_v1" }] });
|
|
1121
1153
|
} catch {
|
|
1122
|
-
socket.close(
|
|
1154
|
+
socket.close(CLIENT_RESTART_CLOSE_CODE, "whiteboard authentication failed");
|
|
1123
1155
|
}
|
|
1124
1156
|
}
|
|
1125
1157
|
async #receive(socket, data) {
|
|
@@ -1231,6 +1263,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1231
1263
|
#welcome(frame) {
|
|
1232
1264
|
if (this.#phase !== "authenticating") throw new Error("unexpected whiteboard welcome");
|
|
1233
1265
|
this.#phase = "live";
|
|
1266
|
+
this.#reconnectAttempt = 0;
|
|
1234
1267
|
this.#participantId = frame.participant_id;
|
|
1235
1268
|
this.#sceneId = frame.scene_id;
|
|
1236
1269
|
this.#revision = frame.revision;
|
|
@@ -1443,10 +1476,13 @@ var ChalkWhiteboardV1Client = class {
|
|
|
1443
1476
|
}
|
|
1444
1477
|
#scheduleReconnect() {
|
|
1445
1478
|
this.#clearReconnect();
|
|
1479
|
+
const configuredDelay = this.#options.reconnectDelayMs;
|
|
1480
|
+
const delay = configuredDelay === 0 ? 0 : Math.min(MAX_RECONNECT_DELAY_MS, (configuredDelay ?? DEFAULT_RECONNECT_DELAY_MS) * 2 ** Math.min(this.#reconnectAttempt, 5));
|
|
1481
|
+
this.#reconnectAttempt += 1;
|
|
1446
1482
|
this.#reconnectTimer = this.#clock().setTimeout(() => {
|
|
1447
1483
|
this.#reconnectTimer = void 0;
|
|
1448
1484
|
this.#connect();
|
|
1449
|
-
},
|
|
1485
|
+
}, delay);
|
|
1450
1486
|
}
|
|
1451
1487
|
#handleLifecycle(event) {
|
|
1452
1488
|
if (event === "online" || event === "offline") this.#online = event === "online";
|
|
@@ -1989,7 +2025,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
1989
2025
|
};
|
|
1990
2026
|
const foreign6 = (operation, code = "internal_error", message = "A browser transport operation failed") => Effect2.tryPromise({ try: operation, catch: (cause) => lifecycleFailure(code, true, message, cause) });
|
|
1991
2027
|
const toPromise = (effect) => Effect2.runPromiseWith(context)(effect);
|
|
1992
|
-
const publish = () => Effect2.sync(() => snapshotFor(model, access.currentUnsafe()
|
|
2028
|
+
const publish = () => Effect2.sync(() => snapshotFor(model, access.currentUnsafe())).pipe(
|
|
1993
2029
|
Effect2.tap((snapshot) => SubscriptionRef.set(snapshotRef, snapshot)),
|
|
1994
2030
|
Effect2.asVoid,
|
|
1995
2031
|
Effect2.tap(
|
|
@@ -2352,6 +2388,18 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2352
2388
|
yield* publish();
|
|
2353
2389
|
});
|
|
2354
2390
|
const handleMediaFailure = () => model.media ? handleMediaSnapshot(model.media, model.media.getSnapshot()) : Effect2.void;
|
|
2391
|
+
const applyAutomaticMediaReplacement = (grant) => Effect2.gen(function* () {
|
|
2392
|
+
const media = model.media;
|
|
2393
|
+
if (!media || !active(model)) return;
|
|
2394
|
+
const restartInput = grant.media.provider === "cloudflare_sfu" ? grant.media.clientPayload : grant.media;
|
|
2395
|
+
const restarted = yield* Effect2.exit(foreign6(() => media.restart(restartInput), "media_start_failed", "The media client could not apply a replacement access grant"));
|
|
2396
|
+
if (restarted._tag === "Failure") {
|
|
2397
|
+
yield* recover("media");
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
model.mediaSnapshot = media.getSnapshot();
|
|
2401
|
+
yield* publish();
|
|
2402
|
+
});
|
|
2355
2403
|
const notifyScreenEnded = () => Effect2.sync(() => {
|
|
2356
2404
|
if (!active(model)) return;
|
|
2357
2405
|
for (const listener of screenEndedListeners) {
|
|
@@ -2370,10 +2418,12 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2370
2418
|
yield* Deferred.done(work.deferred, exit);
|
|
2371
2419
|
}) : Deferred.complete(work.deferred, work.effect).pipe(Effect2.asVoid);
|
|
2372
2420
|
yield* Effect2.forkScoped(Queue.take(queue).pipe(Effect2.flatMap(process), Effect2.forever));
|
|
2421
|
+
const accessReplacementUnsubscribe = yield* access.subscribeAutomaticMediaReplacement((grant) => enqueueBackground(applyAutomaticMediaReplacement(grant)));
|
|
2373
2422
|
const foregroundUnsubscribe = platform.subscribeForeground?.(() => enqueueBackground(refreshAccess())) ?? null;
|
|
2374
2423
|
yield* Effect2.addFinalizer(
|
|
2375
2424
|
() => Effect2.gen(function* () {
|
|
2376
2425
|
model.closed = true;
|
|
2426
|
+
accessReplacementUnsubscribe();
|
|
2377
2427
|
foregroundUnsubscribe?.();
|
|
2378
2428
|
if (activeJoin) yield* Fiber.interrupt(activeJoin);
|
|
2379
2429
|
yield* performLeave().pipe(Effect2.ignore);
|
|
@@ -2446,16 +2496,28 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2446
2496
|
function idleSnapshot() {
|
|
2447
2497
|
return Object.freeze({ state: "idle", subject: null, episode: null, connection: Object.freeze({ sync: "idle", media: "idle" }), failure: null });
|
|
2448
2498
|
}
|
|
2449
|
-
function snapshotFor(model,
|
|
2499
|
+
function snapshotFor(model, access) {
|
|
2450
2500
|
const control = model.syncSnapshot?.optimisticControl ?? model.syncSnapshot?.control;
|
|
2451
2501
|
return Object.freeze({
|
|
2452
2502
|
state: model.state,
|
|
2453
|
-
subject:
|
|
2454
|
-
episode:
|
|
2503
|
+
subject: subjectFor(access),
|
|
2504
|
+
episode: episodeFor(access, control),
|
|
2455
2505
|
connection: Object.freeze({ sync: syncPhase(model.syncSnapshot?.connection.phase), media: mediaPhase(model.mediaSnapshot?.connection.phase) }),
|
|
2456
2506
|
failure: model.failure ? Object.freeze({ ...model.failure }) : null
|
|
2457
2507
|
});
|
|
2458
2508
|
}
|
|
2509
|
+
function subjectFor(access) {
|
|
2510
|
+
if (!access) return null;
|
|
2511
|
+
return Object.freeze({ ...access.subject });
|
|
2512
|
+
}
|
|
2513
|
+
function episodeFor(access, control) {
|
|
2514
|
+
if (!access) return null;
|
|
2515
|
+
return Object.freeze({
|
|
2516
|
+
id: access.subject.episodeId,
|
|
2517
|
+
startedAt: access.episodeStartedAt ?? null,
|
|
2518
|
+
deadline: control ? new Date(control.deadlineAtMs).toISOString() : null
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2459
2521
|
function portsFor(model) {
|
|
2460
2522
|
return model.sync && model.media ? { sync: model.sync, media: model.media } : null;
|
|
2461
2523
|
}
|
|
@@ -4726,8 +4788,15 @@ function stripTrailingUrlSlash(value) {
|
|
|
4726
4788
|
}
|
|
4727
4789
|
function intakeResponse(value) {
|
|
4728
4790
|
if (!isRecord3(value) || typeof value.diagnosticReference !== "string" || !Number.isSafeInteger(value.committedCursor)) return null;
|
|
4729
|
-
|
|
4730
|
-
|
|
4791
|
+
const accepted = legacyEmptyAcknowledgement(value.accepted);
|
|
4792
|
+
const duplicates = legacyEmptyAcknowledgement(value.duplicates);
|
|
4793
|
+
const conflicts = legacyEmptyAcknowledgement(value.conflicts);
|
|
4794
|
+
if (accepted === null || duplicates === null || conflicts === null) return null;
|
|
4795
|
+
return { accepted, duplicates, conflicts };
|
|
4796
|
+
}
|
|
4797
|
+
function legacyEmptyAcknowledgement(value) {
|
|
4798
|
+
if (value === null) return [];
|
|
4799
|
+
return Array.isArray(value) ? value : null;
|
|
4731
4800
|
}
|
|
4732
4801
|
function completeDelivery(delivery, expected) {
|
|
4733
4802
|
const all = [...delivery.accepted, ...delivery.duplicates, ...delivery.conflicts];
|
|
@@ -4831,7 +4900,7 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
|
|
|
4831
4900
|
now: baseDependencies.clock.now,
|
|
4832
4901
|
setTimeout: baseDependencies.clock.setTimeout,
|
|
4833
4902
|
clearTimeout: baseDependencies.clock.clearTimeout,
|
|
4834
|
-
release: { id: "chalk-client@4.1.
|
|
4903
|
+
release: { id: "chalk-client@4.1.15" }
|
|
4835
4904
|
});
|
|
4836
4905
|
const diagnosticSyncClients = /* @__PURE__ */ new Set();
|
|
4837
4906
|
const dependencies = {
|
|
@@ -5102,7 +5171,7 @@ async function resolveAccessGrant(input) {
|
|
|
5102
5171
|
const { operationName: operationName2, reason } = diagnosticAccessOperation(input.request);
|
|
5103
5172
|
const operationBeforeRotation = input.diagnostics.activeContext() ? input.diagnostics.startOperation(operationName2, { reason }) : void 0;
|
|
5104
5173
|
try {
|
|
5105
|
-
const grant = await requireParsedAccessGrant(await
|
|
5174
|
+
const grant = await requireParsedAccessGrant(await fetchAccessGrant2(input));
|
|
5106
5175
|
input.diagnostics.rotateCredential(grant.diagnostics ?? null);
|
|
5107
5176
|
(operationBeforeRotation ?? input.diagnostics.startOperation(operationName2, { reason }))?.succeed({ result: "bound" });
|
|
5108
5177
|
return grant;
|
|
@@ -5115,7 +5184,7 @@ function diagnosticAccessOperation(request) {
|
|
|
5115
5184
|
const reason = request?.reason ?? "join";
|
|
5116
5185
|
return { operationName: reason === "join" ? "access.request" : "access.refresh", reason };
|
|
5117
5186
|
}
|
|
5118
|
-
async function
|
|
5187
|
+
async function fetchAccessGrant2(input) {
|
|
5119
5188
|
if (input.platform.connectionAccess) return input.platform.connectionAccess(input.request);
|
|
5120
5189
|
return input.options.getAccess({ space: input.options.space.trim(), reason: accessReason(input.request?.reason) });
|
|
5121
5190
|
}
|
|
@@ -76,6 +76,7 @@ var AccessGrantSchema = Schema.Struct({
|
|
|
76
76
|
})
|
|
77
77
|
)
|
|
78
78
|
),
|
|
79
|
+
episode_started_at: Schema.optional(Schema.NullOr(DateTimeStringSchema)),
|
|
79
80
|
media: Schema.Struct({
|
|
80
81
|
client_payload: Schema.Record(Schema.String, Schema.Unknown),
|
|
81
82
|
expires_at: DateTimeStringSchema,
|
|
@@ -2150,6 +2151,44 @@ var AccessInvalidPasswordErrorSchema = AccessInvalidPasswordErrorWireSchema.pipe
|
|
|
2150
2151
|
encode: SchemaGetter.transform((error) => ({ error: error.error }))
|
|
2151
2152
|
})
|
|
2152
2153
|
);
|
|
2154
|
+
var AccessMediaProofExpiredError = class extends Schema.TaggedErrorClass()("AccessMediaProofExpiredError", {
|
|
2155
|
+
error: Schema.Struct({
|
|
2156
|
+
code: Schema.Literal("access.media_proof_expired"),
|
|
2157
|
+
message: Schema.String
|
|
2158
|
+
})
|
|
2159
|
+
}) {
|
|
2160
|
+
};
|
|
2161
|
+
var AccessMediaProofExpiredErrorWireSchema = Schema.Struct({
|
|
2162
|
+
error: Schema.Struct({
|
|
2163
|
+
code: Schema.Literal("access.media_proof_expired"),
|
|
2164
|
+
message: Schema.String
|
|
2165
|
+
})
|
|
2166
|
+
});
|
|
2167
|
+
var AccessMediaProofExpiredErrorSchema = AccessMediaProofExpiredErrorWireSchema.pipe(
|
|
2168
|
+
Schema.decodeTo(AccessMediaProofExpiredError, {
|
|
2169
|
+
decode: SchemaGetter.transform((wire) => ({ _tag: "AccessMediaProofExpiredError", ...wire })),
|
|
2170
|
+
encode: SchemaGetter.transform((error) => ({ error: error.error }))
|
|
2171
|
+
})
|
|
2172
|
+
);
|
|
2173
|
+
var AccessMediaProofRejectedError = class extends Schema.TaggedErrorClass()("AccessMediaProofRejectedError", {
|
|
2174
|
+
error: Schema.Struct({
|
|
2175
|
+
code: Schema.Literal("access.media_proof_rejected"),
|
|
2176
|
+
message: Schema.String
|
|
2177
|
+
})
|
|
2178
|
+
}) {
|
|
2179
|
+
};
|
|
2180
|
+
var AccessMediaProofRejectedErrorWireSchema = Schema.Struct({
|
|
2181
|
+
error: Schema.Struct({
|
|
2182
|
+
code: Schema.Literal("access.media_proof_rejected"),
|
|
2183
|
+
message: Schema.String
|
|
2184
|
+
})
|
|
2185
|
+
});
|
|
2186
|
+
var AccessMediaProofRejectedErrorSchema = AccessMediaProofRejectedErrorWireSchema.pipe(
|
|
2187
|
+
Schema.decodeTo(AccessMediaProofRejectedError, {
|
|
2188
|
+
decode: SchemaGetter.transform((wire) => ({ _tag: "AccessMediaProofRejectedError", ...wire })),
|
|
2189
|
+
encode: SchemaGetter.transform((error) => ({ error: error.error }))
|
|
2190
|
+
})
|
|
2191
|
+
);
|
|
2153
2192
|
var AccessRecentAuthRequiredError = class extends Schema.TaggedErrorClass()("AccessRecentAuthRequiredError", {
|
|
2154
2193
|
error: Schema.Struct({
|
|
2155
2194
|
code: Schema.Literal("access.recent_auth_required"),
|
|
@@ -5368,6 +5407,8 @@ var RefreshIntegrationConnectionErrorSchema = Schema.Union([
|
|
|
5368
5407
|
TenantInvalidIdErrorSchema
|
|
5369
5408
|
]);
|
|
5370
5409
|
var RefreshSpacePublicInviteAccessErrorSchema = Schema.Union([
|
|
5410
|
+
AccessMediaProofExpiredErrorSchema,
|
|
5411
|
+
AccessMediaProofRejectedErrorSchema,
|
|
5371
5412
|
ArrivalInvalidHandleErrorSchema,
|
|
5372
5413
|
ArrivalUnavailableErrorSchema,
|
|
5373
5414
|
EpisodeCapacityExceededErrorSchema,
|
|
@@ -7375,8 +7416,10 @@ var spacesGroup = HttpApiGroup.make("spaces").add(
|
|
|
7375
7416
|
ArrivalInvalidHandleErrorSchema.pipe(HttpApiSchema.status(400)),
|
|
7376
7417
|
RequestInvalidErrorSchema.pipe(HttpApiSchema.status(400)),
|
|
7377
7418
|
RequestInvalidIdempotencyKeyErrorSchema.pipe(HttpApiSchema.status(400)),
|
|
7419
|
+
AccessMediaProofRejectedErrorSchema.pipe(HttpApiSchema.status(401)),
|
|
7378
7420
|
ArrivalUnavailableErrorSchema.pipe(HttpApiSchema.status(404)),
|
|
7379
7421
|
SpacePublicInviteUnavailableErrorSchema.pipe(HttpApiSchema.status(404)),
|
|
7422
|
+
AccessMediaProofExpiredErrorSchema.pipe(HttpApiSchema.status(409)),
|
|
7380
7423
|
EpisodeCapacityExceededErrorSchema.pipe(HttpApiSchema.status(409)),
|
|
7381
7424
|
RequestIdempotencyConflictErrorSchema.pipe(HttpApiSchema.status(409)),
|
|
7382
7425
|
RequestPayloadTooLargeErrorSchema.pipe(HttpApiSchema.status(413)),
|
|
@@ -7801,9 +7844,11 @@ var AccessGrantError = class extends TypeError {
|
|
|
7801
7844
|
function parseParsedAccessGrant(value) {
|
|
7802
7845
|
if (!isRecord2(value)) throw new AccessGrantError();
|
|
7803
7846
|
const subject = parseSubject(value.subject);
|
|
7847
|
+
const episodeStartedAt = parseOptionalDateTime(value.episode_started_at);
|
|
7804
7848
|
const diagnostics = parseEpisodeDiagnosticCredential(value.diagnostics);
|
|
7805
7849
|
return {
|
|
7806
7850
|
subject,
|
|
7851
|
+
episodeStartedAt,
|
|
7807
7852
|
sync: parseSyncAccess(value.sync),
|
|
7808
7853
|
media: parseMediaAccess(value.media),
|
|
7809
7854
|
diagnostics: diagnostics?.generation === subject.participantGeneration ? diagnostics : null
|
|
@@ -7821,6 +7866,7 @@ function accessGrantFromParsed(value) {
|
|
|
7821
7866
|
participant_id: value.subject.participantId,
|
|
7822
7867
|
participant_generation: value.subject.participantGeneration
|
|
7823
7868
|
},
|
|
7869
|
+
...value.episodeStartedAt ? { episode_started_at: value.episodeStartedAt } : {},
|
|
7824
7870
|
sync: { token: value.sync.token, expires_at: value.sync.expiresAt },
|
|
7825
7871
|
media: {
|
|
7826
7872
|
token: value.media.token,
|
|
@@ -7929,6 +7975,10 @@ function requireDateTime(value) {
|
|
|
7929
7975
|
if (!Number.isFinite(Date.parse(dateTime))) throw new AccessGrantError();
|
|
7930
7976
|
return dateTime;
|
|
7931
7977
|
}
|
|
7978
|
+
function parseOptionalDateTime(value) {
|
|
7979
|
+
if (typeof value !== "string" || value.trim() === "" || !Number.isFinite(Date.parse(value))) return null;
|
|
7980
|
+
return value;
|
|
7981
|
+
}
|
|
7932
7982
|
function requireNonEmptyString(value) {
|
|
7933
7983
|
if (typeof value !== "string" || value.trim().length === 0) throw new AccessGrantError();
|
|
7934
7984
|
return value;
|
|
@@ -8422,6 +8472,12 @@ export {
|
|
|
8422
8472
|
AccessInvalidPasswordError,
|
|
8423
8473
|
AccessInvalidPasswordErrorWireSchema,
|
|
8424
8474
|
AccessInvalidPasswordErrorSchema,
|
|
8475
|
+
AccessMediaProofExpiredError,
|
|
8476
|
+
AccessMediaProofExpiredErrorWireSchema,
|
|
8477
|
+
AccessMediaProofExpiredErrorSchema,
|
|
8478
|
+
AccessMediaProofRejectedError,
|
|
8479
|
+
AccessMediaProofRejectedErrorWireSchema,
|
|
8480
|
+
AccessMediaProofRejectedErrorSchema,
|
|
8425
8481
|
AccessRecentAuthRequiredError,
|
|
8426
8482
|
AccessRecentAuthRequiredErrorWireSchema,
|
|
8427
8483
|
AccessRecentAuthRequiredErrorSchema,
|
|
@@ -135,6 +135,7 @@ async function waitFor(predicate, timeoutMs, timeoutMessage = "Timed out waiting
|
|
|
135
135
|
// src/media/client.ts
|
|
136
136
|
var EMPTY_LOCAL = Object.freeze([]);
|
|
137
137
|
var EMPTY_REMOTE = Object.freeze([]);
|
|
138
|
+
var INVALID_PUBLICATION_SIGNATURE = "\0invalid";
|
|
138
139
|
var CONNECTION_TIMEOUT_MS = 8e3;
|
|
139
140
|
var CloudflareSFUClient = class {
|
|
140
141
|
#localListeners = /* @__PURE__ */ new Set();
|
|
@@ -478,12 +479,13 @@ var CloudflareSFUClient = class {
|
|
|
478
479
|
}
|
|
479
480
|
}
|
|
480
481
|
async #reconcileRemotePublications(authoritative, generation) {
|
|
481
|
-
const cursor =
|
|
482
|
+
const cursor = this.#validatedRemotePublicationCursor(authoritative);
|
|
483
|
+
if (cursor === null) return;
|
|
482
484
|
const ordering = comparePublicationCursor(this.#cursor, cursor);
|
|
483
485
|
if (ordering !== "newer") return;
|
|
484
|
-
const desired = desiredRemotePublications(authoritative
|
|
486
|
+
const desired = this.#desiredRemotePublications(authoritative, cursor);
|
|
485
487
|
const toPull = [...desired].filter(([key, publication]) => this.#remoteTracks.get(key)?.publicationId !== publication.publicationId).map(([, publication]) => publication);
|
|
486
|
-
const pulled = await this.#
|
|
488
|
+
const pulled = await this.#pullWithRecovery(toPull, cursor, generation);
|
|
487
489
|
if (pulled === null) return;
|
|
488
490
|
this.#requireGeneration(generation);
|
|
489
491
|
const next = reconcileRemoteTracks(desired, pulled, this.#remoteTracks);
|
|
@@ -495,6 +497,52 @@ var CloudflareSFUClient = class {
|
|
|
495
497
|
this.#publishSnapshot();
|
|
496
498
|
this.#emitRemote();
|
|
497
499
|
}
|
|
500
|
+
#validatedRemotePublicationCursor(authoritative) {
|
|
501
|
+
if (this.#cursor?.signature === INVALID_PUBLICATION_SIGNATURE && this.#cursor.incarnation === authoritative.incarnation && this.#cursor.sequence === authoritative.sequence) return null;
|
|
502
|
+
try {
|
|
503
|
+
return validatePublicationSnapshot(authoritative);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
this.#quarantineInvalidRemotePublicationCursor(authoritative);
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
#desiredRemotePublications(authoritative, cursor) {
|
|
510
|
+
try {
|
|
511
|
+
return desiredRemotePublications(authoritative.publications, this.#participantId);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
this.#observeRemotePublicationCursor(cursor);
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async #pullWithRecovery(publications, cursor, generation) {
|
|
518
|
+
try {
|
|
519
|
+
return await this.#pull(publications, generation);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (generation !== this.#generation || this.#stopped) throw error;
|
|
522
|
+
if (!this.#canReplaceConnectionAfterRemotePull(error, generation)) {
|
|
523
|
+
this.#observeRemotePublicationCursor(cursor);
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
return this.#retryRemotePullOnReplacement(publications, cursor, generation);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
#canReplaceConnectionAfterRemotePull(error, generation) {
|
|
530
|
+
return error instanceof CloudflareSFUError && error.code === "signaling_failed" && !this.#hasLiveRemoteTracks() && this.#replaceMediaConnection !== void 0 && this.#replacementAttemptedGeneration !== generation;
|
|
531
|
+
}
|
|
532
|
+
#hasLiveRemoteTracks() {
|
|
533
|
+
return [...this.#remoteTracks.values()].some((publication) => publication.track.readyState === "live");
|
|
534
|
+
}
|
|
535
|
+
async #retryRemotePullOnReplacement(publications, cursor, generation) {
|
|
536
|
+
try {
|
|
537
|
+
await this.#replaceMediaConnectionForRecovery(generation);
|
|
538
|
+
await this.#republishPreparedTracksAfterRecovery(generation);
|
|
539
|
+
return await this.#pull(publications, generation);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (generation !== this.#generation || this.#stopped) throw error;
|
|
542
|
+
this.#observeRemotePublicationCursor(cursor);
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
498
546
|
async #pull(publications, generation) {
|
|
499
547
|
if (publications.length === 0) return [];
|
|
500
548
|
return this.#serializeSDP(async () => {
|
|
@@ -630,6 +678,18 @@ var CloudflareSFUClient = class {
|
|
|
630
678
|
}
|
|
631
679
|
this.#setPhase("recovering", null);
|
|
632
680
|
}
|
|
681
|
+
async #republishPreparedTracksAfterRecovery(generation) {
|
|
682
|
+
const enabled = [...this.#localTracks.values()].filter((state) => state.desiredEnabled && state.track.readyState !== "ended");
|
|
683
|
+
await this.#publishPreparedTracks(enabled, generation);
|
|
684
|
+
}
|
|
685
|
+
#observeRemotePublicationCursor(cursor) {
|
|
686
|
+
this.#cursor = cursor;
|
|
687
|
+
this.#publishSnapshot();
|
|
688
|
+
}
|
|
689
|
+
#quarantineInvalidRemotePublicationCursor(authoritative) {
|
|
690
|
+
if (!Number.isSafeInteger(authoritative.incarnation) || authoritative.incarnation < 0 || !Number.isSafeInteger(authoritative.sequence) || authoritative.sequence < 0) return;
|
|
691
|
+
this.#observeRemotePublicationCursor({ incarnation: authoritative.incarnation, sequence: authoritative.sequence, signature: INVALID_PUBLICATION_SIGNATURE });
|
|
692
|
+
}
|
|
633
693
|
#isRetryableConnectionFailure(error) {
|
|
634
694
|
return error instanceof CloudflareSFUError && error.code === "signaling_failed" && error.options.retryableConnection === true;
|
|
635
695
|
}
|
|
@@ -1916,6 +1916,8 @@ var MAX_LIVE_TARGET_RETRY_DELAY_MS = 1e3;
|
|
|
1916
1916
|
var MAX_PROJECTION_EVENT_EVIDENCE = 256;
|
|
1917
1917
|
var OPERATION_PENDING_POLL_INTERVAL_MS = 1e3;
|
|
1918
1918
|
var CLIENT_RESTART_CLOSE_CODE = 4e3;
|
|
1919
|
+
var DEFAULT_RECONNECT_DELAY_MS = 250;
|
|
1920
|
+
var MAX_RECONNECT_DELAY_MS = 5e3;
|
|
1919
1921
|
var MAX_COLLABORATION_REQUESTS_IN_FLIGHT = 64;
|
|
1920
1922
|
var V1SyncError = class extends Error {
|
|
1921
1923
|
constructor(message, code) {
|
|
@@ -1962,6 +1964,7 @@ var V1SyncClient = class {
|
|
|
1962
1964
|
#startupGeneration = 0;
|
|
1963
1965
|
#connectionGeneration = 0;
|
|
1964
1966
|
#reconnectTimer;
|
|
1967
|
+
#reconnectAttempt = 0;
|
|
1965
1968
|
#heartbeatTimer;
|
|
1966
1969
|
#missedHeartbeats = 0;
|
|
1967
1970
|
#unsubscribeLifecycle;
|
|
@@ -2855,6 +2858,7 @@ var V1SyncClient = class {
|
|
|
2855
2858
|
if (!this.#recovery?.controlComplete || !this.#media || !this.#presence) return;
|
|
2856
2859
|
this.#recovery = null;
|
|
2857
2860
|
this.#phase = { phase: "live" };
|
|
2861
|
+
this.#reconnectAttempt = 0;
|
|
2858
2862
|
this.#missedHeartbeats = 0;
|
|
2859
2863
|
for (const pending of this.#commands.values()) {
|
|
2860
2864
|
pending.retries = 0;
|
|
@@ -2936,10 +2940,13 @@ var V1SyncClient = class {
|
|
|
2936
2940
|
this.#phase = { phase: "connecting" };
|
|
2937
2941
|
this.#emit();
|
|
2938
2942
|
this.#clearReconnect();
|
|
2943
|
+
const configuredDelay = this.#options.reconnectDelayMs;
|
|
2944
|
+
const delay = configuredDelay === 0 ? 0 : Math.min(MAX_RECONNECT_DELAY_MS, (configuredDelay ?? DEFAULT_RECONNECT_DELAY_MS) * 2 ** Math.min(this.#reconnectAttempt, 5));
|
|
2945
|
+
this.#reconnectAttempt += 1;
|
|
2939
2946
|
this.#reconnectTimer = this.#clock().setTimeout(() => {
|
|
2940
2947
|
this.#reconnectTimer = void 0;
|
|
2941
2948
|
this.#connect();
|
|
2942
|
-
},
|
|
2949
|
+
}, delay);
|
|
2943
2950
|
}
|
|
2944
2951
|
#recover(reason) {
|
|
2945
2952
|
const socket = this.#socket;
|