@stream-io/video-client 1.59.1 → 1.61.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.browser.es.js +744 -156
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +744 -155
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.es.js +744 -156
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/src/Call.d.ts +34 -8
  10. package/dist/src/coordinator/connection/types.d.ts +16 -0
  11. package/dist/src/events/call.d.ts +0 -11
  12. package/dist/src/gen/coordinator/index.d.ts +73 -0
  13. package/dist/src/reporting/ClientEventReporter.d.ts +11 -1
  14. package/dist/src/ringing/RingStatePoller.d.ts +46 -0
  15. package/dist/src/ringing/RingTimeout.d.ts +27 -0
  16. package/dist/src/ringing/index.d.ts +4 -0
  17. package/dist/src/ringing/reconcileRingState.d.ts +18 -0
  18. package/dist/src/ringing/resolveOwnRingOutcome.d.ts +32 -0
  19. package/dist/src/rtc/Publisher.d.ts +18 -0
  20. package/dist/src/rtc/TransceiverCache.d.ts +10 -0
  21. package/dist/src/rtc/e2ee/E2EEManager.d.ts +10 -1
  22. package/dist/src/stats/utils.d.ts +6 -0
  23. package/dist/src/store/CallState.d.ts +10 -1
  24. package/dist/src/types.d.ts +30 -1
  25. package/index.ts +1 -0
  26. package/package.json +1 -1
  27. package/src/Call.ts +272 -108
  28. package/src/StreamSfuClient.ts +6 -2
  29. package/src/__tests__/Call.autodrop.test.ts +116 -64
  30. package/src/__tests__/Call.getRingState.test.ts +87 -0
  31. package/src/__tests__/Call.lifecycle.test.ts +498 -5
  32. package/src/__tests__/Call.ringSettled.test.ts +71 -0
  33. package/src/__tests__/Call.ringingLifecycle.test.ts +242 -0
  34. package/src/__tests__/StreamSfuClient.test.ts +48 -0
  35. package/src/coordinator/connection/types.ts +19 -0
  36. package/src/events/__tests__/call.test.ts +2 -217
  37. package/src/events/call.ts +1 -73
  38. package/src/events/callEventHandlers.ts +12 -23
  39. package/src/gen/coordinator/index.ts +67 -0
  40. package/src/reporting/ClientEventReporter.ts +30 -4
  41. package/src/reporting/__tests__/ClientEventReporter.test.ts +184 -7
  42. package/src/ringing/RingStatePoller.ts +177 -0
  43. package/src/ringing/RingTimeout.ts +90 -0
  44. package/src/ringing/__tests__/RingStatePoller.test.ts +462 -0
  45. package/src/ringing/__tests__/RingTimeout.test.ts +199 -0
  46. package/src/ringing/__tests__/reconcileRingState.test.ts +330 -0
  47. package/src/ringing/__tests__/resolveOwnRingOutcome.test.ts +103 -0
  48. package/src/ringing/index.ts +4 -0
  49. package/src/ringing/reconcileRingState.ts +108 -0
  50. package/src/ringing/resolveOwnRingOutcome.ts +58 -0
  51. package/src/rtc/Publisher.ts +66 -12
  52. package/src/rtc/TransceiverCache.ts +17 -0
  53. package/src/rtc/__tests__/Publisher.test.ts +209 -0
  54. package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -0
  55. package/src/rtc/e2ee/E2EEManager.ts +10 -1
  56. package/src/rtc/e2ee/EncryptionManager.ts +1 -1
  57. package/src/rtc/e2ee/SPEC.md +1 -1
  58. package/src/stats/__tests__/utils.test.ts +33 -0
  59. package/src/stats/utils.ts +15 -0
  60. package/src/store/CallState.ts +25 -0
  61. package/src/store/__tests__/CallState.test.ts +59 -0
  62. package/src/types.ts +37 -1
@@ -2,11 +2,11 @@ import { type CallMediaEngine, Publisher, Subscriber, TrackPublishOptions } from
2
2
  import type { E2EEManager } from './rtc/e2ee/E2EEManager';
3
3
  import { CallState } from './store';
4
4
  import { ScopedLogger } from './logger';
5
- import { AcceptCallResponse, BlockUserResponse, CallRingEvent, CallSettingsResponse, CallStateResponseFields, CollectUserFeedbackRequest, CollectUserFeedbackResponse, DeleteCallRequest, DeleteCallResponse, DeleteRecordingResponse, DeleteTranscriptionResponse, EndCallResponse, GetCallReportResponse, GetCallResponse, GetCallSessionParticipantStatsDetailsResponse, GetOrCreateCallRequest, GetOrCreateCallResponse, GoLiveRequest, GoLiveResponse, JoinCallResponse, KickUserRequest, KickUserResponse, ListRecordingsResponse, ListTranscriptionsResponse, MuteUsersResponse, PinRequest, PinResponse, QueryCallMembersRequest, QueryCallMembersResponse, QueryCallParticipantsRequest, QueryCallParticipantsResponse, QueryCallSessionParticipantStatsResponse, QueryCallSessionParticipantStatsTimelineResponse, QueryCallStatsMapResponse, RejectCallResponse, RequestPermissionRequest, RequestPermissionResponse, RingCallRequest, RingCallResponse, SendCallEventResponse, SendVideoReactionRequest, SendVideoReactionResponse, StartClosedCaptionsRequest, StartClosedCaptionsResponse, StartFrameRecordingRequest, StartFrameRecordingResponse, StartHLSBroadcastingResponse, StartRTMPBroadcastsRequest, StartRTMPBroadcastsResponse, StartTranscriptionRequest, StartTranscriptionResponse, StopAllRTMPBroadcastsResponse, StopClosedCaptionsRequest, StopClosedCaptionsResponse, StopFrameRecordingResponse, StopHLSBroadcastingResponse, StopLiveRequest, StopLiveResponse, StopRecordingResponse, StopRTMPBroadcastsResponse, StopTranscriptionResponse, UnblockUserResponse, UnpinRequest, UnpinResponse, UpdateCallMembersRequest, UpdateCallMembersResponse, UpdateCallRequest, UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsRequestGrantPermissionsEnum, UpdateUserPermissionsRequestRevokePermissionsEnum, UpdateUserPermissionsResponse } from './gen/coordinator';
5
+ import { AcceptCallResponse, BlockUserResponse, CallRingEvent, CallSettingsResponse, CallStateResponseFields, CollectUserFeedbackRequest, CollectUserFeedbackResponse, DeleteCallRequest, DeleteCallResponse, DeleteRecordingResponse, DeleteTranscriptionResponse, EndCallResponse, GetCallReportResponse, GetCallResponse, GetCallRingStateResponse, GetCallSessionParticipantStatsDetailsResponse, GetOrCreateCallRequest, GetOrCreateCallResponse, GoLiveRequest, GoLiveResponse, JoinCallResponse, KickUserRequest, KickUserResponse, ListRecordingsResponse, ListTranscriptionsResponse, MuteUsersResponse, PinRequest, PinResponse, QueryCallMembersRequest, QueryCallMembersResponse, QueryCallParticipantsRequest, QueryCallParticipantsResponse, QueryCallSessionParticipantStatsResponse, QueryCallSessionParticipantStatsTimelineResponse, QueryCallStatsMapResponse, RejectCallResponse, RequestPermissionRequest, RequestPermissionResponse, RingCallRequest, RingCallResponse, SendCallEventResponse, SendVideoReactionRequest, SendVideoReactionResponse, StartClosedCaptionsRequest, StartClosedCaptionsResponse, StartFrameRecordingRequest, StartFrameRecordingResponse, StartHLSBroadcastingResponse, StartRTMPBroadcastsRequest, StartRTMPBroadcastsResponse, StartTranscriptionRequest, StartTranscriptionResponse, StopAllRTMPBroadcastsResponse, StopClosedCaptionsRequest, StopClosedCaptionsResponse, StopFrameRecordingResponse, StopHLSBroadcastingResponse, StopLiveRequest, StopLiveResponse, StopRecordingResponse, StopRTMPBroadcastsResponse, StopTranscriptionResponse, UnblockUserResponse, UnpinRequest, UnpinResponse, UpdateCallMembersRequest, UpdateCallMembersResponse, UpdateCallRequest, UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsRequestGrantPermissionsEnum, UpdateUserPermissionsRequestRevokePermissionsEnum, UpdateUserPermissionsResponse } from './gen/coordinator';
6
6
  import { AudioTrackType, CallConstructor, CallLeaveOptions, CallRecordingType, ClientPublishOptions, ClosedCaptionsSettings, JoinCallData, StartCallRecordingFnType, TrackMuteType, VideoTrackType } from './types';
7
7
  import { ClientCapability, TrackType, VideoDimension } from './gen/video/sfu/models/models';
8
8
  import { Tracer } from './stats';
9
- import type { ClientEventReporter } from './reporting';
9
+ import type { ClientEventReporter, JoinSource } from './reporting';
10
10
  import { AudioBindingsWatchdog } from './helpers/AudioBindingsWatchdog';
11
11
  import { BlockedAudioTracker } from './helpers/BlockedAudioTracker';
12
12
  import { TrackSubscriptionManager } from './helpers/TrackSubscriptionManager';
@@ -107,7 +107,8 @@ export declare class Call {
107
107
  private statsReporter?;
108
108
  private sfuStatsReporter?;
109
109
  private lastStatsOptions?;
110
- private dropTimeout;
110
+ private ringTimeout;
111
+ private ringStatePoller;
111
112
  private readonly clientStore;
112
113
  readonly streamClient: StreamClient;
113
114
  readonly clientEventReporter: ClientEventReporter;
@@ -265,6 +266,15 @@ export declare class Call {
265
266
  * All users should be members of the call.
266
267
  */
267
268
  ring: (data?: RingCallRequest) => Promise<RingCallResponse>;
269
+ /**
270
+ * Returns who accepted, rejected or missed the ring for a call session.
271
+ * Safe to poll: it performs no writes and emits no events.
272
+ *
273
+ * @param callSessionId the call session to read. Defaults to the current one.
274
+ * Pass it explicitly to read a session that has already ended, as ending a
275
+ * call clears its current session.
276
+ */
277
+ getRingState: (callSessionId?: string) => Promise<GetCallRingStateResponse>;
268
278
  /**
269
279
  * A shortcut for {@link Call.get} with `notify` parameter set to `true`.
270
280
  * Will send a `call.notification` event to the call members.
@@ -291,6 +301,10 @@ export declare class Call {
291
301
  /**
292
302
  * Will start to watch for call related WebSocket events and initiate a call session with the server.
293
303
  *
304
+ * One instance is one call flow: discard it after leaving, cancelling, or a
305
+ * failed join, and create a fresh one for a later flow. Reconnection inside a
306
+ * live call is handled here and needs no new instance.
307
+ *
294
308
  * @returns a promise which resolves once the call join-flow has finished.
295
309
  */
296
310
  join: (args_0?: (JoinCallData & {
@@ -298,6 +312,7 @@ export declare class Call {
298
312
  joinResponseTimeout?: number;
299
313
  rpcRequestTimeout?: number;
300
314
  allowOwnTracksLoopback?: boolean;
315
+ joinSource?: JoinSource;
301
316
  }) | undefined) => Promise<void>;
302
317
  /**
303
318
  * Will make a single attempt to watch for call related WebSocket events
@@ -344,8 +359,9 @@ export declare class Call {
344
359
  *
345
360
  * @internal
346
361
  * @param data the join call data.
362
+ * @returns The coordinator response, or undefined if leave superseded the request before it was sent.
347
363
  */
348
- doJoinRequest: (data?: JoinCallData) => Promise<JoinCallResponse>;
364
+ doJoinRequest: (data?: JoinCallData) => Promise<JoinCallResponse | undefined>;
349
365
  /**
350
366
  * Whether the per-call media engine currently exists. True from join until leave.
351
367
  *
@@ -473,10 +489,11 @@ export declare class Call {
473
489
  * Must be called before {@link join} so the RTCPeerConnection can be
474
490
  * configured for E2EE.
475
491
  *
476
- * The manager is kept across {@link leave} so a rejoin of this same instance
477
- * stays encrypted: do not dispose it while this call may be joined again.
478
- * A disposed manager throws from `encrypt`/`decrypt` rather than silently
479
- * publishing nothing, so re-attach a fresh one instead of reusing it.
492
+ * The application owns the manager's lifetime - the SDK never disposes it, and
493
+ * closing the peer connections does not release it. Discard the manager
494
+ * together with this call instance once its flow has ended, and create a fresh
495
+ * pair for a later flow: a disposed manager throws from `encrypt`/`decrypt`
496
+ * rather than silently publishing nothing.
480
497
  *
481
498
  * @param e2ee - Any `E2EEManager`. Use `EncryptionManager.create()` for the
482
499
  * built-in AES-GCM scheme, or pass your own implementation.
@@ -769,6 +786,15 @@ export declare class Call {
769
786
  * Cancels a scheduled auto-drop timeout.
770
787
  */
771
788
  private cancelAutoDrop;
789
+ /**
790
+ * Starts polling for the ring outcome. Applicable only to ringing calls the
791
+ * current user created.
792
+ */
793
+ private scheduleRingStatePolling;
794
+ /**
795
+ * Cancels the ring state polling.
796
+ */
797
+ private cancelRingStatePolling;
772
798
  /**
773
799
  * Retrieves the list of recordings for the current call or call session.
774
800
  *
@@ -136,6 +136,16 @@ export type AllClientCallEvents = {
136
136
  export type AllCallEvents = AllClientCallEvents & AllSfuEvents;
137
137
  export type CallEventListener<E extends keyof AllCallEvents> = (event: AllCallEvents[E]) => void;
138
138
  export type Logger = (logLevel: LogLevel, message: string, ...args: unknown[]) => void;
139
+ export type RingStatePollingOptions = {
140
+ /**
141
+ * Quiet time after the ring starts, before the first poll. Defaults to 15_000.
142
+ */
143
+ startAfterMs?: number;
144
+ /**
145
+ * The interval between polls. Defaults to 5_000.
146
+ */
147
+ intervalMs?: number;
148
+ };
139
149
  export type StreamClientOptions = Partial<AxiosRequestConfig> & {
140
150
  /**
141
151
  * Used to disable warnings that are triggered by using connectUser or connectAnonymousUser server-side.
@@ -227,6 +237,12 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
227
237
  * When set to true, the incoming calls are rejected when the user is busy in an another call.
228
238
  */
229
239
  rejectCallWhenBusy?: boolean;
240
+ /**
241
+ * Caller-side polling for the ring outcome, used when the `call.accepted`,
242
+ * `call.rejected` or `call.missed` event never arrives. Enabled by default;
243
+ * set to `false` to disable, or pass an object to tune the timings.
244
+ */
245
+ ringStatePolling?: false | RingStatePollingOptions;
230
246
  /**
231
247
  * Device persistence preference options (web only).
232
248
  */
@@ -1,15 +1,4 @@
1
1
  import { Call } from '../Call';
2
- import { CallAcceptedEvent, CallRejectedEvent } from '../gen/coordinator';
3
- /**
4
- * Event handler that watched the delivery of `call.accepted`.
5
- * Once the event is received, the call is joined.
6
- */
7
- export declare const watchCallAccepted: (call: Call) => (event: CallAcceptedEvent) => Promise<void>;
8
- /**
9
- * Event handler that watches delivery of `call.rejected` Websocket event.
10
- * Once the event is received, the call is left.
11
- */
12
- export declare const watchCallRejected: (call: Call) => (event: CallRejectedEvent) => Promise<void>;
13
2
  /**
14
3
  * Event handler that watches the delivery of `call.ended` Websocket event.
15
4
  */
@@ -5133,6 +5133,79 @@ export interface GetCallResponse {
5133
5133
  */
5134
5134
  own_capabilities: Array<OwnCapability>;
5135
5135
  }
5136
+ /**
5137
+ * The ring state of a call session: who accepted, rejected, or missed the ring.
5138
+ * @export
5139
+ * @interface GetCallRingStateResponse
5140
+ */
5141
+ export interface GetCallRingStateResponse {
5142
+ /**
5143
+ * Users that accepted the call, mapped to when they accepted
5144
+ * @type {{ [key: string]: string; }}
5145
+ * @memberof GetCallRingStateResponse
5146
+ */
5147
+ accepted_by: {
5148
+ [key: string]: string;
5149
+ };
5150
+ /**
5151
+ * The CID of the call
5152
+ * @type {string}
5153
+ * @memberof GetCallRingStateResponse
5154
+ */
5155
+ call_cid: string;
5156
+ /**
5157
+ * When the call ended
5158
+ * @type {string}
5159
+ * @memberof GetCallRingStateResponse
5160
+ */
5161
+ call_ended_at?: string;
5162
+ /**
5163
+ * The user that created the call, i.e. the caller
5164
+ * @type {string}
5165
+ * @memberof GetCallRingStateResponse
5166
+ */
5167
+ created_by_user_id: string;
5168
+ /**
5169
+ * Duration of the request in milliseconds
5170
+ * @type {string}
5171
+ * @memberof GetCallRingStateResponse
5172
+ */
5173
+ duration: string;
5174
+ /**
5175
+ * Users that missed the call, mapped to when they were marked as missed
5176
+ * @type {{ [key: string]: string; }}
5177
+ * @memberof GetCallRingStateResponse
5178
+ */
5179
+ missed_by: {
5180
+ [key: string]: string;
5181
+ };
5182
+ /**
5183
+ * Users that rejected the call, mapped to when they rejected
5184
+ * @type {{ [key: string]: string; }}
5185
+ * @memberof GetCallRingStateResponse
5186
+ */
5187
+ rejected_by: {
5188
+ [key: string]: string;
5189
+ };
5190
+ /**
5191
+ * When the call session ended
5192
+ * @type {string}
5193
+ * @memberof GetCallRingStateResponse
5194
+ */
5195
+ session_ended_at?: string;
5196
+ /**
5197
+ * The call session this state belongs to, empty when the call has never rung
5198
+ * @type {string}
5199
+ * @memberof GetCallRingStateResponse
5200
+ */
5201
+ session_id: string;
5202
+ /**
5203
+ * When the call session started
5204
+ * @type {string}
5205
+ * @memberof GetCallRingStateResponse
5206
+ */
5207
+ session_started_at?: string;
5208
+ }
5136
5209
  /**
5137
5210
  * Basic response information
5138
5211
  * @export
@@ -6,6 +6,12 @@ export type ClientEventStage = 'JoinInitiated' | 'CoordinatorWS' | 'MediaDeviceP
6
6
  export type MediaPermissionState = 'INITIATED' | 'FAILED' | 'GRANTED' | 'NOT_INITIATED';
7
7
  export type ReportedIceState = 'CONNECTED' | 'FAILED' | 'NOT_CONNECTED';
8
8
  export type JoinReason = 'first-attempt' | 'network-available' | 'migration' | 'full-rejoin';
9
+ /**
10
+ * What triggered an automatic join, reported as `source` on the call's
11
+ * CoordinatorJoin events. `ring-ws` is a ring WebSocket event, `ring-poll-api`
12
+ * is the ring state poller.
13
+ */
14
+ export type JoinSource = 'ring-ws' | 'ring-poll-api';
9
15
  export type ClientEventStandardCode = 'CLIENT_ABORTED' | 'BACKEND_LEAVE' | 'REQUEST_TIMEOUT' | 'NETWORK_ERROR' | 'SFU_ERROR' | 'SFU_GO_AWAY' | 'ICE_CONNECTIVITY_FAILED' | 'DTLS_CONNECTIVITY_FAILED';
10
16
  export type CallReportContext = {
11
17
  callType: string;
@@ -27,6 +33,7 @@ export declare class ClientEventReporter {
27
33
  private callContexts;
28
34
  private joinAttemptIds;
29
35
  private joinReasons;
36
+ private joinSources;
30
37
  private coordinatorPairs;
31
38
  private wsPairs;
32
39
  private peerConnectionPairs;
@@ -50,7 +57,10 @@ export declare class ClientEventReporter {
50
57
  registerCall: (cid: string, ctx: CallReportContext) => void;
51
58
  unregisterCall: (cid: string) => void;
52
59
  startCorrelation: (cid: string, joinReason: JoinReason) => void;
53
- withJoinLifecycle: <T>(cid: string, joinReason: JoinReason, op: () => Promise<T>) => Promise<T>;
60
+ withJoinLifecycle: <T>(cid: string, options: {
61
+ joinReason: JoinReason;
62
+ joinSource?: JoinSource;
63
+ }, op: () => Promise<T>) => Promise<T>;
54
64
  track: <T>(cid: string, stage: "CoordinatorJoin" | "WSJoin", op: () => Promise<T>) => Promise<T>;
55
65
  reportFirstFrame: (cid: string, trackType: TrackType, trackId: string) => void;
56
66
  captureWsError: (cid: string, opts: {
@@ -0,0 +1,46 @@
1
+ import type { Call } from '../Call';
2
+ import { type RingStatePollingOptions } from '../coordinator/connection/types';
3
+ /**
4
+ * Polls the coordinator for the outcome of a ring the current user started.
5
+ *
6
+ * `call.accepted`, `call.rejected` and `call.missed` are delivered best-effort,
7
+ * with no store-and-forward, so a caller that drops one is left on a ringing
8
+ * screen while the callee is already in the call. After a quiet period this
9
+ * reads the ring state until a terminal outcome or the end of the ring window.
10
+ */
11
+ export declare class RingStatePoller {
12
+ private readonly logger;
13
+ private readonly call;
14
+ private readonly startAfterMs;
15
+ private readonly intervalMs;
16
+ private sessionId;
17
+ private deadlineAt;
18
+ private idleTimeoutId;
19
+ private intervalId;
20
+ private stopped;
21
+ private wasPolling;
22
+ private inFlight;
23
+ private unsubscribe;
24
+ constructor(call: Call, options?: RingStatePollingOptions);
25
+ /**
26
+ * Starts polling. Does nothing when the call has no session yet.
27
+ */
28
+ start: () => void;
29
+ /**
30
+ * Pauses polling, keeping the captured session, the deadline and the event
31
+ * subscriptions. Resuming does not extend the ring window.
32
+ */
33
+ pause: () => void;
34
+ /**
35
+ * Resumes a paused poller. Goes straight back to polling if it was already
36
+ * past the quiet period, so a pause does not buy the ring another one.
37
+ */
38
+ resume: () => void;
39
+ /**
40
+ * Stops polling. The poller cannot be restarted.
41
+ */
42
+ stop: () => void;
43
+ private armIdleWindow;
44
+ private runTick;
45
+ private tick;
46
+ }
@@ -0,0 +1,27 @@
1
+ import type { Call } from '../Call';
2
+ /**
3
+ * Drops a call that has been ringing for too long: the caller cancels after
4
+ * `auto_cancel_timeout_ms`, a callee after `incoming_call_timeout_ms`.
5
+ *
6
+ * Either timeout being `0` means the call rings until something else settles it.
7
+ */
8
+ export declare class RingTimeout {
9
+ private readonly call;
10
+ private timeoutId;
11
+ private deadlineAt;
12
+ private stopped;
13
+ constructor(call: Call);
14
+ /**
15
+ * Schedules an auto-drop timeout based on the call settings.
16
+ * Applicable only for ringing calls.
17
+ */
18
+ start: () => void;
19
+ /**
20
+ * Pauses the timeout while preserving its original deadline.
21
+ */
22
+ pause: () => void;
23
+ /**
24
+ * Cancels a scheduled auto-drop timeout. It cannot be armed again.
25
+ */
26
+ stop: () => void;
27
+ }
@@ -0,0 +1,4 @@
1
+ export * from './RingStatePoller';
2
+ export * from './RingTimeout';
3
+ export * from './reconcileRingState';
4
+ export * from './resolveOwnRingOutcome';
@@ -0,0 +1,18 @@
1
+ import type { Call } from '../Call';
2
+ import type { JoinSource } from '../reporting';
3
+ /**
4
+ * Decides what a ringing call should do next, based on the current call state.
5
+ *
6
+ * The `call.accepted`, `call.rejected` and `call.missed` handlers and the ring
7
+ * state poller both run this. They differ only in how the state got there: the
8
+ * handlers rely on `CallState.updateFromEvent`, which runs before them, and the
9
+ * poller applies the polled ring state itself.
10
+ *
11
+ * @param call the call to reconcile.
12
+ * @param joinSource which of the two triggered this run, reported on the
13
+ * caller's join: `ring-ws` for the event handlers, `ring-poll-api` for the
14
+ * poller.
15
+ * @returns whether the ring reached a terminal state. A failed join is not
16
+ * terminal: the caller should keep trying while the ring is open.
17
+ */
18
+ export declare const reconcileRingState: (call: Call, joinSource: JoinSource) => Promise<boolean>;
@@ -0,0 +1,32 @@
1
+ import { CallingState } from '../store';
2
+ import type { CallSessionResponse } from '../gen/coordinator';
3
+ export type OwnRingOutcome = {
4
+ /**
5
+ * Whether the current user accepted or rejected the ring, on this device or
6
+ * on another one. Either way the ring no longer needs to time out.
7
+ */
8
+ settledByMe: boolean;
9
+ /**
10
+ * Set when this device should stop ringing and leave, carrying the reason to
11
+ * report to the native call UI.
12
+ */
13
+ leaveReason?: 'answeredElsewhere' | 'rejected';
14
+ };
15
+ export type OwnRingOutcomeInput = {
16
+ /** The call session to read the accept and reject maps from. */
17
+ session: CallSessionResponse | undefined;
18
+ /** The connected user, or `undefined` when there is none. */
19
+ currentUserId: string | undefined;
20
+ /** The current calling state. */
21
+ callingState: CallingState;
22
+ };
23
+ /**
24
+ * Decides what a ringing call should do about the current user's own accept or
25
+ * reject, which may have happened on another device.
26
+ *
27
+ * Accepting on *this* device also lands in `accepted_by`, so an acceptance only
28
+ * means another device took the call when this one is still ringing.
29
+ *
30
+ * Only meaningful for a ringing call; the caller checks that.
31
+ */
32
+ export declare const resolveOwnRingOutcome: ({ session, currentUserId, callingState, }: OwnRingOutcomeInput) => OwnRingOutcome;
@@ -37,6 +37,24 @@ export declare class Publisher extends BasePeerConnection {
37
37
  * Adds a new transceiver carrying the given track to the peer connection.
38
38
  */
39
39
  private addTransceiver;
40
+ /**
41
+ * Attaches the encryptor, applies sender parameters, then attaches the track.
42
+ *
43
+ * `encrypt` must run before the first await: Chrome short-circuits a sender
44
+ * whose encoded streams aren't created in the task that created it, and the
45
+ * track goes on last so the sender has no media until both steps succeed.
46
+ */
47
+ private initEncryptedSender;
48
+ private setDegradationPreference;
49
+ /**
50
+ * Discards a sender whose E2EE initialization failed.
51
+ *
52
+ * A partially initialized sender may carry no transform, so publish() must
53
+ * not reuse it: retiring it from the cache is what keeps a later publish or
54
+ * ICE restart from putting cleartext on the wire. Cleanup cannot throw, so
55
+ * the initialization failure is what reaches the caller.
56
+ */
57
+ private retireTransceiver;
40
58
  /**
41
59
  * Updates the transceiver with the given track and track type.
42
60
  */
@@ -14,6 +14,16 @@ export declare class TransceiverCache {
14
14
  * Adds a transceiver to the cache.
15
15
  */
16
16
  add: (bundle: PublishBundle) => void;
17
+ /**
18
+ * Retires a failed sender from the reusable cache.
19
+ *
20
+ * A transceiver that never reached an m-section also leaves the creation
21
+ * history: once stopped, it is left out of every later offer, so counting
22
+ * it would shift the `mid` index of every transceiver added after it. One
23
+ * that is already associated keeps its slot, since its m-section survives
24
+ * as a rejected one.
25
+ */
26
+ remove: (transceiver: RTCRtpTransceiver) => void;
17
27
  /**
18
28
  * Gets the transceiver for the given publish option.
19
29
  */
@@ -5,7 +5,16 @@
5
5
  * attaching their own encoded transform in these two methods.
6
6
  */
7
7
  export interface E2EEManager {
8
- /** `trackType` only groups perf stats, keeping a camera and screen share apart. */
8
+ /**
9
+ * Synchronously attach a transform that encrypts the sender's outgoing frames.
10
+ * Must support a sender without a track, and throw if attachment fails.
11
+ *
12
+ * `sender.track` is always null here: the track is attached only after this
13
+ * returns, so implementations must not read it. Use `codec` and `trackType`
14
+ * instead.
15
+ *
16
+ * `trackType` only groups perf stats, keeping a camera and screen share apart.
17
+ */
9
18
  encrypt(sender: RTCRtpSender, codec?: string, trackType?: string): void;
10
19
  /** `trackType` only groups perf stats, keeping a peer's audio and video apart. */
11
20
  decrypt(receiver: RTCRtpReceiver, userId: string, trackType?: string): void;
@@ -29,3 +29,9 @@ export declare const getSdkSignature: (clientDetails: ClientDetails) => {
29
29
  };
30
30
  export declare const getSdkName: (sdk: Sdk | undefined) => "stream-react" | "stream-react-native" | "stream-js";
31
31
  export declare const getSdkVersion: (sdk: Sdk | undefined) => string;
32
+ /**
33
+ * Returns the identifier the SFU receives in the `X-Stream-Client` header,
34
+ * e.g. `stream-video-react-v1.43.1`. Matches the format the coordinator
35
+ * already gets through the client's user agent.
36
+ */
37
+ export declare const getStreamClientId: (sdk: Sdk | undefined) => string;
@@ -3,7 +3,7 @@ import type { Patch } from './rxUtils';
3
3
  import { CallingState } from './CallingState';
4
4
  import { type ClosedCaptionsSettings, type StreamVideoParticipant, type StreamVideoParticipantPatch, type StreamVideoParticipantPatches, type SubscriptionChanges, VideoTrackType } from '../types';
5
5
  import { CallStatsReport } from '../stats';
6
- import { CallClosedCaption, CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, VideoEvent } from '../gen/coordinator';
6
+ import { CallClosedCaption, CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, GetCallRingStateResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, VideoEvent } from '../gen/coordinator';
7
7
  import { ReconnectDetails } from '../gen/video/sfu/event/events';
8
8
  import { CallGrants, CallState as SfuCallState, Pin, TrackType } from '../gen/video/sfu/models/models';
9
9
  import { Comparator } from '../sorting';
@@ -615,6 +615,15 @@ export declare class CallState {
615
615
  * @param call the call response from the server.
616
616
  */
617
617
  updateFromCallResponse: (call: CallResponse) => void;
618
+ /**
619
+ * Merges a polled ring state into the current session. Only the ring fields
620
+ * are touched: the endpoint does not return the session roster.
621
+ *
622
+ * @internal
623
+ *
624
+ * @param ringState the ring state as returned by the coordinator.
625
+ */
626
+ updateFromRingState: (ringState: GetCallRingStateResponse) => void;
618
627
  /**
619
628
  * Updates the call state with the data received from the SFU server.
620
629
  *
@@ -375,14 +375,43 @@ type StreamRNVideoSDKEndCallReason =
375
375
  /** Unknown or unspecified disconnect reason. */
376
376
  | 'unknown';
377
377
  type StreamRNVideoSDKCallingX = {
378
- joinCall: (call: Call, activeCalls: Call[]) => Promise<void>;
378
+ /**
379
+ * @param isCancelled - polled around the bridge's waits: registration is
380
+ * skipped, or undone, when it returns true. Supplied by the join attempt
381
+ * because the call's own state cannot distinguish a fresh join starting
382
+ * from `LEFT` from an abandoned one that reached `LEFT` while waiting.
383
+ */
384
+ joinCall: (call: Call, activeCalls: Call[], isCancelled?: () => boolean) => Promise<void>;
379
385
  endCall: (call: Call, reason?: StreamRNVideoSDKEndCallReason) => Promise<void>;
380
386
  registerOutgoingCall: (call: Call) => Promise<void>;
381
387
  wireAudioEngineSubscription: () => void;
382
388
  unwireAudioEngineSubscription: () => void;
383
389
  };
390
+ /**
391
+ * React Native's preparation and cleanup for a ringing call's join.
392
+ *
393
+ * A ringing call is joined by the SDK rather than by app code, so there is no
394
+ * point at which the app holds the call and can still set it up. These are that
395
+ * point, and the matching release.
396
+ */
397
+ type StreamRNVideoSDKRingingCallLifecycle = {
398
+ /**
399
+ * Runs the app's pre-join setup and resolves once it is done, bounded by a
400
+ * deadline. Rejecting fails the join closed rather than joining with nothing
401
+ * installed.
402
+ */
403
+ beforeJoin: (call: Call) => Promise<void>;
404
+ /**
405
+ * The join failed terminally. Ends the ringing flow and releases what the
406
+ * setup installed. Never rejects, so the original join error survives.
407
+ */
408
+ onJoinFailed: (call: Call) => Promise<void>;
409
+ /** The call has ended; release whatever the pre-join hook installed. */
410
+ onLeave: (call: Call) => void;
411
+ };
384
412
  export type StreamRNVideoSDKGlobals = {
385
413
  callingX: StreamRNVideoSDKCallingX;
414
+ ringingCallLifecycle: StreamRNVideoSDKRingingCallLifecycle;
386
415
  callManager: {
387
416
  /**
388
417
  * Sets up the in call manager.
package/index.ts CHANGED
@@ -26,6 +26,7 @@ export * from './src/helpers/sound-detector';
26
26
  export * from './src/helpers/loopback';
27
27
  export * from './src/helpers/MediaStreamRecorder';
28
28
  export * from './src/helpers/participantUtils';
29
+ export * from './src/helpers/TypedEventEmitter';
29
30
  export * from './src/rtc/e2ee/E2EEManager';
30
31
  export * from './src/rtc/e2ee/EncryptionManager';
31
32
  export * as Browsers from './src/helpers/browsers';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.59.1",
3
+ "version": "1.61.0",
4
4
  "main": "dist/index.cjs.js",
5
5
  "module": "dist/index.es.js",
6
6
  "browser": "dist/index.browser.es.js",