@q9labsai/chalk-client 5.2.5 → 5.3.0

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.
@@ -1,3 +1,6 @@
1
+ import type { operations } from "../generated/openapi-types";
2
+ export type MediaConnectionReplacementCause = NonNullable<NonNullable<operations["addCloudflareSFUTracks"]["parameters"]["query"]>["replacement_cause"]>;
3
+ export type LocalPublicationRetirementCause = Exclude<MediaConnectionReplacementCause, "local_signaling_failure" | "remote_signaling_failure" | "dormant_connection" | "access_refresh" | "receive_stalled">;
1
4
  export type ScreenShareSurface = "monitor" | "window" | "browser";
2
5
  export type ScreenShareOptions = {
3
6
  readonly surface?: ScreenShareSurface;
@@ -24,6 +27,7 @@ export type ConnectionMediaRemoteTrack = Pick<MediaPublication, "participantId"
24
27
  readonly track: MediaStreamTrack;
25
28
  };
26
29
  export type ConnectionMediaSnapshot = {
30
+ readonly receiveStalls?: readonly import("./receive-stall").ReceiveStallObservation[];
27
31
  readonly connection: {
28
32
  readonly phase: ConnectionMediaPhase;
29
33
  readonly peerConnectionState: RTCPeerConnectionState | null;
@@ -54,11 +58,13 @@ export type MediaPlaneResult = {
54
58
  };
55
59
  export type ClientMediaPlane = {
56
60
  readonly setLocalPublicationTarget: (target: MediaPlaneTarget) => Promise<MediaPlaneResult>;
57
- readonly setLocalSourceIntent?: (source: MediaSource, enabled: boolean) => void;
58
- readonly closeForcedLocalPublication?: (source: MediaSource) => Promise<void>;
61
+ readonly pauseLocalCapture?: (source: "microphone" | "camera") => Promise<void>;
62
+ readonly setLocalSourceIntent?: (source: MediaSource, enabled: boolean, cause?: LocalPublicationRetirementCause) => void;
63
+ readonly closeForcedLocalPublication?: (source: MediaSource, cause?: LocalPublicationRetirementCause) => Promise<void>;
59
64
  readonly observeLocalPublications: (listener: (publications: readonly MediaPublication[]) => void) => () => void;
60
65
  readonly observeRemotePublications: (listener: (publications: readonly MediaPublication[]) => void) => () => void;
61
- readonly remotePublicationsChanged?: () => void;
66
+ /** Unavailable suspends receive eligibility without withdrawing publication identities. */
67
+ readonly remotePublicationsChanged?: (publications?: readonly MediaPublication[], projectionState?: "available" | "unavailable") => void;
62
68
  readonly remoteParticipantsChanged?: (participantIds: readonly string[]) => void;
63
69
  readonly remotePublicationResumed?: (publicationId: string) => void;
64
70
  };
@@ -0,0 +1,43 @@
1
+ import type { MediaPublication } from "./plane";
2
+ import type { CloudflareSFURemoteTrack } from "./types";
3
+ export declare const RECEIVE_STALL_WINDOW_MS = 7000;
4
+ export declare const RECEIVE_STARTUP_GRACE_MS = 20000;
5
+ export type ReceivePacketSample = {
6
+ readonly publicationId: string;
7
+ readonly trackId: string;
8
+ readonly packets: number;
9
+ };
10
+ /** Only consecutive, timely observations of expected media establish a stall. */
11
+ export declare class ReceiveStallDetector {
12
+ #private;
13
+ forget(publicationId: string): void;
14
+ clear(): void;
15
+ sample(now: number, tracks: readonly ReceivePacketSample[]): {
16
+ readonly stalled: readonly string[];
17
+ readonly advanced: readonly string[];
18
+ readonly resumed: readonly string[];
19
+ };
20
+ }
21
+ export type ReceiveStallObservation = {
22
+ readonly event: "receive_stalled";
23
+ readonly scope: "partial" | "all";
24
+ readonly observedAtMs: number;
25
+ readonly expectedTracks: number;
26
+ readonly stalledTracks: number;
27
+ };
28
+ /** Owns eligibility, pending stalls and the budget across replacement peers. */
29
+ export declare class ReceiveStallRecovery {
30
+ #private;
31
+ get observations(): readonly ReceiveStallObservation[];
32
+ expect(publications: readonly MediaPublication[], participantId: string): void;
33
+ suspend(): void;
34
+ isStalled(publicationId: string): boolean;
35
+ canRebuild(): boolean;
36
+ reserveRebuild(): boolean;
37
+ clear(): void;
38
+ sample(now: number, connection: RTCPeerConnection, report: RTCStatsReport, tracks: Iterable<CloudflareSFURemoteTrack>): {
39
+ readonly stalled: readonly string[];
40
+ readonly advanced: readonly string[];
41
+ readonly newlyStalled: readonly string[];
42
+ };
43
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ConnectionReportTransportEvent } from "../transport-diagnostics";
2
- import type { MediaPublication, MediaPublicationSource } from "./plane";
2
+ import type { MediaConnectionReplacementCause, MediaPublication, MediaPublicationSource } from "./plane";
3
3
  export type CloudflareSFUBootstrap = {
4
4
  readonly connectionId: string;
5
5
  readonly stunServer: string;
@@ -52,6 +52,7 @@ export type CloudflareSFUSignalingTransport = {
52
52
  readonly sessionDescription?: CloudflareSFUSessionDescription;
53
53
  readonly tracks: readonly CloudflareSFUTrackRequest[];
54
54
  readonly allowPartialRemoteTracks?: boolean;
55
+ readonly replacementCause?: MediaConnectionReplacementCause;
55
56
  }) => Promise<CloudflareSFUTracksResponse>;
56
57
  readonly closeTracks: (input: {
57
58
  readonly connectionId: string;
@@ -76,6 +77,8 @@ export type CloudflareSFUFailureCode = CloudflareSFUErrorCode;
76
77
  export type CloudflareSFUConnectionPhase = "idle" | "connecting" | "live" | "recovering" | "failed" | "stopped";
77
78
  export type CloudflareSFUPhase = CloudflareSFUConnectionPhase;
78
79
  export type CloudflareSFUSnapshot = {
80
+ /** Omitted by media adapters without receive diagnostics; treat as empty. */
81
+ readonly receiveStalls?: readonly import("./receive-stall").ReceiveStallObservation[];
79
82
  readonly connection: {
80
83
  readonly phase: CloudflareSFUConnectionPhase;
81
84
  readonly peerConnectionState: RTCPeerConnectionState | null;
@@ -2,12 +2,12 @@ import {
2
2
  createChalkPublicClient,
3
3
  createChalkPublicInviteClient,
4
4
  parseInviteCode
5
- } from "../chunk-LKNT6XFD.js";
5
+ } from "../chunk-O26BHHMI.js";
6
6
  import {
7
7
  ArrivalInvalidHandleError,
8
8
  ArrivalUnavailableError,
9
9
  SpacePublicInviteUnavailableError
10
- } from "../chunk-UMAE5GCV.js";
10
+ } from "../chunk-GOSWSXTH.js";
11
11
  export {
12
12
  ArrivalInvalidHandleError,
13
13
  ArrivalUnavailableError,
@@ -602,6 +602,7 @@ function participantAdmissionRequest(input) {
602
602
  name: input.name,
603
603
  role: input.role,
604
604
  ...input.identityId === void 0 ? {} : { identity_id: input.identityId },
605
+ ...input.identityExternalId === void 0 ? {} : { identity_external_id: input.identityExternalId },
605
606
  ...input.metadata === void 0 ? {} : { metadata: input.metadata },
606
607
  ...input.participantId === void 0 ? {} : { participant_id: input.participantId }
607
608
  };
@@ -504,6 +504,7 @@ function participantAdmissionRequest(input) {
504
504
  name: input.name,
505
505
  role: input.role,
506
506
  ...input.identityId === void 0 ? {} : { identity_id: input.identityId },
507
+ ...input.identityExternalId === void 0 ? {} : { identity_external_id: input.identityExternalId },
507
508
  ...input.metadata === void 0 ? {} : { metadata: input.metadata },
508
509
  ...input.participantId === void 0 ? {} : { participant_id: input.participantId }
509
510
  };
@@ -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;
@@ -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;
@@ -1,11 +1,11 @@
1
1
  import { Effect } from "effect";
2
- type ControllerCommand = (...args: any[]) => unknown;
3
- type PromiseRuntime = {
4
- readonly runPromise: (effect: Effect.Effect<any, any, any>) => Promise<any>;
2
+ type ControllerCommand = (...args: never[]) => unknown;
3
+ type PromiseRuntime<Services> = {
4
+ readonly runPromise: (effect: Effect.Effect<unknown, unknown, Services>) => Promise<unknown>;
5
5
  };
6
- export type PromiseCommand<Command> = Command extends (...args: infer Arguments) => infer Result ? (Result extends Effect.Effect<infer Success, any, any> ? (...args: Arguments) => Promise<Success> : (...args: Arguments) => Result) : never;
6
+ export type PromiseCommand<Command> = Command extends (...args: infer Arguments) => infer Result ? (Result extends Effect.Effect<infer Success, unknown, unknown> ? (...args: Arguments) => Promise<Success> : (...args: Arguments) => Result) : never;
7
7
  export type PromiseController<Controller extends Record<string, ControllerCommand>> = {
8
8
  readonly [Key in keyof Controller]: PromiseCommand<Controller[Key]>;
9
9
  };
10
- export declare function toPromiseController<Controller extends Record<string, ControllerCommand>>(runtime: PromiseRuntime, controller: Controller): PromiseController<Omit<Controller, "configure" | "dispose">>;
10
+ export declare function toPromiseController<Services, Controller extends Record<string, ControllerCommand>>(runtime: PromiseRuntime<Services>, controller: Controller): PromiseController<Omit<Controller, "configure" | "dispose">>;
11
11
  export {};
@@ -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[];
@@ -22,7 +22,7 @@ import {
22
22
  encodeV1ClientFrame,
23
23
  optimisticV1Control,
24
24
  restoreV1Snapshot
25
- } from "../chunk-5YPQNMJE.js";
25
+ } from "../chunk-XSB2GDRW.js";
26
26
  export {
27
27
  AsyncStorageV1PendingTargetStore,
28
28
  InMemoryV1PendingTargetStore,
@@ -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.2.5",
3
+ "version": "5.3.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -221,6 +221,7 @@
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",
224
225
  "test:sync-browser": "pnpm run build && node ./scripts/real-browser-sync.mjs",
225
226
  "test:sync-node-restart": "pnpm run build && node ./scripts/real-node-restart-sync.mjs",
226
227
  "check-types": "tsc-rs --project tsconfig.check-types.json --noEmit",