@q9labsai/chalk-client 5.2.6 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/access/grant.d.ts +2 -0
- package/dist/{chunk-I7Y4HR2P.js → chunk-2CEMOTZF.js} +333 -64
- package/dist/{chunk-5YPQNMJE.js → chunk-FD7ZSDTM.js} +103 -26
- package/dist/{chunk-CJVLTSIO.js → chunk-MHJ4UPM3.js} +165 -6
- package/dist/chunk-N4IPKRS6.js +76 -0
- package/dist/{chunk-YIDKETOX.js → chunk-O5JB5XVX.js} +840 -38
- package/dist/{chunk-WFRTUBN6.js → chunk-VAJ6XNAA.js} +1 -1
- package/dist/client.d.ts +182 -1
- package/dist/connection/dependencies.d.ts +2 -1
- package/dist/effect.js +29 -4
- package/dist/generated/http-api.d.ts +180 -1
- package/dist/generated/schemas.d.ts +290 -1
- package/dist/generated/sync.d.ts +60 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +33 -15
- package/dist/media/call-stats.d.ts +27 -0
- package/dist/media/client.d.ts +2 -2
- package/dist/media/ice-servers.d.ts +3 -0
- package/dist/media/index.d.ts +2 -0
- package/dist/media/index.js +4 -1
- package/dist/media/plane.d.ts +4 -2
- package/dist/media/receive-stall.d.ts +52 -0
- package/dist/media/types.d.ts +21 -0
- package/dist/media/video-render-size.d.ts +7 -0
- package/dist/media/viewer-layer.d.ts +48 -0
- package/dist/public-invites/index.js +3 -2
- package/dist/server/index.cjs +19 -4
- package/dist/server/index.js +19 -4
- package/dist/server/types.d.cts +2 -0
- package/dist/server/types.d.ts +2 -0
- package/dist/session/index.d.ts +18 -0
- package/dist/space-client/same-identity.d.ts +6 -0
- package/dist/space-client/types.d.ts +6 -0
- package/dist/sync/index.js +2 -1
- package/dist/sync/v1-command-scheduler.d.ts +4 -0
- package/dist/sync/v1-types.d.ts +6 -0
- package/package.json +3 -1
|
@@ -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-
|
|
5
|
+
} from "../chunk-VAJ6XNAA.js";
|
|
6
6
|
import {
|
|
7
7
|
ArrivalInvalidHandleError,
|
|
8
8
|
ArrivalUnavailableError,
|
|
9
9
|
SpacePublicInviteUnavailableError
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-MHJ4UPM3.js";
|
|
11
|
+
import "../chunk-N4IPKRS6.js";
|
|
11
12
|
export {
|
|
12
13
|
ArrivalInvalidHandleError,
|
|
13
14
|
ArrivalUnavailableError,
|
package/dist/server/index.cjs
CHANGED
|
@@ -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 {
|
|
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) {
|
|
@@ -602,6 +616,7 @@ function participantAdmissionRequest(input) {
|
|
|
602
616
|
name: input.name,
|
|
603
617
|
role: input.role,
|
|
604
618
|
...input.identityId === void 0 ? {} : { identity_id: input.identityId },
|
|
619
|
+
...input.identityExternalId === void 0 ? {} : { identity_external_id: input.identityExternalId },
|
|
605
620
|
...input.metadata === void 0 ? {} : { metadata: input.metadata },
|
|
606
621
|
...input.participantId === void 0 ? {} : { participant_id: input.participantId }
|
|
607
622
|
};
|
|
@@ -699,9 +714,9 @@ function accessGrant(value) {
|
|
|
699
714
|
function mediaClientPayload2(media) {
|
|
700
715
|
switch (media.provider) {
|
|
701
716
|
case "cloudflare_sfu": {
|
|
702
|
-
const { connectionId, stunServer } = media.client_payload;
|
|
717
|
+
const { connectionId, stunServer, iceServers } = media.client_payload;
|
|
703
718
|
if (typeof connectionId !== "string" || typeof stunServer !== "string") throw invalidResponse(201);
|
|
704
|
-
return { connectionId, stunServer };
|
|
719
|
+
return { connectionId, stunServer, ...iceServers === void 0 ? {} : { iceServers } };
|
|
705
720
|
}
|
|
706
721
|
default:
|
|
707
722
|
throw invalidResponse(201);
|
package/dist/server/index.js
CHANGED
|
@@ -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 {
|
|
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) {
|
|
@@ -504,6 +518,7 @@ function participantAdmissionRequest(input) {
|
|
|
504
518
|
name: input.name,
|
|
505
519
|
role: input.role,
|
|
506
520
|
...input.identityId === void 0 ? {} : { identity_id: input.identityId },
|
|
521
|
+
...input.identityExternalId === void 0 ? {} : { identity_external_id: input.identityExternalId },
|
|
507
522
|
...input.metadata === void 0 ? {} : { metadata: input.metadata },
|
|
508
523
|
...input.participantId === void 0 ? {} : { participant_id: input.participantId }
|
|
509
524
|
};
|
|
@@ -601,9 +616,9 @@ function accessGrant(value) {
|
|
|
601
616
|
function mediaClientPayload2(media) {
|
|
602
617
|
switch (media.provider) {
|
|
603
618
|
case "cloudflare_sfu": {
|
|
604
|
-
const { connectionId, stunServer } = media.client_payload;
|
|
619
|
+
const { connectionId, stunServer, iceServers } = media.client_payload;
|
|
605
620
|
if (typeof connectionId !== "string" || typeof stunServer !== "string") throw invalidResponse(201);
|
|
606
|
-
return { connectionId, stunServer };
|
|
621
|
+
return { connectionId, stunServer, ...iceServers === void 0 ? {} : { iceServers } };
|
|
607
622
|
}
|
|
608
623
|
default:
|
|
609
624
|
throw invalidResponse(201);
|
package/dist/server/types.d.cts
CHANGED
|
@@ -104,6 +104,8 @@ export type EpisodeEnd = {
|
|
|
104
104
|
readonly status: string;
|
|
105
105
|
};
|
|
106
106
|
export type AdmitParticipantInput = {
|
|
107
|
+
/** Stable person key scoped to this tenant. Mutually exclusive with identityId. */
|
|
108
|
+
readonly identityExternalId?: string;
|
|
107
109
|
readonly identityId?: string;
|
|
108
110
|
readonly metadata?: unknown;
|
|
109
111
|
readonly name: string;
|
package/dist/server/types.d.ts
CHANGED
|
@@ -104,6 +104,8 @@ export type EpisodeEnd = {
|
|
|
104
104
|
readonly status: string;
|
|
105
105
|
};
|
|
106
106
|
export type AdmitParticipantInput = {
|
|
107
|
+
/** Stable person key scoped to this tenant. Mutually exclusive with identityId. */
|
|
108
|
+
readonly identityExternalId?: string;
|
|
107
109
|
readonly identityId?: string;
|
|
108
110
|
readonly metadata?: unknown;
|
|
109
111
|
readonly name: string;
|
|
@@ -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;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Participant } from "./types";
|
|
2
|
+
import type { V1EpisodeSnapshot } from "../sync/v1-types";
|
|
3
|
+
type PersonSeat = Pick<Participant, "participantId" | "identityId">;
|
|
4
|
+
export declare function otherDevices<Seat extends PersonSeat>(participants: readonly Seat[], participantId: string | null, ownIdentityId?: string): readonly Seat[];
|
|
5
|
+
export declare function otherDeviceHasMicrophone(snapshot: V1EpisodeSnapshot, participants: readonly PersonSeat[]): boolean;
|
|
6
|
+
export {};
|
|
@@ -77,6 +77,8 @@ export type ConnectionSlice = {
|
|
|
77
77
|
};
|
|
78
78
|
export type SelfSlice = {
|
|
79
79
|
readonly participantId: string | null;
|
|
80
|
+
/** Verified person association, including before this seat confirms entry. */
|
|
81
|
+
readonly identityId?: string;
|
|
80
82
|
readonly displayName: string | null;
|
|
81
83
|
readonly role: string | null;
|
|
82
84
|
readonly capabilities: readonly Capability[];
|
|
@@ -89,6 +91,9 @@ export type ParticipantPresence = {
|
|
|
89
91
|
readonly activeSpeaker: boolean;
|
|
90
92
|
};
|
|
91
93
|
export type Participant = {
|
|
94
|
+
/** Stable person identity, independent of the per-device participant ID. */
|
|
95
|
+
readonly identityId?: string;
|
|
96
|
+
readonly deviceType?: "phone" | "tablet" | "computer";
|
|
92
97
|
readonly participantId: string;
|
|
93
98
|
readonly displayName: string;
|
|
94
99
|
readonly role: string;
|
|
@@ -128,6 +133,7 @@ export type RemoteMedia = {
|
|
|
128
133
|
export type IncomingMediaRequest = ChalkIncomingMediaRequest;
|
|
129
134
|
export type { ScreenShareSurface, ScreenShareOptions, ScreenShareCapture } from "../media/plane";
|
|
130
135
|
export type MediaSlice = {
|
|
136
|
+
readonly microphoneMutedByOtherDevice?: boolean;
|
|
131
137
|
readonly devices: {
|
|
132
138
|
readonly microphones: readonly MediaDevice[];
|
|
133
139
|
readonly cameras: readonly MediaDevice[];
|
package/dist/sync/index.js
CHANGED
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
encodeV1ClientFrame,
|
|
23
23
|
optimisticV1Control,
|
|
24
24
|
restoreV1Snapshot
|
|
25
|
-
} from "../chunk-
|
|
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/dist/sync/v1-types.d.ts
CHANGED
|
@@ -52,6 +52,10 @@ export type V1ControlState = {
|
|
|
52
52
|
};
|
|
53
53
|
export type V1MediaPublication = MediaPublication;
|
|
54
54
|
export type V1Presence = {
|
|
55
|
+
readonly admissionRevision?: number;
|
|
56
|
+
/** Stable person identity, independent of the per-device participant ID. */
|
|
57
|
+
readonly identityId?: string;
|
|
58
|
+
readonly deviceType?: "phone" | "tablet" | "computer";
|
|
55
59
|
readonly participantId: string;
|
|
56
60
|
readonly state: "connected" | "disconnected";
|
|
57
61
|
readonly speaking: boolean;
|
|
@@ -86,6 +90,8 @@ export type V1EpisodeSnapshot = {
|
|
|
86
90
|
};
|
|
87
91
|
readonly participantId: string | null;
|
|
88
92
|
readonly participantGeneration: number | null;
|
|
93
|
+
/** Verified own-seat identity, available before admission confirmation on negotiated servers. */
|
|
94
|
+
readonly participantIdentityId?: string;
|
|
89
95
|
readonly control: V1ControlState | null;
|
|
90
96
|
readonly optimisticControl: V1ControlState | null;
|
|
91
97
|
readonly media: V1Projection<V1MediaPublication> | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@q9labsai/chalk-client",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -221,6 +221,8 @@
|
|
|
221
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
|
+
"test:receive-stall-sfu": "node ./scripts/real-sfu-receive-stall.mjs",
|
|
225
|
+
"test:viewer-layer-sfu": "node ./scripts/real-sfu-viewer-layer.mjs",
|
|
224
226
|
"test:sync-browser": "pnpm run build && node ./scripts/real-browser-sync.mjs",
|
|
225
227
|
"test:sync-node-restart": "pnpm run build && node ./scripts/real-node-restart-sync.mjs",
|
|
226
228
|
"check-types": "tsc-rs --project tsconfig.check-types.json --noEmit",
|