@q9labsai/chalk-client 5.3.0 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ export type VideoRenderSize = {
2
+ readonly width: number;
3
+ readonly height: number;
4
+ };
5
+ /** Tells the media client how large the UI draws a remote video, in device pixels. Pass null when the track leaves the screen. */
6
+ export declare function setVideoRenderSize(track: MediaStreamTrack, size: VideoRenderSize | null): void;
7
+ export declare function videoRenderSize(track: MediaStreamTrack): VideoRenderSize | undefined;
@@ -0,0 +1,48 @@
1
+ export type ViewerLayer = "h" | "l";
2
+ export type ViewerLayerSample = {
3
+ /** Fraction of packets lost since the last sample; undefined when no packet arrived or was lost. */
4
+ readonly lossFraction: number | undefined;
5
+ readonly framesPerSecond: number | undefined;
6
+ readonly newFreezes: number;
7
+ readonly freezeSeconds: number;
8
+ /** Rendered tile height in device pixels, when the UI reports it. */
9
+ readonly tileHeight: number | undefined;
10
+ };
11
+ export declare class ViewerLayerPolicy {
12
+ #private;
13
+ get layer(): ViewerLayer;
14
+ /** Drops fast, rises slowly: a failed attempt at h doubles the wait before the next one. */
15
+ sample(sample: ViewerLayerSample): ViewerLayer;
16
+ }
17
+ type ViewerStatsRow = {
18
+ readonly type?: string;
19
+ readonly kind?: string;
20
+ readonly trackIdentifier?: string;
21
+ readonly timestamp?: number;
22
+ readonly packetsReceived?: number;
23
+ readonly packetsLost?: number;
24
+ readonly framesDecoded?: number;
25
+ readonly freezeCount?: number;
26
+ readonly totalFreezesDuration?: number;
27
+ };
28
+ /** Turns cumulative inbound-rtp counters into per-interval rates for one video track. */
29
+ export declare class ViewerStatsReader {
30
+ #private;
31
+ read(stats: Iterable<ViewerStatsRow>, trackId: string): Pick<ViewerLayerSample, "lossFraction" | "framesPerSecond" | "newFreezes" | "freezeSeconds"> | undefined;
32
+ forget(trackId: string): void;
33
+ }
34
+ export type ViewerLayerCamera = {
35
+ readonly publicationId: string;
36
+ readonly track: MediaStreamTrack;
37
+ };
38
+ export type ViewerLayerChange = ViewerLayerCamera & {
39
+ readonly layer: ViewerLayer;
40
+ };
41
+ /** One per peer connection: owns a policy for each remote camera and reports the switches still to apply. */
42
+ export declare class ViewerLayerSelector {
43
+ #private;
44
+ sample(now: number, report: RTCStatsReport, cameras: readonly ViewerLayerCamera[]): readonly ViewerLayerChange[];
45
+ applied({ publicationId, track, layer }: ViewerLayerChange): void;
46
+ failed({ publicationId, track }: ViewerLayerChange, now: number): void;
47
+ }
48
+ export {};
@@ -2,12 +2,13 @@ import {
2
2
  createChalkPublicClient,
3
3
  createChalkPublicInviteClient,
4
4
  parseInviteCode
5
- } from "../chunk-O26BHHMI.js";
5
+ } from "../chunk-VAJ6XNAA.js";
6
6
  import {
7
7
  ArrivalInvalidHandleError,
8
8
  ArrivalUnavailableError,
9
9
  SpacePublicInviteUnavailableError
10
- } from "../chunk-GOSWSXTH.js";
10
+ } from "../chunk-MHJ4UPM3.js";
11
+ import "../chunk-N4IPKRS6.js";
11
12
  export {
12
13
  ArrivalInvalidHandleError,
13
14
  ArrivalUnavailableError,
@@ -115,17 +115,31 @@ function parseMediaAccess(value) {
115
115
  provider: value.provider,
116
116
  clientPayload: {
117
117
  connectionId: requireNonEmptyString(payload.connectionId),
118
- stunServer: requireNonEmptyString(payload.stunServer)
118
+ stunServer: requireNonEmptyString(payload.stunServer),
119
+ ...payload.iceServers === void 0 || payload.iceServers === null ? {} : { iceServers: parseIceServers(payload.iceServers) }
119
120
  }
120
121
  };
121
122
  default:
122
123
  throw new AccessGrantError();
123
124
  }
124
125
  }
126
+ function parseIceServers(value) {
127
+ if (!Array.isArray(value)) throw new AccessGrantError();
128
+ return value.map((entry) => {
129
+ if (!isRecord(entry) || !Array.isArray(entry.urls) || entry.urls.length === 0) throw new AccessGrantError();
130
+ const urls = entry.urls.map(requireNonEmptyString);
131
+ if (entry.username === void 0 && entry.credential === void 0) return { urls };
132
+ return { urls, username: requireNonEmptyString(entry.username), credential: requireNonEmptyString(entry.credential) };
133
+ });
134
+ }
125
135
  function mediaClientPayload(value) {
126
136
  switch (value.provider) {
127
137
  case "cloudflare_sfu":
128
- return { connectionId: value.clientPayload.connectionId, stunServer: value.clientPayload.stunServer };
138
+ return {
139
+ connectionId: value.clientPayload.connectionId,
140
+ stunServer: value.clientPayload.stunServer,
141
+ ...value.clientPayload.iceServers ? { iceServers: value.clientPayload.iceServers } : {}
142
+ };
129
143
  }
130
144
  }
131
145
  function requireCredential(value, audience) {
@@ -700,9 +714,9 @@ function accessGrant(value) {
700
714
  function mediaClientPayload2(media) {
701
715
  switch (media.provider) {
702
716
  case "cloudflare_sfu": {
703
- const { connectionId, stunServer } = media.client_payload;
717
+ const { connectionId, stunServer, iceServers } = media.client_payload;
704
718
  if (typeof connectionId !== "string" || typeof stunServer !== "string") throw invalidResponse(201);
705
- return { connectionId, stunServer };
719
+ return { connectionId, stunServer, ...iceServers === void 0 ? {} : { iceServers } };
706
720
  }
707
721
  default:
708
722
  throw invalidResponse(201);
@@ -93,17 +93,31 @@ function parseMediaAccess(value) {
93
93
  provider: value.provider,
94
94
  clientPayload: {
95
95
  connectionId: requireNonEmptyString(payload.connectionId),
96
- stunServer: requireNonEmptyString(payload.stunServer)
96
+ stunServer: requireNonEmptyString(payload.stunServer),
97
+ ...payload.iceServers === void 0 || payload.iceServers === null ? {} : { iceServers: parseIceServers(payload.iceServers) }
97
98
  }
98
99
  };
99
100
  default:
100
101
  throw new AccessGrantError();
101
102
  }
102
103
  }
104
+ function parseIceServers(value) {
105
+ if (!Array.isArray(value)) throw new AccessGrantError();
106
+ return value.map((entry) => {
107
+ if (!isRecord(entry) || !Array.isArray(entry.urls) || entry.urls.length === 0) throw new AccessGrantError();
108
+ const urls = entry.urls.map(requireNonEmptyString);
109
+ if (entry.username === void 0 && entry.credential === void 0) return { urls };
110
+ return { urls, username: requireNonEmptyString(entry.username), credential: requireNonEmptyString(entry.credential) };
111
+ });
112
+ }
103
113
  function mediaClientPayload(value) {
104
114
  switch (value.provider) {
105
115
  case "cloudflare_sfu":
106
- return { connectionId: value.clientPayload.connectionId, stunServer: value.clientPayload.stunServer };
116
+ return {
117
+ connectionId: value.clientPayload.connectionId,
118
+ stunServer: value.clientPayload.stunServer,
119
+ ...value.clientPayload.iceServers ? { iceServers: value.clientPayload.iceServers } : {}
120
+ };
107
121
  }
108
122
  }
109
123
  function requireCredential(value, audience) {
@@ -602,9 +616,9 @@ function accessGrant(value) {
602
616
  function mediaClientPayload2(media) {
603
617
  switch (media.provider) {
604
618
  case "cloudflare_sfu": {
605
- const { connectionId, stunServer } = media.client_payload;
619
+ const { connectionId, stunServer, iceServers } = media.client_payload;
606
620
  if (typeof connectionId !== "string" || typeof stunServer !== "string") throw invalidResponse(201);
607
- return { connectionId, stunServer };
621
+ return { connectionId, stunServer, ...iceServers === void 0 ? {} : { iceServers } };
608
622
  }
609
623
  default:
610
624
  throw invalidResponse(201);
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The session is one run of a Chalk app on one device: a browser tab, or an app launch on mobile. Its ID is the
3
+ * OpenTelemetry attribute `session.id` and travels with the trace context as W3C baggage on every Chalk API request and
4
+ * Sync connection. It is unrelated to an Episode and to Cloudflare track or dashboard login `sessionId`s.
5
+ */
6
+ export declare const SESSION_ID_KEY = "session.id";
7
+ export type SessionSource = () => string | undefined;
8
+ /** Platforms with their own session rules, such as React Native, install their source once at startup. */
9
+ export declare function setSessionSource(next: SessionSource): void;
10
+ /** The current session ID, or undefined where no app session exists, such as a Node server. */
11
+ export declare function currentSessionId(): string | undefined;
12
+ export declare function newSessionId(): string;
13
+ /** The W3C baggage value that carries the session, or undefined where no app session exists. */
14
+ export declare function sessionBaggage(): string | undefined;
15
+ /** `baggage` and a fresh `traceparent` for one Chalk API request; empty where there is no app session. */
16
+ export declare function requestContextHeaders(): Record<string, string>;
17
+ /** Wraps a fetch so every request carries the session; headers the caller already set win. Use only for Chalk API origins. */
18
+ export declare function withRequestContext(fetchImplementation?: typeof globalThis.fetch): typeof globalThis.fetch;
@@ -22,7 +22,8 @@ import {
22
22
  encodeV1ClientFrame,
23
23
  optimisticV1Control,
24
24
  restoreV1Snapshot
25
- } from "../chunk-XSB2GDRW.js";
25
+ } from "../chunk-FD7ZSDTM.js";
26
+ import "../chunk-N4IPKRS6.js";
26
27
  export {
27
28
  AsyncStorageV1PendingTargetStore,
28
29
  InMemoryV1PendingTargetStore,
@@ -22,6 +22,10 @@ type V1CommandSchedulerOptions = {
22
22
  readonly ackHeadIsProven: (ack: Extract<V1CommandResult, {
23
23
  readonly outcome: "committed" | "satisfied";
24
24
  }>) => boolean;
25
+ readonly controlHead: () => {
26
+ readonly revision: number;
27
+ readonly state_digest: string;
28
+ };
25
29
  };
26
30
  export declare class V1CommandScheduler {
27
31
  #private;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q9labsai/chalk-client",
3
- "version": "5.3.0",
3
+ "version": "5.3.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -222,6 +222,7 @@
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:receive-stall-sfu": "node ./scripts/real-sfu-receive-stall.mjs",
225
+ "test:viewer-layer-sfu": "node ./scripts/real-sfu-viewer-layer.mjs",
225
226
  "test:sync-browser": "pnpm run build && node ./scripts/real-browser-sync.mjs",
226
227
  "test:sync-node-restart": "pnpm run build && node ./scripts/real-node-restart-sync.mjs",
227
228
  "check-types": "tsc-rs --project tsconfig.check-types.json --noEmit",