@q9labsai/chalk-client 5.2.3 → 5.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-DWKTGBHR.js → chunk-3ASKGMD3.js} +6 -1
- package/dist/{chunk-IMMT22US.js → chunk-5YPQNMJE.js} +11 -0
- package/dist/{chunk-GA3JDPFI.js → chunk-AZONR3Q3.js} +106 -0
- package/dist/{chunk-4ZUN55Z2.js → chunk-LDPC2JZB.js} +187 -21
- package/dist/{chunk-6LNRDKJF.js → chunk-SYUGRHZ5.js} +1 -1
- package/dist/client.d.ts +60 -0
- package/dist/connection/dependencies.d.ts +6 -0
- package/dist/connection/report.d.ts +21 -0
- package/dist/effect.js +14 -4
- package/dist/generated/http-api.d.ts +59 -0
- package/dist/generated/schemas.d.ts +98 -0
- package/dist/index.js +5 -5
- package/dist/media/index.js +1 -1
- package/dist/media/types.d.ts +3 -0
- package/dist/public-invites/index.js +2 -2
- package/dist/sync/index.js +1 -1
- package/dist/sync/v1-types.d.ts +3 -0
- package/dist/transport-diagnostics.d.ts +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -311,7 +311,7 @@ SpaceClient negotiates `collaboration_v2` for the 24-emoji set and per-message r
|
|
|
311
311
|
|
|
312
312
|
`await client.media.setScreenShareEnabled(true, { surface: "browser", audio: true })` requests tab audio. The browser picker remains authoritative: selecting a window or declining audio still shares video normally. Audio is published separately as `screen_audio`; it starts, stops, and is revoked with the share, never with the microphone. `media.screenShare.hasAudio` reports local live audio; `participants.roster[].media.screenShareHasAudio` reports the remote publication. `media.remote` contains the received track.
|
|
313
313
|
|
|
314
|
-
The sharer keeps local playback (`suppressLocalAudioPlayback: false`). Display audio disables echo cancellation, noise suppression and automatic gain control to preserve music; microphone capture and its processing are unchanged. System audio is excluded to avoid sending the call back to itself. Headphones avoid acoustic doubling through the microphone; local playback cannot be preserved while guaranteeing that loudspeakers never leak into it. Chrome documents [local playback control](https://developer.chrome.com/blog/screen-sharing-improvements-in-chrome-109) and [system-audio feedback](https://developer.chrome.com/docs/web-platform/screen-sharing-controls). [Meet documents the same “Also share tab audio” picker control](https://support.google.com/meet/answer/9308856?hl=en).
|
|
314
|
+
The sharer keeps local playback (`suppressLocalAudioPlayback: false`). Display audio disables echo cancellation, noise suppression and automatic gain control to preserve music; microphone capture and its processing are unchanged. System audio is excluded to avoid sending the call back to itself. Headphones avoid acoustic doubling through the microphone; local playback cannot be preserved while guaranteeing that loudspeakers never leak into it. Chrome documents [local playback control](https://developer.chrome.com/blog/screen-sharing-improvements-in-chrome-109) and [system-audio feedback](https://developer.chrome.com/docs/web-platform/screen-sharing-controls). [Meet documents the same “Also share tab audio” picker control](https://support.google.com/meet/answer/9308856?hl=en).
|
|
315
315
|
|
|
316
316
|
Shared tab audio is excluded from recordings until the recording format supports it. React Native display capture behavior is unchanged.
|
|
317
317
|
|
|
@@ -1428,12 +1428,17 @@ function createCloudflareSFUHTTPTransport(options) {
|
|
|
1428
1428
|
const token = await withDeadline(CREDENTIAL_TIMEOUT_MS, (signal) => credential(signal));
|
|
1429
1429
|
if (!token.trim()) throw new CloudflareSFUError("The media credential provider returned an empty token", "signaling_failed");
|
|
1430
1430
|
return withDeadline(timeoutMs, async (signal) => {
|
|
1431
|
+
const startedAt = performance.now();
|
|
1431
1432
|
const response = await fetch(`${mediaPath}/${path}`, {
|
|
1432
1433
|
...init,
|
|
1433
1434
|
signal,
|
|
1434
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers }
|
|
1435
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...options.traceparent ? { traceparent: options.traceparent } : {}, ...init?.headers }
|
|
1435
1436
|
});
|
|
1436
1437
|
if (!response.ok) {
|
|
1438
|
+
try {
|
|
1439
|
+
options.onDiagnostic?.({ event: "media_http_error", http_status: response.status, duration_ms: performance.now() - startedAt });
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1437
1442
|
throw new CloudflareSFUError(`Chalk SFU signaling failed with HTTP ${response.status}`, "signaling_failed", {
|
|
1438
1443
|
status: response.status,
|
|
1439
1444
|
retryableConnection: response.status === 404 || response.status === 410 || response.status >= 500 && response.status < 600
|
|
@@ -3303,6 +3303,7 @@ var V1SyncClient = class {
|
|
|
3303
3303
|
this.#phase = { phase: "connecting" };
|
|
3304
3304
|
this.#connectAttemptCount += 1;
|
|
3305
3305
|
this.#connectStartedAt = this.#clock().now();
|
|
3306
|
+
const socketStartedAt = this.#connectStartedAt;
|
|
3306
3307
|
this.#opened = false;
|
|
3307
3308
|
if (this.#connectFailure?.opened) this.#connectFailure = void 0;
|
|
3308
3309
|
this.#emit();
|
|
@@ -3331,6 +3332,7 @@ var V1SyncClient = class {
|
|
|
3331
3332
|
if (socket !== this.#socket) return;
|
|
3332
3333
|
this.#clearDial();
|
|
3333
3334
|
this.#opened = true;
|
|
3335
|
+
this.#diagnostic({ event: "sync_socket_open", attempt: this.#connectAttemptCount, duration_ms: this.#clock().now() - socketStartedAt });
|
|
3334
3336
|
this.#connectFailure = void 0;
|
|
3335
3337
|
void this.#authenticate(socket);
|
|
3336
3338
|
};
|
|
@@ -3338,6 +3340,7 @@ var V1SyncClient = class {
|
|
|
3338
3340
|
this.#inbound = this.#inbound.then(() => this.#receive(socket, event.data));
|
|
3339
3341
|
};
|
|
3340
3342
|
socket.onclose = (event) => {
|
|
3343
|
+
this.#diagnostic({ event: "sync_socket_close", close_code: event.code, duration_ms: this.#clock().now() - socketStartedAt, ...this.#lastHeartbeatReplyAt === null ? {} : { heartbeat_gap_ms: this.#now() - this.#lastHeartbeatReplyAt } });
|
|
3341
3344
|
if (this.#started) {
|
|
3342
3345
|
this.#socketCloses.push({ code: event.code, wasClean: event.wasClean ?? null });
|
|
3343
3346
|
if (this.#socketCloses.length > 50) {
|
|
@@ -3376,6 +3379,7 @@ var V1SyncClient = class {
|
|
|
3376
3379
|
const hello = {
|
|
3377
3380
|
type: "hello",
|
|
3378
3381
|
protocol: 1,
|
|
3382
|
+
...this.#options.traceparent ? { traceparent: this.#options.traceparent } : {},
|
|
3379
3383
|
token,
|
|
3380
3384
|
streams: {
|
|
3381
3385
|
control: { cursor: this.#control && { revision: this.#control.revision, state_schema_version: this.#control.stateSchemaVersion, state_digest: this.#control.stateDigest } },
|
|
@@ -3829,12 +3833,19 @@ var V1SyncClient = class {
|
|
|
3829
3833
|
#updateLocalMediaStates() {
|
|
3830
3834
|
this.#liveTargets.updateFromPublications(this.#localPublications);
|
|
3831
3835
|
}
|
|
3836
|
+
#diagnostic(event) {
|
|
3837
|
+
try {
|
|
3838
|
+
this.#options.onDiagnostic?.(event);
|
|
3839
|
+
} catch {
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3832
3842
|
#startHeartbeat() {
|
|
3833
3843
|
this.#clearHeartbeat();
|
|
3834
3844
|
this.#heartbeatTimer = this.#clock().setTimeout(() => {
|
|
3835
3845
|
this.#heartbeatTimer = void 0;
|
|
3836
3846
|
if (this.#phase.phase !== "live") return;
|
|
3837
3847
|
this.#missedHeartbeats += 1;
|
|
3848
|
+
if (this.#missedHeartbeats > 1) this.#diagnostic({ event: "sync_heartbeat_gap", duration_ms: this.#missedHeartbeats * 2e4, ...this.#lastHeartbeatReplyAt === null ? {} : { heartbeat_gap_ms: this.#now() - this.#lastHeartbeatReplyAt } });
|
|
3838
3849
|
if (this.#missedHeartbeats > 2) {
|
|
3839
3850
|
this.#socket?.close(CLIENT_RESTART_CLOSE_CODE, "heartbeat timeout");
|
|
3840
3851
|
return;
|
|
@@ -243,6 +243,71 @@ var CloudflareSFUTracksRequestSchema = Schema.Struct({
|
|
|
243
243
|
})
|
|
244
244
|
)
|
|
245
245
|
});
|
|
246
|
+
var ConnectionReportRequestSchema = Schema.Struct({
|
|
247
|
+
events: Schema.Array(
|
|
248
|
+
Schema.Struct({
|
|
249
|
+
attempt: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
250
|
+
close_code: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
251
|
+
code: Schema.optional(
|
|
252
|
+
Schema.Literals([
|
|
253
|
+
"invalid_state",
|
|
254
|
+
"invalid_access",
|
|
255
|
+
"access_unavailable",
|
|
256
|
+
"permission_denied",
|
|
257
|
+
"device_not_found",
|
|
258
|
+
"device_busy",
|
|
259
|
+
"device_constraint_invalid",
|
|
260
|
+
"media_capture_failed",
|
|
261
|
+
"sync_start_failed",
|
|
262
|
+
"state_digest_recovery_failed",
|
|
263
|
+
"sync_hello_timeout",
|
|
264
|
+
"media_start_failed",
|
|
265
|
+
"join_cleanup_unconfirmed",
|
|
266
|
+
"sync_recovery_exhausted",
|
|
267
|
+
"media_recovery_exhausted",
|
|
268
|
+
"command_rejected",
|
|
269
|
+
"leave_unconfirmed",
|
|
270
|
+
"episode_ended",
|
|
271
|
+
"unsupported_environment",
|
|
272
|
+
"internal_error",
|
|
273
|
+
"collaboration_unavailable",
|
|
274
|
+
"chat_cursor_reset_required",
|
|
275
|
+
"rate_limited",
|
|
276
|
+
"invalid_payload"
|
|
277
|
+
])
|
|
278
|
+
),
|
|
279
|
+
duration_ms: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
280
|
+
elapsed_ms: Schema.Number,
|
|
281
|
+
event: Schema.Literals([
|
|
282
|
+
"space_join_span",
|
|
283
|
+
"state_changed",
|
|
284
|
+
"connection_failed",
|
|
285
|
+
"sync_connect_failed",
|
|
286
|
+
"sync_dial_timeout",
|
|
287
|
+
"access_refreshed",
|
|
288
|
+
"access_refresh_failed",
|
|
289
|
+
"recovery_attempt",
|
|
290
|
+
"recovery_succeeded",
|
|
291
|
+
"recovery_exhausted",
|
|
292
|
+
"cleanup_completed",
|
|
293
|
+
"cleanup_unconfirmed",
|
|
294
|
+
"sync_socket_open",
|
|
295
|
+
"sync_socket_close",
|
|
296
|
+
"sync_heartbeat_gap",
|
|
297
|
+
"media_state",
|
|
298
|
+
"media_http_error"
|
|
299
|
+
]),
|
|
300
|
+
heartbeat_gap_ms: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
301
|
+
http_status: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
302
|
+
outcome: Schema.optional(Schema.Literals(["started", "succeeded", "failed", "cancelled"])),
|
|
303
|
+
phase: Schema.optional(Schema.Literals(["idle", "connecting", "live", "recovering", "failed", "stopped", "new", "connected", "disconnected", "closed"])),
|
|
304
|
+
state: Schema.optional(Schema.Literals(["idle", "joining", "live", "reconnecting", "leaving", "left", "failed"])),
|
|
305
|
+
step: Schema.optional(Schema.Literals(["join", "acquire_initial_media", "access_initialize", "create_media_client", "create_sync_client", "start_media", "start_sync", "wait_for_sync_live", "confirm_entered"]))
|
|
306
|
+
})
|
|
307
|
+
).check(Schema.isMinLength(1), Schema.isMaxLength(64)),
|
|
308
|
+
trigger: Schema.Literals(["join_failed", "connection_lost", "recovery_exhausted"]),
|
|
309
|
+
version: Schema.Literal(1)
|
|
310
|
+
});
|
|
246
311
|
var CreateAPIKeyRequestSchema = Schema.Struct({
|
|
247
312
|
expires_at: DateTimeStringSchema,
|
|
248
313
|
name: Schema.String.check(Schema.isMinLength(1)),
|
|
@@ -1687,6 +1752,15 @@ var RenegotiateCloudflareSFU429ResponseHeadersSchema = Schema.Struct({
|
|
|
1687
1752
|
"X-RateLimit-Limit": RateLimitLimitHeaderSchema,
|
|
1688
1753
|
"X-RateLimit-Remaining": RateLimitRemainingHeaderSchema
|
|
1689
1754
|
});
|
|
1755
|
+
var ReportConnectionFailurePathParamsSchema = Schema.Struct({
|
|
1756
|
+
tenant_id: TenantIdSchema
|
|
1757
|
+
});
|
|
1758
|
+
var ReportConnectionFailureRequestBodySchema = ConnectionReportRequestSchema;
|
|
1759
|
+
var ReportConnectionFailure429ResponseHeadersSchema = Schema.Struct({
|
|
1760
|
+
"Retry-After": RetryAfterHeaderSchema,
|
|
1761
|
+
"X-RateLimit-Limit": RateLimitLimitHeaderSchema,
|
|
1762
|
+
"X-RateLimit-Remaining": RateLimitRemainingHeaderSchema
|
|
1763
|
+
});
|
|
1690
1764
|
var RequestRecordingExportPathParamsSchema = Schema.Struct({
|
|
1691
1765
|
recording_id: RecordingIdSchema,
|
|
1692
1766
|
tenant_id: TenantIdSchema
|
|
@@ -4794,6 +4868,16 @@ var RenegotiateCloudflareSFUErrorSchema = Schema.Union([
|
|
|
4794
4868
|
SpaceInvalidIdErrorSchema,
|
|
4795
4869
|
TenantInvalidIdErrorSchema
|
|
4796
4870
|
]);
|
|
4871
|
+
var ReportConnectionFailureErrorSchema = Schema.Union([
|
|
4872
|
+
AccessForbiddenErrorSchema,
|
|
4873
|
+
AccessUnauthenticatedErrorSchema,
|
|
4874
|
+
RequestInvalidErrorSchema,
|
|
4875
|
+
RequestPayloadTooLargeErrorSchema,
|
|
4876
|
+
RequestRateLimitedErrorSchema,
|
|
4877
|
+
ServiceInternalErrorSchema,
|
|
4878
|
+
ServiceUnavailableErrorSchema,
|
|
4879
|
+
TenantInvalidIdErrorSchema
|
|
4880
|
+
]);
|
|
4797
4881
|
var RequestRecordingExportErrorSchema = Schema.Union([
|
|
4798
4882
|
AccessForbiddenErrorSchema,
|
|
4799
4883
|
AccessUnauthenticatedErrorSchema,
|
|
@@ -5087,6 +5171,7 @@ var ChalkOperationPolicies = {
|
|
|
5087
5171
|
refreshSpacePublicInviteAccess: { maxBodyBytes: 1048576, rateLimit: { limit: 60, policy: "v1.authenticated.write", windowSeconds: 60 } },
|
|
5088
5172
|
removeEpisodeParticipant: { maxBodyBytes: 1048576, rateLimit: { limit: 60, policy: "v1.authenticated.write", windowSeconds: 60 } },
|
|
5089
5173
|
renegotiateCloudflareSFU: { maxBodyBytes: 1048576, rateLimit: { limit: 60, policy: "v1.authenticated.write", windowSeconds: 60 } },
|
|
5174
|
+
reportConnectionFailure: { maxBodyBytes: 32768, rateLimit: { limit: 6, policy: "connection_reports.participant", windowSeconds: 600 } },
|
|
5090
5175
|
requestRecordingExport: { maxBodyBytes: 1048576, rateLimit: { limit: 60, policy: "v1.authenticated.write", windowSeconds: 60 } },
|
|
5091
5176
|
requestTranscript: { maxBodyBytes: 1048576, rateLimit: { limit: 60, policy: "v1.authenticated.write", windowSeconds: 60 } },
|
|
5092
5177
|
restoreSpace: { rateLimit: { limit: 60, policy: "v1.authenticated.write", windowSeconds: 60 } },
|
|
@@ -5528,6 +5613,22 @@ var defaultGroup = HttpApiGroup.make("default").add(
|
|
|
5528
5613
|
ServiceUnavailableErrorSchema.pipe(HttpApiSchema.status(503))
|
|
5529
5614
|
]
|
|
5530
5615
|
})
|
|
5616
|
+
).add(
|
|
5617
|
+
HttpApiEndpoint.post("reportConnectionFailure", "/v1/tenants/:tenant_id/connection-reports", {
|
|
5618
|
+
params: ReportConnectionFailurePathParamsSchema,
|
|
5619
|
+
payload: ReportConnectionFailureRequestBodySchema,
|
|
5620
|
+
success: HttpApiSchema.Empty(204).pipe(HttpApiSchema.status(204)),
|
|
5621
|
+
error: [
|
|
5622
|
+
RequestInvalidErrorSchema.pipe(HttpApiSchema.status(400)),
|
|
5623
|
+
TenantInvalidIdErrorSchema.pipe(HttpApiSchema.status(400)),
|
|
5624
|
+
AccessUnauthenticatedErrorSchema.pipe(HttpApiSchema.status(401)),
|
|
5625
|
+
AccessForbiddenErrorSchema.pipe(HttpApiSchema.status(403)),
|
|
5626
|
+
RequestPayloadTooLargeErrorSchema.pipe(HttpApiSchema.status(413)),
|
|
5627
|
+
RequestRateLimitedErrorSchema.pipe(HttpApiSchema.status(429)),
|
|
5628
|
+
ServiceInternalErrorSchema.pipe(HttpApiSchema.status(500)),
|
|
5629
|
+
ServiceUnavailableErrorSchema.pipe(HttpApiSchema.status(503))
|
|
5630
|
+
]
|
|
5631
|
+
})
|
|
5531
5632
|
).add(
|
|
5532
5633
|
HttpApiEndpoint.delete("revokeAPIKey", "/v1/tenants/:tenant_id/api-keys/:api_key_id", {
|
|
5533
5634
|
params: RevokeAPIKeyPathParamsSchema,
|
|
@@ -6877,6 +6978,7 @@ export {
|
|
|
6877
6978
|
CloudflareSFURenegotiateResponseSchema,
|
|
6878
6979
|
CloudflareSFUTracksAPIResponseSchema,
|
|
6879
6980
|
CloudflareSFUTracksRequestSchema,
|
|
6981
|
+
ConnectionReportRequestSchema,
|
|
6880
6982
|
CreateAPIKeyRequestSchema,
|
|
6881
6983
|
CreateEpisodeRequestSchema,
|
|
6882
6984
|
CreateMembershipRequestSchema,
|
|
@@ -7174,6 +7276,9 @@ export {
|
|
|
7174
7276
|
RenegotiateCloudflareSFURequestBodySchema,
|
|
7175
7277
|
RenegotiateCloudflareSFUResponseSchema,
|
|
7176
7278
|
RenegotiateCloudflareSFU429ResponseHeadersSchema,
|
|
7279
|
+
ReportConnectionFailurePathParamsSchema,
|
|
7280
|
+
ReportConnectionFailureRequestBodySchema,
|
|
7281
|
+
ReportConnectionFailure429ResponseHeadersSchema,
|
|
7177
7282
|
RequestRecordingExportPathParamsSchema,
|
|
7178
7283
|
RequestRecordingExportRequestBodySchema,
|
|
7179
7284
|
RequestRecordingExportResponseSchema,
|
|
@@ -7642,6 +7747,7 @@ export {
|
|
|
7642
7747
|
RefreshSpacePublicInviteAccessErrorSchema,
|
|
7643
7748
|
RemoveEpisodeParticipantErrorSchema,
|
|
7644
7749
|
RenegotiateCloudflareSFUErrorSchema,
|
|
7750
|
+
ReportConnectionFailureErrorSchema,
|
|
7645
7751
|
RequestRecordingExportErrorSchema,
|
|
7646
7752
|
RequestTranscriptErrorSchema,
|
|
7647
7753
|
RestoreSpaceErrorSchema,
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
CloudflareSFUClient,
|
|
3
3
|
createCloudflareSFUHTTPTransport,
|
|
4
4
|
describeMediaCaptureError
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3ASKGMD3.js";
|
|
6
6
|
import {
|
|
7
7
|
AccessGrantError,
|
|
8
8
|
requireParsedAccessGrant
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-AZONR3Q3.js";
|
|
10
10
|
import {
|
|
11
11
|
ChatAttachmentMimeTypeSchema,
|
|
12
12
|
ChatReactionSetFrameSchema,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
formatSyncConnectDiagnostic,
|
|
21
21
|
scheduleSyncDialDeadline,
|
|
22
22
|
syncConnectFailureCode
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-5YPQNMJE.js";
|
|
24
24
|
|
|
25
25
|
// src/access/manager.ts
|
|
26
26
|
import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
|
|
@@ -59,7 +59,7 @@ var makeConnectionAccessLayer = (provider, refreshWindowMs = DEFAULT_REFRESH_WIN
|
|
|
59
59
|
automaticMediaReplacementListeners.add(listener);
|
|
60
60
|
return () => automaticMediaReplacementListeners.delete(listener);
|
|
61
61
|
}),
|
|
62
|
-
requiresRefresh: millisecondsUntilRefresh.pipe(Effect.map((
|
|
62
|
+
requiresRefresh: millisecondsUntilRefresh.pipe(Effect.map((milliseconds2) => milliseconds2 === null || milliseconds2 === 0)),
|
|
63
63
|
millisecondsUntilRefresh,
|
|
64
64
|
clear: SynchronizedRef.set(state, { access: null })
|
|
65
65
|
};
|
|
@@ -2156,7 +2156,7 @@ var ChalkWhiteboardV1Client = class {
|
|
|
2156
2156
|
return this.#options.clock ?? {
|
|
2157
2157
|
now: Date.now,
|
|
2158
2158
|
monotonicNow: () => globalThis.performance.now(),
|
|
2159
|
-
setTimeout: (callback,
|
|
2159
|
+
setTimeout: (callback, milliseconds2) => globalThis.setTimeout(callback, milliseconds2),
|
|
2160
2160
|
clearTimeout: (handle) => globalThis.clearTimeout(handle)
|
|
2161
2161
|
};
|
|
2162
2162
|
}
|
|
@@ -2288,6 +2288,124 @@ function createChalkWhiteboardV1Client(options) {
|
|
|
2288
2288
|
});
|
|
2289
2289
|
}
|
|
2290
2290
|
|
|
2291
|
+
// src/connection/report.ts
|
|
2292
|
+
var MAX_EVENTS = 64;
|
|
2293
|
+
var MAX_BYTES = 32768;
|
|
2294
|
+
var MAX_MS = 2147483647;
|
|
2295
|
+
var COOLDOWN_MS = 6e4;
|
|
2296
|
+
var participantCooldowns = /* @__PURE__ */ new Map();
|
|
2297
|
+
var milliseconds = (value) => Math.max(0, Math.min(MAX_MS, Math.round(Number.isFinite(value) ? value : 0)));
|
|
2298
|
+
var optionalMilliseconds = (value) => value == null ? void 0 : milliseconds(value);
|
|
2299
|
+
function reserveParticipantReport(access, now) {
|
|
2300
|
+
const key = `${access.subject.tenantId}:${access.subject.participantId}`;
|
|
2301
|
+
for (const [participant, until] of participantCooldowns) if (until <= now) participantCooldowns.delete(participant);
|
|
2302
|
+
if (participantCooldowns.has(key)) return false;
|
|
2303
|
+
if (participantCooldowns.size >= 128) participantCooldowns.delete(participantCooldowns.keys().next().value ?? "");
|
|
2304
|
+
participantCooldowns.set(key, now + COOLDOWN_MS);
|
|
2305
|
+
return true;
|
|
2306
|
+
}
|
|
2307
|
+
var ConnectionReporter = class {
|
|
2308
|
+
#events = [];
|
|
2309
|
+
#startedAt;
|
|
2310
|
+
#options;
|
|
2311
|
+
#pending = null;
|
|
2312
|
+
#unsubscribe;
|
|
2313
|
+
#controller = null;
|
|
2314
|
+
#retryOnReconnect = false;
|
|
2315
|
+
#disposed = false;
|
|
2316
|
+
constructor(options) {
|
|
2317
|
+
this.#options = options;
|
|
2318
|
+
this.#startedAt = options.now();
|
|
2319
|
+
}
|
|
2320
|
+
recordDiagnostic(diagnostic) {
|
|
2321
|
+
this.#record({
|
|
2322
|
+
event: diagnostic.event,
|
|
2323
|
+
elapsed_ms: this.#elapsed(),
|
|
2324
|
+
state: diagnostic.state,
|
|
2325
|
+
step: diagnostic.step,
|
|
2326
|
+
outcome: diagnostic.outcome,
|
|
2327
|
+
code: diagnostic.code,
|
|
2328
|
+
attempt: optionalMilliseconds(diagnostic.attempt),
|
|
2329
|
+
duration_ms: optionalMilliseconds(diagnostic.sync?.durationMs ?? diagnostic.durationMs),
|
|
2330
|
+
close_code: diagnostic.sync?.closeCode ?? void 0,
|
|
2331
|
+
heartbeat_gap_ms: optionalMilliseconds(diagnostic.recovery?.lastHeartbeatReplyAgoMs)
|
|
2332
|
+
});
|
|
2333
|
+
if (diagnostic.event === "recovery_exhausted") this.#report("recovery_exhausted");
|
|
2334
|
+
else if (diagnostic.event === "connection_failed") this.#report(diagnostic.state === "joining" ? "join_failed" : "connection_lost");
|
|
2335
|
+
}
|
|
2336
|
+
recordTransport(event) {
|
|
2337
|
+
this.#record({
|
|
2338
|
+
event: event.event,
|
|
2339
|
+
elapsed_ms: this.#elapsed(),
|
|
2340
|
+
...event.phase ? { phase: event.phase } : {},
|
|
2341
|
+
...event.attempt == null ? {} : { attempt: milliseconds(event.attempt) },
|
|
2342
|
+
...event.duration_ms == null ? {} : { duration_ms: milliseconds(event.duration_ms) },
|
|
2343
|
+
...event.heartbeat_gap_ms == null ? {} : { heartbeat_gap_ms: milliseconds(event.heartbeat_gap_ms) },
|
|
2344
|
+
...event.close_code == null ? {} : { close_code: event.close_code },
|
|
2345
|
+
...event.http_status == null ? {} : { http_status: event.http_status }
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
dispose() {
|
|
2349
|
+
this.#disposed = true;
|
|
2350
|
+
this.#controller?.abort();
|
|
2351
|
+
this.#stopListening();
|
|
2352
|
+
this.#pending = null;
|
|
2353
|
+
this.#events.length = 0;
|
|
2354
|
+
}
|
|
2355
|
+
get traceparent() {
|
|
2356
|
+
return this.#options.traceparent;
|
|
2357
|
+
}
|
|
2358
|
+
#elapsed() {
|
|
2359
|
+
return milliseconds(this.#options.now() - this.#startedAt);
|
|
2360
|
+
}
|
|
2361
|
+
#record(event) {
|
|
2362
|
+
if (this.#disposed) return;
|
|
2363
|
+
this.#events.push(event);
|
|
2364
|
+
if (this.#events.length > MAX_EVENTS) this.#events.shift();
|
|
2365
|
+
}
|
|
2366
|
+
#report(trigger) {
|
|
2367
|
+
if (this.#disposed || this.#pending) return;
|
|
2368
|
+
const access = this.#options.access();
|
|
2369
|
+
if (!access) return;
|
|
2370
|
+
if (!reserveParticipantReport(access, this.#options.now())) return;
|
|
2371
|
+
const events = this.#events.slice();
|
|
2372
|
+
const report = { version: 1, trigger, events };
|
|
2373
|
+
while (events.length && new TextEncoder().encode(JSON.stringify(report)).byteLength > MAX_BYTES) events.shift();
|
|
2374
|
+
if (!events.length) return;
|
|
2375
|
+
this.#pending = { access, report, attempts: 0 };
|
|
2376
|
+
this.#unsubscribe = this.#options.subscribeOnline(() => {
|
|
2377
|
+
this.#retryOnReconnect = true;
|
|
2378
|
+
void this.#flush();
|
|
2379
|
+
});
|
|
2380
|
+
void this.#flush();
|
|
2381
|
+
}
|
|
2382
|
+
async #flush() {
|
|
2383
|
+
const pending = this.#pending;
|
|
2384
|
+
if (!pending || this.#controller || this.#disposed || !this.#options.online()) return;
|
|
2385
|
+
this.#retryOnReconnect = false;
|
|
2386
|
+
const controller = new AbortController();
|
|
2387
|
+
this.#controller = controller;
|
|
2388
|
+
pending.attempts += 1;
|
|
2389
|
+
const timer = globalThis.setTimeout(() => controller.abort(), 5e3);
|
|
2390
|
+
try {
|
|
2391
|
+
await this.#options.send(pending.access, pending.report, controller.signal, this.traceparent);
|
|
2392
|
+
this.#pending = null;
|
|
2393
|
+
} catch {
|
|
2394
|
+
if (pending.attempts >= 2) this.#pending = null;
|
|
2395
|
+
} finally {
|
|
2396
|
+
globalThis.clearTimeout(timer);
|
|
2397
|
+
this.#controller = null;
|
|
2398
|
+
if (!this.#pending) this.#stopListening();
|
|
2399
|
+
if (this.#retryOnReconnect) void this.#flush();
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
#stopListening() {
|
|
2403
|
+
this.#unsubscribe?.();
|
|
2404
|
+
this.#unsubscribe = void 0;
|
|
2405
|
+
this.#retryOnReconnect = false;
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
|
|
2291
2409
|
// src/connection/types.ts
|
|
2292
2410
|
var ConnectionError = class extends Error {
|
|
2293
2411
|
code;
|
|
@@ -2359,9 +2477,34 @@ function createDefaultConnectionDependencies(options) {
|
|
|
2359
2477
|
return {
|
|
2360
2478
|
clock: {
|
|
2361
2479
|
now: () => Date.now(),
|
|
2362
|
-
setTimeout: (callback,
|
|
2480
|
+
setTimeout: (callback, milliseconds2) => globalThis.setTimeout(callback, milliseconds2),
|
|
2363
2481
|
clearTimeout: (handle) => globalThis.clearTimeout(handle)
|
|
2364
2482
|
},
|
|
2483
|
+
createConnectionReporter: (access) => {
|
|
2484
|
+
const random = globalThis.crypto;
|
|
2485
|
+
if (!random?.randomUUID) return void 0;
|
|
2486
|
+
return new ConnectionReporter({
|
|
2487
|
+
traceparent: `00-${random.randomUUID().replaceAll("-", "")}-${random.randomUUID().replaceAll("-", "").slice(0, 16)}-01`,
|
|
2488
|
+
now: () => performance.now(),
|
|
2489
|
+
access,
|
|
2490
|
+
online: () => globalThis.navigator?.onLine !== false,
|
|
2491
|
+
subscribeOnline: (listener) => {
|
|
2492
|
+
globalThis.window?.addEventListener("online", listener);
|
|
2493
|
+
return () => globalThis.window?.removeEventListener("online", listener);
|
|
2494
|
+
},
|
|
2495
|
+
send: async (grant, report, signal, traceparent) => {
|
|
2496
|
+
const response = await fetch(`${options.apiBaseURL.replace(/\/$/, "")}/v1/tenants/${encodeURIComponent(grant.subject.tenantId)}/connection-reports`, {
|
|
2497
|
+
method: "POST",
|
|
2498
|
+
headers: { Authorization: `Bearer ${grant.media.token}`, "Content-Type": "application/json", traceparent },
|
|
2499
|
+
body: JSON.stringify(report),
|
|
2500
|
+
signal,
|
|
2501
|
+
keepalive: true,
|
|
2502
|
+
credentials: "omit"
|
|
2503
|
+
});
|
|
2504
|
+
await response.body?.cancel();
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
},
|
|
2365
2508
|
mediaDevices: createBrowserMediaDevices(),
|
|
2366
2509
|
cancelPendingAdmission: async (access, signal) => {
|
|
2367
2510
|
const { tenantId, spaceId, episodeId, participantId } = access.subject;
|
|
@@ -2426,6 +2569,8 @@ function createMediaClient(apiBaseURL, input) {
|
|
|
2426
2569
|
transport: createCloudflareSFUHTTPTransport({
|
|
2427
2570
|
apiBaseURL,
|
|
2428
2571
|
credential: input.credential,
|
|
2572
|
+
onDiagnostic: input.onTransportDiagnostic,
|
|
2573
|
+
traceparent: input.traceparent,
|
|
2429
2574
|
tenantId: subject.tenantId,
|
|
2430
2575
|
spaceId: subject.spaceId,
|
|
2431
2576
|
episodeId: subject.episodeId,
|
|
@@ -2467,6 +2612,8 @@ function bindConnectionMediaClient(client, restart) {
|
|
|
2467
2612
|
function createSyncClient(syncURL, input) {
|
|
2468
2613
|
return createV1SyncClient({
|
|
2469
2614
|
url: syncURL,
|
|
2615
|
+
onDiagnostic: input.onTransportDiagnostic,
|
|
2616
|
+
traceparent: input.traceparent,
|
|
2470
2617
|
token: input.token,
|
|
2471
2618
|
mediaPlane: input.media,
|
|
2472
2619
|
collaboration: { name: "collaboration_v2", chatCursor: { afterSequence: null, retainedFloorSequence: null } },
|
|
@@ -2498,7 +2645,7 @@ function whiteboardURL(syncURL) {
|
|
|
2498
2645
|
}
|
|
2499
2646
|
|
|
2500
2647
|
// src/foreground-deadline.ts
|
|
2501
|
-
function scheduleForegroundDeadline(clock,
|
|
2648
|
+
function scheduleForegroundDeadline(clock, milliseconds2, expired) {
|
|
2502
2649
|
let timer;
|
|
2503
2650
|
let cancelled = false;
|
|
2504
2651
|
const arm = () => {
|
|
@@ -2506,9 +2653,9 @@ function scheduleForegroundDeadline(clock, milliseconds, expired) {
|
|
|
2506
2653
|
timer = clock.setTimeout(() => {
|
|
2507
2654
|
if (cancelled) return;
|
|
2508
2655
|
const elapsed = clock.now() - started;
|
|
2509
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden" || elapsed >
|
|
2656
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden" || elapsed > milliseconds2 + 2e3 || elapsed < 0) arm();
|
|
2510
2657
|
else expired();
|
|
2511
|
-
},
|
|
2658
|
+
}, milliseconds2);
|
|
2512
2659
|
};
|
|
2513
2660
|
arm();
|
|
2514
2661
|
return () => {
|
|
@@ -2594,6 +2741,7 @@ var MEDIA_RECOVERY_TIMEOUT_MS = 6e4;
|
|
|
2594
2741
|
var START_TIMEOUT_MS = 3e4;
|
|
2595
2742
|
var LEAVE_TIMEOUT_MS = 5e3;
|
|
2596
2743
|
var RECOVERY_BUDGET_MS = 1e4;
|
|
2744
|
+
var SYNC_RECOVERY_BUDGET_MS = MEDIA_RECOVERY_TIMEOUT_MS;
|
|
2597
2745
|
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
2598
2746
|
var REFRESH_RETRY_MS = 5e3;
|
|
2599
2747
|
var RECONNECT_GRACE_MS = 250;
|
|
@@ -2634,7 +2782,16 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2634
2782
|
const clock = yield* Clock2.Clock;
|
|
2635
2783
|
const context = yield* Effect2.context();
|
|
2636
2784
|
const serviceScope = yield* Effect2.scope;
|
|
2637
|
-
const
|
|
2785
|
+
const reporter = platform.createConnectionReporter?.(access.currentUnsafe);
|
|
2786
|
+
const diagnostics = new ConnectionDiagnostics({
|
|
2787
|
+
now: () => clock.currentTimeMillisUnsafe(),
|
|
2788
|
+
...options.diagnostics,
|
|
2789
|
+
onEvent: (event) => {
|
|
2790
|
+
reporter?.recordDiagnostic(event);
|
|
2791
|
+
options.diagnostics?.onEvent?.(event);
|
|
2792
|
+
}
|
|
2793
|
+
});
|
|
2794
|
+
const onTransportDiagnostic = (event) => reporter?.recordTransport(event);
|
|
2638
2795
|
const snapshotRef = yield* SubscriptionRef.make(idleSnapshot());
|
|
2639
2796
|
const queue = yield* Queue.unbounded();
|
|
2640
2797
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -2677,10 +2834,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2677
2834
|
};
|
|
2678
2835
|
const mediaAttempt = { expiresAt: null };
|
|
2679
2836
|
const foreign7 = (operation, code = "internal_error", message = "A browser transport operation failed") => Effect2.tryPromise({ try: operation, catch: (cause) => accessRejected(cause) ? lifecycleFailure("invalid_access", false, "Access was rejected", cause) : lifecycleFailure(code, true, message, cause) });
|
|
2680
|
-
const withinForegroundBudget = (effect,
|
|
2837
|
+
const withinForegroundBudget = (effect, milliseconds2, failure2) => Effect2.raceFirst(
|
|
2681
2838
|
effect,
|
|
2682
2839
|
Effect2.callback((resume) => {
|
|
2683
|
-
const cancel = scheduleForegroundDeadline(platform.clock,
|
|
2840
|
+
const cancel = scheduleForegroundDeadline(platform.clock, milliseconds2, () => resume(Effect2.fail(typeof failure2 === "function" ? failure2() : failure2)));
|
|
2684
2841
|
return Effect2.sync(cancel);
|
|
2685
2842
|
})
|
|
2686
2843
|
);
|
|
@@ -2741,10 +2898,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2741
2898
|
const scheduleRefresh = (delay) => Effect2.gen(function* () {
|
|
2742
2899
|
yield* stopRefresh();
|
|
2743
2900
|
if (!active(model)) return;
|
|
2744
|
-
const
|
|
2901
|
+
const milliseconds2 = delay ?? (yield* access.millisecondsUntilRefresh) ?? 0;
|
|
2745
2902
|
const scope = model.activeScope;
|
|
2746
2903
|
if (!scope) return;
|
|
2747
|
-
model.refreshFiber = yield* Effect2.forkIn(Effect2.sleep(Math.max(0,
|
|
2904
|
+
model.refreshFiber = yield* Effect2.forkIn(Effect2.sleep(Math.max(0, milliseconds2)).pipe(Effect2.andThen(enqueue(refreshAccess())), Effect2.ignore), scope);
|
|
2748
2905
|
});
|
|
2749
2906
|
const refreshAccess = () => Effect2.gen(function* () {
|
|
2750
2907
|
const scope = model.activeScope;
|
|
@@ -2923,6 +3080,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2923
3080
|
if (media === model.media) mediaAttempt.expiresAt = platform.clock.now() + MEDIA_RECOVERY_TIMEOUT_MS;
|
|
2924
3081
|
return replacement.media;
|
|
2925
3082
|
}),
|
|
3083
|
+
onTransportDiagnostic,
|
|
3084
|
+
traceparent: reporter?.traceparent,
|
|
2926
3085
|
onFailure: () => enqueueBackground(handleMediaFailure()),
|
|
2927
3086
|
onScreenEnded: () => enqueueBackground(notifyScreenEnded())
|
|
2928
3087
|
}),
|
|
@@ -2933,7 +3092,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2933
3092
|
const sync = yield* trace(
|
|
2934
3093
|
"create_sync_client",
|
|
2935
3094
|
Effect2.try({
|
|
2936
|
-
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media }),
|
|
3095
|
+
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media, onTransportDiagnostic, traceparent: reporter?.traceparent }),
|
|
2937
3096
|
catch: (cause) => accessRejected(cause) ? lifecycleFailure("invalid_access", false, "Access was rejected", cause) : lifecycleFailure("sync_start_failed", true, "The sync layer could not start", cause)
|
|
2938
3097
|
})
|
|
2939
3098
|
);
|
|
@@ -3134,7 +3293,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3134
3293
|
const recoveryPlan = (kind) => Clock2.currentTimeMillis.pipe(
|
|
3135
3294
|
Effect2.map((now) => ({
|
|
3136
3295
|
kind,
|
|
3137
|
-
deadline: now + boundedInteger(options.recovery?.budgetMs, RECOVERY_BUDGET_MS, 1, 6e4),
|
|
3296
|
+
deadline: now + boundedInteger(options.recovery?.budgetMs, kind === "sync" ? SYNC_RECOVERY_BUDGET_MS : RECOVERY_BUDGET_MS, 1, 6e4),
|
|
3138
3297
|
attempts: boundedInteger(options.recovery?.maxAttempts, MAX_RECOVERY_ATTEMPTS, 1, 10),
|
|
3139
3298
|
delays: options.recovery?.backoffMs?.length ? options.recovery.backoffMs : [100, 250, 500]
|
|
3140
3299
|
}))
|
|
@@ -3259,7 +3418,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3259
3418
|
attempt.expiresAt = now + MEDIA_RECOVERY_TIMEOUT_MS;
|
|
3260
3419
|
}
|
|
3261
3420
|
if ((attempt.started || snapshot.connection.phase !== "idle") && (yield* syncNeedsReplacement(snapshot))) return yield* restartSync(attempt);
|
|
3262
|
-
yield* waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs,
|
|
3421
|
+
yield* waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs, SYNC_RECOVERY_BUDGET_MS, 1, 6e4));
|
|
3263
3422
|
});
|
|
3264
3423
|
const restartSync = (attempt) => Effect2.gen(function* () {
|
|
3265
3424
|
const epoch = model.epoch;
|
|
@@ -3273,7 +3432,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3273
3432
|
model.syncBindingCleanup?.();
|
|
3274
3433
|
model.sync = null;
|
|
3275
3434
|
const sync2 = yield* Effect2.try({
|
|
3276
|
-
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media }),
|
|
3435
|
+
try: () => platform.createSyncClient({ access: grant, token: () => toPromise(access.getSyncToken()), media, onTransportDiagnostic, traceparent: reporter?.traceparent }),
|
|
3277
3436
|
catch: (cause) => lifecycleFailure("sync_start_failed", true, "The sync layer could not start", cause)
|
|
3278
3437
|
});
|
|
3279
3438
|
model.sync = sync2;
|
|
@@ -3290,7 +3449,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3290
3449
|
else if (attempt.client === sync) attempt.started = true;
|
|
3291
3450
|
})
|
|
3292
3451
|
).pipe(
|
|
3293
|
-
Effect2.andThen(waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs,
|
|
3452
|
+
Effect2.andThen(waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs, SYNC_RECOVERY_BUDGET_MS, 1, 6e4))),
|
|
3294
3453
|
Effect2.andThen(Effect2.suspend(() => sync === model.sync && model.epoch === epoch && active(model) ? confirmSyncEntry(sync) : Effect2.fail(lifecycleFailure("invalid_state", false, "Sync recovery was superseded")))),
|
|
3295
3454
|
Effect2.tapError(
|
|
3296
3455
|
() => enqueue(
|
|
@@ -3341,6 +3500,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3341
3500
|
yield* transition("left");
|
|
3342
3501
|
});
|
|
3343
3502
|
const syncNeedsRecovery = (snapshot) => active(model) && (snapshot.connection.phase === "connecting" || snapshot.connection.phase === "recovering" || snapshot.connection.phase === "terminal");
|
|
3503
|
+
const mediaNeedsRecovery = (snapshot) => active(model) && (snapshot.connection.phase === "recovering" || snapshot.connection.phase === "failed" && snapshot.failure?.recoverable === true);
|
|
3344
3504
|
const recordFailure = (failure2) => Effect2.sync(() => {
|
|
3345
3505
|
diagnostics.record({ event: "connection_failed", state: model.state, epoch: model.epoch, code: failure2.code, ...failure2.sync ? { sync: failure2.sync } : {} });
|
|
3346
3506
|
});
|
|
@@ -3353,8 +3513,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3353
3513
|
const handleMediaSnapshot = (media, snapshot) => Effect2.gen(function* () {
|
|
3354
3514
|
if (media !== model.media || media.getSnapshot() !== snapshot) return;
|
|
3355
3515
|
if (snapshot === model.mediaSnapshot) return;
|
|
3516
|
+
const phase = mediaDiagnosticPhase(snapshot);
|
|
3517
|
+
if (phase !== mediaDiagnosticPhase(model.mediaSnapshot)) onTransportDiagnostic({ event: "media_state", phase });
|
|
3356
3518
|
model.mediaSnapshot = snapshot;
|
|
3357
|
-
if (
|
|
3519
|
+
if (mediaNeedsRecovery(snapshot)) yield* recover("media");
|
|
3358
3520
|
yield* publish();
|
|
3359
3521
|
});
|
|
3360
3522
|
const handleMediaFailure = () => model.media ? handleMediaSnapshot(model.media, model.media.getSnapshot()) : Effect2.void;
|
|
@@ -3418,6 +3580,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3418
3580
|
yield* Effect2.addFinalizer(
|
|
3419
3581
|
() => Effect2.gen(function* () {
|
|
3420
3582
|
model.closed = true;
|
|
3583
|
+
reporter?.dispose();
|
|
3421
3584
|
accessReplacementUnsubscribe();
|
|
3422
3585
|
foregroundUnsubscribe?.();
|
|
3423
3586
|
if (activeJoin) yield* Fiber.interrupt(activeJoin);
|
|
@@ -3491,7 +3654,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3491
3654
|
),
|
|
3492
3655
|
runPortCommand: (operation) => withFreshAccess(operation),
|
|
3493
3656
|
nowUnsafe: () => clock.currentTimeMillisUnsafe(),
|
|
3494
|
-
scheduleUnsafe: (callback,
|
|
3657
|
+
scheduleUnsafe: (callback, milliseconds2) => platform.clock.setTimeout(callback, milliseconds2),
|
|
3495
3658
|
cancelScheduleUnsafe: (handle) => platform.clock.clearTimeout(handle),
|
|
3496
3659
|
createId: () => platform.createId?.() ?? `local-${++fallbackIdentifier}`
|
|
3497
3660
|
};
|
|
@@ -3617,6 +3780,9 @@ function syncPhase(phase) {
|
|
|
3617
3780
|
if (phase === "connecting") return "connecting";
|
|
3618
3781
|
return "idle";
|
|
3619
3782
|
}
|
|
3783
|
+
function mediaDiagnosticPhase(snapshot) {
|
|
3784
|
+
return snapshot?.connection.peerConnectionState ?? snapshot?.connection.phase;
|
|
3785
|
+
}
|
|
3620
3786
|
function mediaPhase(phase) {
|
|
3621
3787
|
if (phase === "live") return "healthy";
|
|
3622
3788
|
if (phase === "recovering") return "recovering";
|
package/dist/client.d.ts
CHANGED
|
@@ -1761,6 +1761,66 @@ export declare const createChalkEffectClient: (options: ChalkEffectClientOptions
|
|
|
1761
1761
|
readonly message: import("effect/Schema").String;
|
|
1762
1762
|
}>;
|
|
1763
1763
|
}>, never, never>>, never, never>, never, never>;
|
|
1764
|
+
readonly reportConnectionFailure: HttpApiClient.Client.Method<import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"reportConnectionFailure", "POST", "/v1/tenants/:tenant_id/connection-reports", import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
1765
|
+
readonly tenant_id: import("effect/Schema").brand<import("effect/Schema").String, "TenantId">;
|
|
1766
|
+
}>>, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
1767
|
+
readonly events: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
1768
|
+
readonly attempt: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1769
|
+
readonly close_code: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1770
|
+
readonly code: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["invalid_state", "invalid_access", "access_unavailable", "permission_denied", "device_not_found", "device_busy", "device_constraint_invalid", "media_capture_failed", "sync_start_failed", "state_digest_recovery_failed", "sync_hello_timeout", "media_start_failed", "join_cleanup_unconfirmed", "sync_recovery_exhausted", "media_recovery_exhausted", "command_rejected", "leave_unconfirmed", "episode_ended", "unsupported_environment", "internal_error", "collaboration_unavailable", "chat_cursor_reset_required", "rate_limited", "invalid_payload"]>>;
|
|
1771
|
+
readonly duration_ms: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1772
|
+
readonly elapsed_ms: import("effect/Schema").Number;
|
|
1773
|
+
readonly event: import("effect/Schema").Literals<readonly ["space_join_span", "state_changed", "connection_failed", "sync_connect_failed", "sync_dial_timeout", "access_refreshed", "access_refresh_failed", "recovery_attempt", "recovery_succeeded", "recovery_exhausted", "cleanup_completed", "cleanup_unconfirmed", "sync_socket_open", "sync_socket_close", "sync_heartbeat_gap", "media_state", "media_http_error"]>;
|
|
1774
|
+
readonly heartbeat_gap_ms: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1775
|
+
readonly http_status: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1776
|
+
readonly outcome: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["started", "succeeded", "failed", "cancelled"]>>;
|
|
1777
|
+
readonly phase: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["idle", "connecting", "live", "recovering", "failed", "stopped", "new", "connected", "disconnected", "closed"]>>;
|
|
1778
|
+
readonly state: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["idle", "joining", "live", "reconnecting", "leaving", "left", "failed"]>>;
|
|
1779
|
+
readonly step: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["join", "acquire_initial_media", "access_initialize", "create_media_client", "create_sync_client", "start_media", "start_sync", "wait_for_sync_live", "confirm_entered"]>>;
|
|
1780
|
+
}>>;
|
|
1781
|
+
readonly trigger: import("effect/Schema").Literals<readonly ["join_failed", "connection_lost", "recovery_exhausted"]>;
|
|
1782
|
+
readonly version: import("effect/Schema").Literal<1>;
|
|
1783
|
+
}>>, never, import("effect/Schema").toCodecJson<import("effect/Schema").Void>, import("effect/Schema").toCodecJson<import("effect/Schema").decodeTo<typeof import("./effect").AccessForbiddenError, import("effect/Schema").Struct<{
|
|
1784
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1785
|
+
readonly code: import("effect/Schema").Literal<"access.forbidden">;
|
|
1786
|
+
readonly message: import("effect/Schema").String;
|
|
1787
|
+
}>;
|
|
1788
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof import("./effect").AccessUnauthenticatedError, import("effect/Schema").Struct<{
|
|
1789
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1790
|
+
readonly code: import("effect/Schema").Literal<"access.unauthenticated">;
|
|
1791
|
+
readonly message: import("effect/Schema").String;
|
|
1792
|
+
}>;
|
|
1793
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof import("./effect").RequestInvalidError, import("effect/Schema").Struct<{
|
|
1794
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1795
|
+
readonly code: import("effect/Schema").Literal<"request.invalid">;
|
|
1796
|
+
readonly message: import("effect/Schema").String;
|
|
1797
|
+
}>;
|
|
1798
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof import("./effect").RequestPayloadTooLargeError, import("effect/Schema").Struct<{
|
|
1799
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1800
|
+
readonly code: import("effect/Schema").Literal<"request.payload_too_large">;
|
|
1801
|
+
readonly message: import("effect/Schema").String;
|
|
1802
|
+
}>;
|
|
1803
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof import("./effect").RequestRateLimitedError, import("effect/Schema").Struct<{
|
|
1804
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1805
|
+
readonly code: import("effect/Schema").Literal<"request.rate_limited">;
|
|
1806
|
+
readonly message: import("effect/Schema").String;
|
|
1807
|
+
}>;
|
|
1808
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof import("./effect").ServiceInternalError, import("effect/Schema").Struct<{
|
|
1809
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1810
|
+
readonly code: import("effect/Schema").Literal<"service.internal_error">;
|
|
1811
|
+
readonly message: import("effect/Schema").String;
|
|
1812
|
+
}>;
|
|
1813
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof import("./effect").ServiceUnavailableError, import("effect/Schema").Struct<{
|
|
1814
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1815
|
+
readonly code: import("effect/Schema").Literal<"service.unavailable">;
|
|
1816
|
+
readonly message: import("effect/Schema").String;
|
|
1817
|
+
}>;
|
|
1818
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof import("./effect").TenantInvalidIdError, import("effect/Schema").Struct<{
|
|
1819
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1820
|
+
readonly code: import("effect/Schema").Literal<"tenant.invalid_id">;
|
|
1821
|
+
readonly message: import("effect/Schema").String;
|
|
1822
|
+
}>;
|
|
1823
|
+
}>, never, never>>, never, never>, never, never>;
|
|
1764
1824
|
readonly revokeAPIKey: HttpApiClient.Client.Method<import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"revokeAPIKey", "DELETE", "/v1/tenants/:tenant_id/api-keys/:api_key_id", import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
1765
1825
|
readonly api_key_id: import("effect/Schema").brand<import("effect/Schema").String, "UUID">;
|
|
1766
1826
|
readonly tenant_id: import("effect/Schema").brand<import("effect/Schema").String, "TenantId">;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConnectionReporter, ConnectionReportTransportEvent } from "./report";
|
|
1
2
|
import type { ClientMediaPlane, CloudflareSFUBootstrap, ConnectionMediaSnapshot } from "../media";
|
|
2
3
|
import type { ChalkChatFileTransport } from "../chat-files";
|
|
3
4
|
import type { V1AdmissionTarget, V1AssignableRole, V1CommandResult, V1EpisodeSnapshot, V1SelfMediaTargetResult } from "../sync";
|
|
@@ -79,6 +80,8 @@ export type ConnectionMediaFactoryInput = {
|
|
|
79
80
|
readonly access: ParsedAccessGrant;
|
|
80
81
|
readonly credential: () => Promise<string>;
|
|
81
82
|
readonly replaceMediaConnection?: () => Promise<ParticipantMediaAccess>;
|
|
83
|
+
readonly traceparent?: string;
|
|
84
|
+
readonly onTransportDiagnostic?: (event: ConnectionReportTransportEvent) => void;
|
|
82
85
|
readonly onFailure: (error: unknown) => void;
|
|
83
86
|
readonly onScreenEnded: () => void;
|
|
84
87
|
};
|
|
@@ -86,6 +89,8 @@ export type ConnectionSyncFactoryInput = {
|
|
|
86
89
|
readonly access: ParsedAccessGrant;
|
|
87
90
|
readonly token: () => Promise<string>;
|
|
88
91
|
readonly media: ConnectionMediaClient;
|
|
92
|
+
readonly traceparent?: string;
|
|
93
|
+
readonly onTransportDiagnostic?: (event: ConnectionReportTransportEvent) => void;
|
|
89
94
|
};
|
|
90
95
|
export type ConnectionWhiteboardFactoryInput = {
|
|
91
96
|
readonly onWatchdogReplacement?: ChalkWhiteboardV1ClientOptions["onWatchdogReplacement"];
|
|
@@ -97,6 +102,7 @@ export type ConnectionChatFileFactoryInput = {
|
|
|
97
102
|
};
|
|
98
103
|
export type ConnectionDependencies = {
|
|
99
104
|
readonly clock: ConnectionClock;
|
|
105
|
+
readonly createConnectionReporter?: (access: () => ParsedAccessGrant | null) => ConnectionReporter | undefined;
|
|
100
106
|
readonly cancelPendingAdmission?: (access: ParsedAccessGrant, signal: AbortSignal) => Promise<boolean>;
|
|
101
107
|
readonly mediaDevices: ConnectionMediaDevices;
|
|
102
108
|
readonly createMediaClient: (input: ConnectionMediaFactoryInput) => ConnectionMediaClient;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ParsedAccessGrant } from "../access/grant";
|
|
2
|
+
import type { ConnectionDiagnostic } from "./diagnostics";
|
|
3
|
+
import type { ConnectionReport, ConnectionReportTransportEvent } from "../transport-diagnostics";
|
|
4
|
+
export type { ConnectionReport, ConnectionReportTransportEvent } from "../transport-diagnostics";
|
|
5
|
+
type ReporterOptions = {
|
|
6
|
+
readonly traceparent: string;
|
|
7
|
+
readonly now: () => number;
|
|
8
|
+
readonly access: () => ParsedAccessGrant | null;
|
|
9
|
+
readonly send: (access: ParsedAccessGrant, report: ConnectionReport, signal: AbortSignal, traceparent: string) => Promise<void>;
|
|
10
|
+
readonly online: () => boolean;
|
|
11
|
+
readonly subscribeOnline: (listener: () => void) => () => void;
|
|
12
|
+
};
|
|
13
|
+
/** Only SDK-owned enums and numeric facts enter this buffer. No generic logging. */
|
|
14
|
+
export declare class ConnectionReporter {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(options: ReporterOptions);
|
|
17
|
+
recordDiagnostic(diagnostic: ConnectionDiagnostic): void;
|
|
18
|
+
recordTransport(event: ConnectionReportTransportEvent): void;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
get traceparent(): string;
|
|
21
|
+
}
|
package/dist/effect.js
CHANGED
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
makeSpaceClientCoreLayer,
|
|
18
18
|
makeSpaceClientCoreLayerFromServices,
|
|
19
19
|
makeSpaceStoreLayer
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-LDPC2JZB.js";
|
|
21
|
+
import "./chunk-3ASKGMD3.js";
|
|
22
22
|
import {
|
|
23
23
|
APIKeyListSchema,
|
|
24
24
|
APIKeyWithSecretSchema,
|
|
@@ -139,6 +139,7 @@ import {
|
|
|
139
139
|
CloudflareSFURenegotiateResponseSchema,
|
|
140
140
|
CloudflareSFUTracksAPIResponseSchema,
|
|
141
141
|
CloudflareSFUTracksRequestSchema,
|
|
142
|
+
ConnectionReportRequestSchema,
|
|
142
143
|
CreateAPIKey429ResponseHeadersSchema,
|
|
143
144
|
CreateAPIKeyErrorSchema,
|
|
144
145
|
CreateAPIKeyPathParamsSchema,
|
|
@@ -548,6 +549,10 @@ import {
|
|
|
548
549
|
RenegotiateCloudflareSFUPathParamsSchema,
|
|
549
550
|
RenegotiateCloudflareSFURequestBodySchema,
|
|
550
551
|
RenegotiateCloudflareSFUResponseSchema,
|
|
552
|
+
ReportConnectionFailure429ResponseHeadersSchema,
|
|
553
|
+
ReportConnectionFailureErrorSchema,
|
|
554
|
+
ReportConnectionFailurePathParamsSchema,
|
|
555
|
+
ReportConnectionFailureRequestBodySchema,
|
|
551
556
|
ReportsDeliveryFailedError,
|
|
552
557
|
ReportsDeliveryFailedErrorSchema,
|
|
553
558
|
ReportsDeliveryFailedErrorWireSchema,
|
|
@@ -828,8 +833,8 @@ import {
|
|
|
828
833
|
WhiteboardStorageUnavailableErrorSchema,
|
|
829
834
|
WhiteboardStorageUnavailableErrorWireSchema,
|
|
830
835
|
createChalkEffectClient
|
|
831
|
-
} from "./chunk-
|
|
832
|
-
import "./chunk-
|
|
836
|
+
} from "./chunk-AZONR3Q3.js";
|
|
837
|
+
import "./chunk-5YPQNMJE.js";
|
|
833
838
|
|
|
834
839
|
// src/space-client/effect.ts
|
|
835
840
|
import { Context, Effect, Exit, Layer, Scope } from "effect";
|
|
@@ -990,6 +995,7 @@ export {
|
|
|
990
995
|
ConnectionAccessService,
|
|
991
996
|
ConnectionLifecycleService,
|
|
992
997
|
ConnectionPlatformService,
|
|
998
|
+
ConnectionReportRequestSchema,
|
|
993
999
|
CreateAPIKey429ResponseHeadersSchema,
|
|
994
1000
|
CreateAPIKeyErrorSchema,
|
|
995
1001
|
CreateAPIKeyPathParamsSchema,
|
|
@@ -1399,6 +1405,10 @@ export {
|
|
|
1399
1405
|
RenegotiateCloudflareSFUPathParamsSchema,
|
|
1400
1406
|
RenegotiateCloudflareSFURequestBodySchema,
|
|
1401
1407
|
RenegotiateCloudflareSFUResponseSchema,
|
|
1408
|
+
ReportConnectionFailure429ResponseHeadersSchema,
|
|
1409
|
+
ReportConnectionFailureErrorSchema,
|
|
1410
|
+
ReportConnectionFailurePathParamsSchema,
|
|
1411
|
+
ReportConnectionFailureRequestBodySchema,
|
|
1402
1412
|
ReportsDeliveryFailedError,
|
|
1403
1413
|
ReportsDeliveryFailedErrorSchema,
|
|
1404
1414
|
ReportsDeliveryFailedErrorWireSchema,
|
|
@@ -1726,6 +1726,65 @@ declare const ChalkApi_base: HttpApi.HttpApi<"ChalkApi", HttpApiGroup.HttpApiGro
|
|
|
1726
1726
|
readonly code: import("effect/Schema").Literal<"tenant.invalid_id">;
|
|
1727
1727
|
readonly message: import("effect/Schema").String;
|
|
1728
1728
|
}>;
|
|
1729
|
+
}>, never, never>>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"reportConnectionFailure", "POST", "/v1/tenants/:tenant_id/connection-reports", import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
1730
|
+
readonly tenant_id: import("effect/Schema").brand<import("effect/Schema").String, "TenantId">;
|
|
1731
|
+
}>>, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
1732
|
+
readonly events: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
1733
|
+
readonly attempt: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1734
|
+
readonly close_code: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1735
|
+
readonly code: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["invalid_state", "invalid_access", "access_unavailable", "permission_denied", "device_not_found", "device_busy", "device_constraint_invalid", "media_capture_failed", "sync_start_failed", "state_digest_recovery_failed", "sync_hello_timeout", "media_start_failed", "join_cleanup_unconfirmed", "sync_recovery_exhausted", "media_recovery_exhausted", "command_rejected", "leave_unconfirmed", "episode_ended", "unsupported_environment", "internal_error", "collaboration_unavailable", "chat_cursor_reset_required", "rate_limited", "invalid_payload"]>>;
|
|
1736
|
+
readonly duration_ms: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1737
|
+
readonly elapsed_ms: import("effect/Schema").Number;
|
|
1738
|
+
readonly event: import("effect/Schema").Literals<readonly ["space_join_span", "state_changed", "connection_failed", "sync_connect_failed", "sync_dial_timeout", "access_refreshed", "access_refresh_failed", "recovery_attempt", "recovery_succeeded", "recovery_exhausted", "cleanup_completed", "cleanup_unconfirmed", "sync_socket_open", "sync_socket_close", "sync_heartbeat_gap", "media_state", "media_http_error"]>;
|
|
1739
|
+
readonly heartbeat_gap_ms: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1740
|
+
readonly http_status: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Number>>;
|
|
1741
|
+
readonly outcome: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["started", "succeeded", "failed", "cancelled"]>>;
|
|
1742
|
+
readonly phase: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["idle", "connecting", "live", "recovering", "failed", "stopped", "new", "connected", "disconnected", "closed"]>>;
|
|
1743
|
+
readonly state: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["idle", "joining", "live", "reconnecting", "leaving", "left", "failed"]>>;
|
|
1744
|
+
readonly step: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["join", "acquire_initial_media", "access_initialize", "create_media_client", "create_sync_client", "start_media", "start_sync", "wait_for_sync_live", "confirm_entered"]>>;
|
|
1745
|
+
}>>;
|
|
1746
|
+
readonly trigger: import("effect/Schema").Literals<readonly ["join_failed", "connection_lost", "recovery_exhausted"]>;
|
|
1747
|
+
readonly version: import("effect/Schema").Literal<1>;
|
|
1748
|
+
}>>, never, import("effect/Schema").toCodecJson<import("effect/Schema").Void>, import("effect/Schema").toCodecJson<import("effect/Schema").decodeTo<typeof S.AccessForbiddenError, import("effect/Schema").Struct<{
|
|
1749
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1750
|
+
readonly code: import("effect/Schema").Literal<"access.forbidden">;
|
|
1751
|
+
readonly message: import("effect/Schema").String;
|
|
1752
|
+
}>;
|
|
1753
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof S.AccessUnauthenticatedError, import("effect/Schema").Struct<{
|
|
1754
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1755
|
+
readonly code: import("effect/Schema").Literal<"access.unauthenticated">;
|
|
1756
|
+
readonly message: import("effect/Schema").String;
|
|
1757
|
+
}>;
|
|
1758
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof S.RequestInvalidError, import("effect/Schema").Struct<{
|
|
1759
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1760
|
+
readonly code: import("effect/Schema").Literal<"request.invalid">;
|
|
1761
|
+
readonly message: import("effect/Schema").String;
|
|
1762
|
+
}>;
|
|
1763
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof S.RequestPayloadTooLargeError, import("effect/Schema").Struct<{
|
|
1764
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1765
|
+
readonly code: import("effect/Schema").Literal<"request.payload_too_large">;
|
|
1766
|
+
readonly message: import("effect/Schema").String;
|
|
1767
|
+
}>;
|
|
1768
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof S.RequestRateLimitedError, import("effect/Schema").Struct<{
|
|
1769
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1770
|
+
readonly code: import("effect/Schema").Literal<"request.rate_limited">;
|
|
1771
|
+
readonly message: import("effect/Schema").String;
|
|
1772
|
+
}>;
|
|
1773
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof S.ServiceInternalError, import("effect/Schema").Struct<{
|
|
1774
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1775
|
+
readonly code: import("effect/Schema").Literal<"service.internal_error">;
|
|
1776
|
+
readonly message: import("effect/Schema").String;
|
|
1777
|
+
}>;
|
|
1778
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof S.ServiceUnavailableError, import("effect/Schema").Struct<{
|
|
1779
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1780
|
+
readonly code: import("effect/Schema").Literal<"service.unavailable">;
|
|
1781
|
+
readonly message: import("effect/Schema").String;
|
|
1782
|
+
}>;
|
|
1783
|
+
}>, never, never> | import("effect/Schema").decodeTo<typeof S.TenantInvalidIdError, import("effect/Schema").Struct<{
|
|
1784
|
+
readonly error: import("effect/Schema").Struct<{
|
|
1785
|
+
readonly code: import("effect/Schema").Literal<"tenant.invalid_id">;
|
|
1786
|
+
readonly message: import("effect/Schema").String;
|
|
1787
|
+
}>;
|
|
1729
1788
|
}>, never, never>>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"revokeAPIKey", "DELETE", "/v1/tenants/:tenant_id/api-keys/:api_key_id", import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
1730
1789
|
readonly api_key_id: import("effect/Schema").brand<import("effect/Schema").String, "UUID">;
|
|
1731
1790
|
readonly tenant_id: import("effect/Schema").brand<import("effect/Schema").String, "TenantId">;
|
|
@@ -219,6 +219,25 @@ export declare const CloudflareSFUTracksRequestSchema: Schema.Struct<{
|
|
|
219
219
|
}>>;
|
|
220
220
|
}>;
|
|
221
221
|
export type CloudflareSFUTracksRequest = typeof CloudflareSFUTracksRequestSchema.Type;
|
|
222
|
+
export declare const ConnectionReportRequestSchema: Schema.Struct<{
|
|
223
|
+
readonly events: Schema.$Array<Schema.Struct<{
|
|
224
|
+
readonly attempt: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
225
|
+
readonly close_code: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
226
|
+
readonly code: Schema.optional<Schema.Literals<readonly ["invalid_state", "invalid_access", "access_unavailable", "permission_denied", "device_not_found", "device_busy", "device_constraint_invalid", "media_capture_failed", "sync_start_failed", "state_digest_recovery_failed", "sync_hello_timeout", "media_start_failed", "join_cleanup_unconfirmed", "sync_recovery_exhausted", "media_recovery_exhausted", "command_rejected", "leave_unconfirmed", "episode_ended", "unsupported_environment", "internal_error", "collaboration_unavailable", "chat_cursor_reset_required", "rate_limited", "invalid_payload"]>>;
|
|
227
|
+
readonly duration_ms: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
228
|
+
readonly elapsed_ms: Schema.Number;
|
|
229
|
+
readonly event: Schema.Literals<readonly ["space_join_span", "state_changed", "connection_failed", "sync_connect_failed", "sync_dial_timeout", "access_refreshed", "access_refresh_failed", "recovery_attempt", "recovery_succeeded", "recovery_exhausted", "cleanup_completed", "cleanup_unconfirmed", "sync_socket_open", "sync_socket_close", "sync_heartbeat_gap", "media_state", "media_http_error"]>;
|
|
230
|
+
readonly heartbeat_gap_ms: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
231
|
+
readonly http_status: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
232
|
+
readonly outcome: Schema.optional<Schema.Literals<readonly ["started", "succeeded", "failed", "cancelled"]>>;
|
|
233
|
+
readonly phase: Schema.optional<Schema.Literals<readonly ["idle", "connecting", "live", "recovering", "failed", "stopped", "new", "connected", "disconnected", "closed"]>>;
|
|
234
|
+
readonly state: Schema.optional<Schema.Literals<readonly ["idle", "joining", "live", "reconnecting", "leaving", "left", "failed"]>>;
|
|
235
|
+
readonly step: Schema.optional<Schema.Literals<readonly ["join", "acquire_initial_media", "access_initialize", "create_media_client", "create_sync_client", "start_media", "start_sync", "wait_for_sync_live", "confirm_entered"]>>;
|
|
236
|
+
}>>;
|
|
237
|
+
readonly trigger: Schema.Literals<readonly ["join_failed", "connection_lost", "recovery_exhausted"]>;
|
|
238
|
+
readonly version: Schema.Literal<1>;
|
|
239
|
+
}>;
|
|
240
|
+
export type ConnectionReportRequest = typeof ConnectionReportRequestSchema.Type;
|
|
222
241
|
export declare const CreateAPIKeyRequestSchema: Schema.Struct<{
|
|
223
242
|
readonly expires_at: Schema.brand<Schema.String, "DateTimeString">;
|
|
224
243
|
readonly name: Schema.String;
|
|
@@ -3284,6 +3303,35 @@ export declare const RenegotiateCloudflareSFU429ResponseHeadersSchema: Schema.St
|
|
|
3284
3303
|
readonly "X-RateLimit-Remaining": Schema.NumberFromString;
|
|
3285
3304
|
}>;
|
|
3286
3305
|
export type RenegotiateCloudflareSFU429ResponseHeaders = typeof RenegotiateCloudflareSFU429ResponseHeadersSchema.Type;
|
|
3306
|
+
export declare const ReportConnectionFailurePathParamsSchema: Schema.Struct<{
|
|
3307
|
+
readonly tenant_id: Schema.brand<Schema.String, "TenantId">;
|
|
3308
|
+
}>;
|
|
3309
|
+
export type ReportConnectionFailurePathParams = typeof ReportConnectionFailurePathParamsSchema.Type;
|
|
3310
|
+
export declare const ReportConnectionFailureRequestBodySchema: Schema.Struct<{
|
|
3311
|
+
readonly events: Schema.$Array<Schema.Struct<{
|
|
3312
|
+
readonly attempt: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
3313
|
+
readonly close_code: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
3314
|
+
readonly code: Schema.optional<Schema.Literals<readonly ["invalid_state", "invalid_access", "access_unavailable", "permission_denied", "device_not_found", "device_busy", "device_constraint_invalid", "media_capture_failed", "sync_start_failed", "state_digest_recovery_failed", "sync_hello_timeout", "media_start_failed", "join_cleanup_unconfirmed", "sync_recovery_exhausted", "media_recovery_exhausted", "command_rejected", "leave_unconfirmed", "episode_ended", "unsupported_environment", "internal_error", "collaboration_unavailable", "chat_cursor_reset_required", "rate_limited", "invalid_payload"]>>;
|
|
3315
|
+
readonly duration_ms: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
3316
|
+
readonly elapsed_ms: Schema.Number;
|
|
3317
|
+
readonly event: Schema.Literals<readonly ["space_join_span", "state_changed", "connection_failed", "sync_connect_failed", "sync_dial_timeout", "access_refreshed", "access_refresh_failed", "recovery_attempt", "recovery_succeeded", "recovery_exhausted", "cleanup_completed", "cleanup_unconfirmed", "sync_socket_open", "sync_socket_close", "sync_heartbeat_gap", "media_state", "media_http_error"]>;
|
|
3318
|
+
readonly heartbeat_gap_ms: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
3319
|
+
readonly http_status: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
3320
|
+
readonly outcome: Schema.optional<Schema.Literals<readonly ["started", "succeeded", "failed", "cancelled"]>>;
|
|
3321
|
+
readonly phase: Schema.optional<Schema.Literals<readonly ["idle", "connecting", "live", "recovering", "failed", "stopped", "new", "connected", "disconnected", "closed"]>>;
|
|
3322
|
+
readonly state: Schema.optional<Schema.Literals<readonly ["idle", "joining", "live", "reconnecting", "leaving", "left", "failed"]>>;
|
|
3323
|
+
readonly step: Schema.optional<Schema.Literals<readonly ["join", "acquire_initial_media", "access_initialize", "create_media_client", "create_sync_client", "start_media", "start_sync", "wait_for_sync_live", "confirm_entered"]>>;
|
|
3324
|
+
}>>;
|
|
3325
|
+
readonly trigger: Schema.Literals<readonly ["join_failed", "connection_lost", "recovery_exhausted"]>;
|
|
3326
|
+
readonly version: Schema.Literal<1>;
|
|
3327
|
+
}>;
|
|
3328
|
+
export type ReportConnectionFailureRequestBody = typeof ReportConnectionFailureRequestBodySchema.Type;
|
|
3329
|
+
export declare const ReportConnectionFailure429ResponseHeadersSchema: Schema.Struct<{
|
|
3330
|
+
readonly "Retry-After": Schema.NumberFromString;
|
|
3331
|
+
readonly "X-RateLimit-Limit": Schema.NumberFromString;
|
|
3332
|
+
readonly "X-RateLimit-Remaining": Schema.NumberFromString;
|
|
3333
|
+
}>;
|
|
3334
|
+
export type ReportConnectionFailure429ResponseHeaders = typeof ReportConnectionFailure429ResponseHeadersSchema.Type;
|
|
3287
3335
|
export declare const RequestRecordingExportPathParamsSchema: Schema.Struct<{
|
|
3288
3336
|
readonly recording_id: Schema.brand<Schema.String, "RecordingId">;
|
|
3289
3337
|
readonly tenant_id: Schema.brand<Schema.String, "TenantId">;
|
|
@@ -9843,6 +9891,48 @@ export declare const RenegotiateCloudflareSFUErrorSchema: Schema.Union<readonly
|
|
|
9843
9891
|
}>;
|
|
9844
9892
|
}>, never, never>]>;
|
|
9845
9893
|
export type RenegotiateCloudflareSFUError = typeof RenegotiateCloudflareSFUErrorSchema.Type;
|
|
9894
|
+
export declare const ReportConnectionFailureErrorSchema: Schema.Union<readonly [Schema.decodeTo<typeof AccessForbiddenError, Schema.Struct<{
|
|
9895
|
+
readonly error: Schema.Struct<{
|
|
9896
|
+
readonly code: Schema.Literal<"access.forbidden">;
|
|
9897
|
+
readonly message: Schema.String;
|
|
9898
|
+
}>;
|
|
9899
|
+
}>, never, never>, Schema.decodeTo<typeof AccessUnauthenticatedError, Schema.Struct<{
|
|
9900
|
+
readonly error: Schema.Struct<{
|
|
9901
|
+
readonly code: Schema.Literal<"access.unauthenticated">;
|
|
9902
|
+
readonly message: Schema.String;
|
|
9903
|
+
}>;
|
|
9904
|
+
}>, never, never>, Schema.decodeTo<typeof RequestInvalidError, Schema.Struct<{
|
|
9905
|
+
readonly error: Schema.Struct<{
|
|
9906
|
+
readonly code: Schema.Literal<"request.invalid">;
|
|
9907
|
+
readonly message: Schema.String;
|
|
9908
|
+
}>;
|
|
9909
|
+
}>, never, never>, Schema.decodeTo<typeof RequestPayloadTooLargeError, Schema.Struct<{
|
|
9910
|
+
readonly error: Schema.Struct<{
|
|
9911
|
+
readonly code: Schema.Literal<"request.payload_too_large">;
|
|
9912
|
+
readonly message: Schema.String;
|
|
9913
|
+
}>;
|
|
9914
|
+
}>, never, never>, Schema.decodeTo<typeof RequestRateLimitedError, Schema.Struct<{
|
|
9915
|
+
readonly error: Schema.Struct<{
|
|
9916
|
+
readonly code: Schema.Literal<"request.rate_limited">;
|
|
9917
|
+
readonly message: Schema.String;
|
|
9918
|
+
}>;
|
|
9919
|
+
}>, never, never>, Schema.decodeTo<typeof ServiceInternalError, Schema.Struct<{
|
|
9920
|
+
readonly error: Schema.Struct<{
|
|
9921
|
+
readonly code: Schema.Literal<"service.internal_error">;
|
|
9922
|
+
readonly message: Schema.String;
|
|
9923
|
+
}>;
|
|
9924
|
+
}>, never, never>, Schema.decodeTo<typeof ServiceUnavailableError, Schema.Struct<{
|
|
9925
|
+
readonly error: Schema.Struct<{
|
|
9926
|
+
readonly code: Schema.Literal<"service.unavailable">;
|
|
9927
|
+
readonly message: Schema.String;
|
|
9928
|
+
}>;
|
|
9929
|
+
}>, never, never>, Schema.decodeTo<typeof TenantInvalidIdError, Schema.Struct<{
|
|
9930
|
+
readonly error: Schema.Struct<{
|
|
9931
|
+
readonly code: Schema.Literal<"tenant.invalid_id">;
|
|
9932
|
+
readonly message: Schema.String;
|
|
9933
|
+
}>;
|
|
9934
|
+
}>, never, never>]>;
|
|
9935
|
+
export type ReportConnectionFailureError = typeof ReportConnectionFailureErrorSchema.Type;
|
|
9846
9936
|
export declare const RequestRecordingExportErrorSchema: Schema.Union<readonly [Schema.decodeTo<typeof AccessForbiddenError, Schema.Struct<{
|
|
9847
9937
|
readonly error: Schema.Struct<{
|
|
9848
9938
|
readonly code: Schema.Literal<"access.forbidden">;
|
|
@@ -11346,6 +11436,14 @@ export declare const ChalkOperationPolicies: {
|
|
|
11346
11436
|
readonly windowSeconds: 60;
|
|
11347
11437
|
};
|
|
11348
11438
|
};
|
|
11439
|
+
readonly reportConnectionFailure: {
|
|
11440
|
+
readonly maxBodyBytes: 32768;
|
|
11441
|
+
readonly rateLimit: {
|
|
11442
|
+
readonly limit: 6;
|
|
11443
|
+
readonly policy: "connection_reports.participant";
|
|
11444
|
+
readonly windowSeconds: 600;
|
|
11445
|
+
};
|
|
11446
|
+
};
|
|
11349
11447
|
readonly requestRecordingExport: {
|
|
11350
11448
|
readonly maxBodyBytes: 1048576;
|
|
11351
11449
|
readonly rateLimit: {
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
createSpaceClient,
|
|
16
16
|
decodeWhiteboardV1ServerFrame,
|
|
17
17
|
encodeWhiteboardV1ClientFrame
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-LDPC2JZB.js";
|
|
19
19
|
import {
|
|
20
20
|
CloudflareSFUClient,
|
|
21
21
|
CloudflareSFUError,
|
|
@@ -28,19 +28,19 @@ import {
|
|
|
28
28
|
requireSFUDescription,
|
|
29
29
|
validatePublicationSnapshot,
|
|
30
30
|
waitFor
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-3ASKGMD3.js";
|
|
32
32
|
import {
|
|
33
33
|
createChalkPublicClient,
|
|
34
34
|
createChalkPublicInviteClient,
|
|
35
35
|
parseInviteCode
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-SYUGRHZ5.js";
|
|
37
37
|
import {
|
|
38
38
|
ArrivalInvalidHandleError,
|
|
39
39
|
ArrivalUnavailableError,
|
|
40
40
|
SpacePublicInviteUnavailableError,
|
|
41
41
|
parseParsedAccessGrant,
|
|
42
42
|
requireAccessGrant
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-AZONR3Q3.js";
|
|
44
44
|
import {
|
|
45
45
|
AsyncStorageV1PendingTargetStore,
|
|
46
46
|
InMemoryV1PendingTargetStore,
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
optimisticV1Control,
|
|
70
70
|
reactionFromFrame,
|
|
71
71
|
restoreV1Snapshot
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-5YPQNMJE.js";
|
|
73
73
|
|
|
74
74
|
// src/space-client/reload-rejoin.ts
|
|
75
75
|
import { Schema } from "effect";
|
package/dist/media/index.js
CHANGED
package/dist/media/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConnectionReportTransportEvent } from "../transport-diagnostics";
|
|
1
2
|
import type { MediaPublication, MediaPublicationSource } from "./plane";
|
|
2
3
|
export type CloudflareSFUBootstrap = {
|
|
3
4
|
readonly connectionId: string;
|
|
@@ -108,6 +109,8 @@ export type CloudflareSFURestartOptions = {
|
|
|
108
109
|
};
|
|
109
110
|
export type CloudflareSFUCredentialProvider = (signal?: AbortSignal) => string | Promise<string>;
|
|
110
111
|
export type CloudflareSFUHTTPTransportOptions = {
|
|
112
|
+
readonly traceparent?: string;
|
|
113
|
+
readonly onDiagnostic?: (event: ConnectionReportTransportEvent) => void;
|
|
111
114
|
readonly apiBaseURL: string;
|
|
112
115
|
readonly credential?: CloudflareSFUCredentialProvider;
|
|
113
116
|
/** @deprecated Use credential so refreshed media access is read before every request. */
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
createChalkPublicClient,
|
|
3
3
|
createChalkPublicInviteClient,
|
|
4
4
|
parseInviteCode
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-SYUGRHZ5.js";
|
|
6
6
|
import {
|
|
7
7
|
ArrivalInvalidHandleError,
|
|
8
8
|
ArrivalUnavailableError,
|
|
9
9
|
SpacePublicInviteUnavailableError
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-AZONR3Q3.js";
|
|
11
11
|
export {
|
|
12
12
|
ArrivalInvalidHandleError,
|
|
13
13
|
ArrivalUnavailableError,
|
package/dist/sync/index.js
CHANGED
package/dist/sync/v1-types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConnectionReportTransportEvent } from "../transport-diagnostics";
|
|
1
2
|
import type { SetAdmissionPolicyCommandFrame, SyncV1ServerFrame } from "../generated/sync";
|
|
2
3
|
import type { ClientMediaPlane, MediaPlaneOutcome, MediaPlaneResult, MediaPlaneTarget, MediaPublication, MediaSource } from "../media/plane";
|
|
3
4
|
import type { ChalkChatReactionInput, ChalkChatReactions, ChalkChatMessage, ChalkChatPageResult, ChalkChatReadReceipt, ChalkReaction, ChalkReactionEvent, ChalkSendChatMessageInput, ChalkSyncV1CollaborationCapability } from "../collaboration/types";
|
|
@@ -189,6 +190,8 @@ export type V1CollaborationClient = {
|
|
|
189
190
|
}) => Promise<ChalkChatPageResult>;
|
|
190
191
|
};
|
|
191
192
|
export type V1SyncClientOptions = {
|
|
193
|
+
readonly traceparent?: string;
|
|
194
|
+
readonly onDiagnostic?: (event: ConnectionReportTransportEvent) => void;
|
|
192
195
|
readonly url: string;
|
|
193
196
|
readonly token: () => Promise<string>;
|
|
194
197
|
readonly webSocket: SyncWebSocketFactory;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { components } from "./generated/openapi-types";
|
|
2
|
+
export type ConnectionReport = components["schemas"]["ConnectionReportRequest"];
|
|
3
|
+
export type ConnectionReportTransportEvent = Pick<ConnectionReport["events"][number], "event" | "phase" | "attempt" | "duration_ms" | "heartbeat_gap_ms" | "close_code" | "http_status">;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@q9labsai/chalk-client",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
"dev": "tsup ./src/index.ts ./src/effect.ts ./src/media/index.ts ./src/sync/index.ts --format esm --out-dir ./dist --platform browser --external effect --watch",
|
|
219
219
|
"generate:webhooks": "node ./scripts/generate-webhook-contract.mjs",
|
|
220
220
|
"check:webhooks-generated": "node ./scripts/generate-webhook-contract.mjs --check",
|
|
221
|
-
"test": "pnpm exec vitest run",
|
|
221
|
+
"test": "node ../../../scripts/test/run.mjs pnpm exec vitest run",
|
|
222
222
|
"test:webhooks-package": "pnpm run build && node ./scripts/check-webhook-package.mjs",
|
|
223
223
|
"test:server-package": "pnpm run build && node ./scripts/check-server-package.mjs",
|
|
224
224
|
"test:sync-browser": "pnpm run build && node ./scripts/real-browser-sync.mjs",
|