@stream-io/video-client 0.1.3 → 0.1.5

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 (34) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.browser.es.js +401 -106
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +400 -105
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +401 -106
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +1 -1
  9. package/dist/src/StreamSfuClient.d.ts +2 -1
  10. package/dist/src/StreamVideoClient.d.ts +3 -4
  11. package/dist/src/coordinator/connection/client.d.ts +0 -6
  12. package/dist/src/gen/video/sfu/event/events.d.ts +45 -2
  13. package/dist/src/gen/video/sfu/models/models.d.ts +12 -0
  14. package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +9 -1
  15. package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +42 -0
  16. package/dist/src/rtc/Publisher.d.ts +10 -2
  17. package/dist/src/rtc/Subscriber.d.ts +8 -3
  18. package/dist/version.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/Call.ts +18 -9
  21. package/src/StreamSfuClient.ts +16 -5
  22. package/src/StreamVideoClient.ts +32 -30
  23. package/src/coordinator/connection/client.ts +0 -25
  24. package/src/gen/google/protobuf/struct.ts +1 -2
  25. package/src/gen/google/protobuf/timestamp.ts +1 -1
  26. package/src/gen/video/sfu/event/events.ts +165 -5
  27. package/src/gen/video/sfu/models/models.ts +13 -1
  28. package/src/gen/video/sfu/signal_rpc/signal.client.ts +27 -1
  29. package/src/gen/video/sfu/signal_rpc/signal.ts +194 -1
  30. package/src/rtc/Dispatcher.ts +1 -0
  31. package/src/rtc/Publisher.ts +69 -34
  32. package/src/rtc/Subscriber.ts +74 -7
  33. package/src/rtc/__tests__/Publisher.test.ts +82 -2
  34. package/src/rtc/__tests__/Subscriber.test.ts +84 -1
@@ -164,7 +164,6 @@ export declare class Call {
164
164
  */
165
165
  join: (data?: JoinCallData) => Promise<void>;
166
166
  private waitForJoinResponse;
167
- private assertCallJoined;
168
167
  /**
169
168
  * Starts publishing the given video stream to the call.
170
169
  * The stream will be stopped if the user changes an input device, or if the user leaves the call.
@@ -276,6 +275,7 @@ export declare class Call {
276
275
  * @returns
277
276
  */
278
277
  updatePublishQuality: (enabledRids: string[]) => Promise<void | undefined>;
278
+ private assertCallJoined;
279
279
  /**
280
280
  * Sends a reaction to the other call participants.
281
281
  *
@@ -2,7 +2,7 @@ import type { WebSocket } from 'ws';
2
2
  import type { FinishedUnaryCall } from '@protobuf-ts/runtime-rpc';
3
3
  import { Dispatcher, IceTrickleBuffer } from './rtc';
4
4
  import { JoinRequest, SfuRequest } from './gen/video/sfu/event/events';
5
- import { SendAnswerRequest, SetPublisherRequest, TrackSubscriptionDetails, UpdateMuteStatesRequest } from './gen/video/sfu/signal_rpc/signal';
5
+ import { ICERestartRequest, SendAnswerRequest, SetPublisherRequest, TrackSubscriptionDetails, UpdateMuteStatesRequest } from './gen/video/sfu/signal_rpc/signal';
6
6
  import { ICETrickle, TrackType } from './gen/video/sfu/models/models';
7
7
  import { SFUResponse } from './gen/coordinator';
8
8
  export type StreamSfuClientConstructor = {
@@ -84,6 +84,7 @@ export declare class StreamSfuClient {
84
84
  setPublisher: (data: Omit<SetPublisherRequest, 'sessionId'>) => Promise<FinishedUnaryCall<SetPublisherRequest, import("./gen/video/sfu/signal_rpc/signal").SetPublisherResponse>>;
85
85
  sendAnswer: (data: Omit<SendAnswerRequest, 'sessionId'>) => Promise<FinishedUnaryCall<SendAnswerRequest, import("./gen/video/sfu/signal_rpc/signal").SendAnswerResponse>>;
86
86
  iceTrickle: (data: Omit<ICETrickle, 'sessionId'>) => Promise<FinishedUnaryCall<ICETrickle, import("./gen/video/sfu/signal_rpc/signal").ICETrickleResponse>>;
87
+ iceRestart: (data: Omit<ICERestartRequest, 'sessionId'>) => Promise<FinishedUnaryCall<ICERestartRequest, import("./gen/video/sfu/signal_rpc/signal").ICERestartResponse>>;
87
88
  updateMuteState: (trackType: TrackType, muted: boolean) => Promise<FinishedUnaryCall<UpdateMuteStatesRequest, import("./gen/video/sfu/signal_rpc/signal").UpdateMuteStatesResponse>>;
88
89
  updateMuteStates: (data: Omit<UpdateMuteStatesRequest, 'sessionId'>) => Promise<FinishedUnaryCall<UpdateMuteStatesRequest, import("./gen/video/sfu/signal_rpc/signal").UpdateMuteStatesResponse>>;
89
90
  join: (data: Omit<JoinRequest, 'sessionId' | 'token'>) => Promise<void>;
@@ -2,7 +2,7 @@ import { Call } from './Call';
2
2
  import { StreamClient } from './coordinator/connection/client';
3
3
  import { StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore } from './store';
4
4
  import type { ConnectedEvent, CreateDeviceRequest, CreateGuestRequest, CreateGuestResponse, GetEdgesResponse, ListDevicesResponse, QueryCallsRequest } from './gen/coordinator';
5
- import type { EventHandler, EventTypes, Logger, StreamClientOptions, TokenOrProvider, TokenProvider, User, UserWithId } from './coordinator/connection/types';
5
+ import type { EventHandler, EventTypes, Logger, LogLevel, StreamClientOptions, TokenOrProvider, TokenProvider, User, UserWithId } from './coordinator/connection/types';
6
6
  /**
7
7
  * A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
8
8
  */
@@ -11,8 +11,7 @@ export declare class StreamVideoClient {
11
11
  * A reactive store that exposes all the state variables in a reactive manner - you can subscribe to changes of the different state variables. Our library is built in a way that all state changes are exposed in this store, so all UI changes in your application should be handled by subscribing to these variables.
12
12
  */
13
13
  readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
14
- readonly user?: User;
15
- readonly token?: TokenOrProvider;
14
+ readonly logLevel: LogLevel;
16
15
  readonly logger: Logger;
17
16
  protected readonly writeableStateStore: StreamVideoWriteableStateStore;
18
17
  streamClient: StreamClient;
@@ -38,7 +37,7 @@ export declare class StreamVideoClient {
38
37
  * @param user the user to connect.
39
38
  * @param token a token or a function that returns a token.
40
39
  */
41
- connectUser(user?: User, token?: TokenOrProvider): Promise<void | ConnectedEvent>;
40
+ connectUser(user: User, token?: TokenOrProvider): Promise<void | ConnectedEvent>;
42
41
  /**
43
42
  * Disconnects the currently connected user from the client.
44
43
  *
@@ -39,8 +39,6 @@ export declare class StreamClient {
39
39
  rejectConnectionId?: Function;
40
40
  connectionIdPromise?: Promise<string | undefined>;
41
41
  private nextRequestAbortController;
42
- private waitForConnectPromise?;
43
- private resolveConnectPromise?;
44
42
  /**
45
43
  * Initialize a client.
46
44
  *
@@ -60,10 +58,6 @@ export declare class StreamClient {
60
58
  getLocationHint: (hintUrl?: string, timeout?: number) => Promise<string>;
61
59
  _getConnectionID: () => string | undefined;
62
60
  _hasConnectionID: () => boolean;
63
- /**
64
- * This will start a promise to hold API calls until `connectUser` is called, useful when user is set in `StreamVideoClient constructor`
65
- */
66
- startWaitingForConnection: () => void;
67
61
  /**
68
62
  * connectUser - Set the current user and open a WebSocket connection
69
63
  *
@@ -160,6 +160,14 @@ export interface SfuEvent {
160
160
  * @generated from protobuf field: stream.video.sfu.event.GoAway go_away = 20;
161
161
  */
162
162
  goAway: GoAway;
163
+ } | {
164
+ oneofKind: 'iceRestart';
165
+ /**
166
+ * ICERestart tells the client to perform ICE restart.
167
+ *
168
+ * @generated from protobuf field: stream.video.sfu.event.ICERestart ice_restart = 21;
169
+ */
170
+ iceRestart: ICERestart;
163
171
  } | {
164
172
  oneofKind: undefined;
165
173
  };
@@ -186,6 +194,15 @@ export interface ICETrickle {
186
194
  */
187
195
  iceCandidate: string;
188
196
  }
197
+ /**
198
+ * @generated from protobuf message stream.video.sfu.event.ICERestart
199
+ */
200
+ export interface ICERestart {
201
+ /**
202
+ * @generated from protobuf field: stream.video.sfu.models.PeerType peer_type = 1;
203
+ */
204
+ peerType: PeerType;
205
+ }
189
206
  /**
190
207
  * SfuRequest is a message that is sent from the client to the SFU.
191
208
  *
@@ -312,6 +329,20 @@ export interface JoinRequest {
312
329
  * @generated from protobuf field: stream.video.sfu.event.Migration migration = 5;
313
330
  */
314
331
  migration?: Migration;
332
+ /**
333
+ * Fast reconnect flag explicitly indicates that if the participant session
334
+ * and the associated state is still present in the SFU, the client is ready
335
+ * to restore the PeerConnection with an ICE restart. If the SFU replies with
336
+ * "reconnected: true" in its JoinResponse, then it is safe to perform an ICE
337
+ * restart or else the existing PeerConnections must be cleaned up.
338
+ *
339
+ * For the SFU, fast_reconnect:false indicates that even if it has the state
340
+ * cached, the client state is not in sync and hence it must be cleaned up before
341
+ * proceeding further.
342
+ *
343
+ * @generated from protobuf field: bool fast_reconnect = 6;
344
+ */
345
+ fastReconnect: boolean;
315
346
  }
316
347
  /**
317
348
  * @generated from protobuf message stream.video.sfu.event.Migration
@@ -335,11 +366,13 @@ export interface Migration {
335
366
  */
336
367
  export interface JoinResponse {
337
368
  /**
338
- * TODO: include full list of participants with track and audio info
339
- *
340
369
  * @generated from protobuf field: stream.video.sfu.models.CallState call_state = 1;
341
370
  */
342
371
  callState?: CallState;
372
+ /**
373
+ * @generated from protobuf field: bool reconnected = 2;
374
+ */
375
+ reconnected: boolean;
343
376
  }
344
377
  /**
345
378
  * ParticipantJoined is fired when a user joins a call
@@ -666,6 +699,16 @@ declare class ICETrickle$Type extends MessageType<ICETrickle> {
666
699
  * @generated MessageType for protobuf message stream.video.sfu.event.ICETrickle
667
700
  */
668
701
  export declare const ICETrickle: ICETrickle$Type;
702
+ declare class ICERestart$Type extends MessageType<ICERestart> {
703
+ constructor();
704
+ create(value?: PartialMessage<ICERestart>): ICERestart;
705
+ internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ICERestart): ICERestart;
706
+ internalBinaryWrite(message: ICERestart, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
707
+ }
708
+ /**
709
+ * @generated MessageType for protobuf message stream.video.sfu.event.ICERestart
710
+ */
711
+ export declare const ICERestart: ICERestart$Type;
669
712
  declare class SfuRequest$Type extends MessageType<SfuRequest> {
670
713
  constructor();
671
714
  create(value?: PartialMessage<SfuRequest>): SfuRequest;
@@ -534,6 +534,18 @@ export declare enum ErrorCode {
534
534
  * @generated from protobuf enum value: ERROR_CODE_PARTICIPANT_MIGRATION_FAILED = 202;
535
535
  */
536
536
  PARTICIPANT_MIGRATION_FAILED = 202,
537
+ /**
538
+ * @generated from protobuf enum value: ERROR_CODE_PARTICIPANT_MIGRATING = 203;
539
+ */
540
+ PARTICIPANT_MIGRATING = 203,
541
+ /**
542
+ * @generated from protobuf enum value: ERROR_CODE_PARTICIPANT_RECONNECT_FAILED = 204;
543
+ */
544
+ PARTICIPANT_RECONNECT_FAILED = 204,
545
+ /**
546
+ * @generated from protobuf enum value: ERROR_CODE_PARTICIPANT_MEDIA_TRANSPORT_FAILURE = 205;
547
+ */
548
+ PARTICIPANT_MEDIA_TRANSPORT_FAILURE = 205,
537
549
  /**
538
550
  * @generated from protobuf enum value: ERROR_CODE_CALL_NOT_FOUND = 300;
539
551
  */
@@ -1,5 +1,5 @@
1
1
  import type { RpcOptions, RpcTransport, ServiceInfo, UnaryCall } from '@protobuf-ts/runtime-rpc';
2
- import type { ICETrickleResponse, SendAnswerRequest, SendAnswerResponse, SetPublisherRequest, SetPublisherResponse, UpdateMuteStatesRequest, UpdateMuteStatesResponse, UpdateSubscriptionsRequest, UpdateSubscriptionsResponse } from './signal';
2
+ import type { ICERestartRequest, ICERestartResponse, ICETrickleResponse, SendAnswerRequest, SendAnswerResponse, SetPublisherRequest, SetPublisherResponse, UpdateMuteStatesRequest, UpdateMuteStatesResponse, UpdateSubscriptionsRequest, UpdateSubscriptionsResponse } from './signal';
3
3
  import type { ICETrickle } from '../models/models';
4
4
  /**
5
5
  * @generated from protobuf service stream.video.sfu.signal.SignalServer
@@ -34,6 +34,10 @@ export interface ISignalServerClient {
34
34
  * @generated from protobuf rpc: UpdateMuteStates(stream.video.sfu.signal.UpdateMuteStatesRequest) returns (stream.video.sfu.signal.UpdateMuteStatesResponse);
35
35
  */
36
36
  updateMuteStates(input: UpdateMuteStatesRequest, options?: RpcOptions): UnaryCall<UpdateMuteStatesRequest, UpdateMuteStatesResponse>;
37
+ /**
38
+ * @generated from protobuf rpc: IceRestart(stream.video.sfu.signal.ICERestartRequest) returns (stream.video.sfu.signal.ICERestartResponse);
39
+ */
40
+ iceRestart(input: ICERestartRequest, options?: RpcOptions): UnaryCall<ICERestartRequest, ICERestartResponse>;
37
41
  }
38
42
  /**
39
43
  * @generated from protobuf service stream.video.sfu.signal.SignalServer
@@ -75,4 +79,8 @@ export declare class SignalServerClient implements ISignalServerClient, ServiceI
75
79
  * @generated from protobuf rpc: UpdateMuteStates(stream.video.sfu.signal.UpdateMuteStatesRequest) returns (stream.video.sfu.signal.UpdateMuteStatesResponse);
76
80
  */
77
81
  updateMuteStates(input: UpdateMuteStatesRequest, options?: RpcOptions): UnaryCall<UpdateMuteStatesRequest, UpdateMuteStatesResponse>;
82
+ /**
83
+ * @generated from protobuf rpc: IceRestart(stream.video.sfu.signal.ICERestartRequest) returns (stream.video.sfu.signal.ICERestartResponse);
84
+ */
85
+ iceRestart(input: ICERestartRequest, options?: RpcOptions): UnaryCall<ICERestartRequest, ICERestartResponse>;
78
86
  }
@@ -2,6 +2,28 @@ import { Error, PeerType, TrackInfo, TrackType, VideoDimension } from '../models
2
2
  import { ServiceType } from '@protobuf-ts/runtime-rpc';
3
3
  import type { BinaryReadOptions, BinaryWriteOptions, IBinaryReader, IBinaryWriter, PartialMessage } from '@protobuf-ts/runtime';
4
4
  import { MessageType } from '@protobuf-ts/runtime';
5
+ /**
6
+ * @generated from protobuf message stream.video.sfu.signal.ICERestartRequest
7
+ */
8
+ export interface ICERestartRequest {
9
+ /**
10
+ * @generated from protobuf field: string session_id = 1;
11
+ */
12
+ sessionId: string;
13
+ /**
14
+ * @generated from protobuf field: stream.video.sfu.models.PeerType peer_type = 2;
15
+ */
16
+ peerType: PeerType;
17
+ }
18
+ /**
19
+ * @generated from protobuf message stream.video.sfu.signal.ICERestartResponse
20
+ */
21
+ export interface ICERestartResponse {
22
+ /**
23
+ * @generated from protobuf field: stream.video.sfu.models.Error error = 1;
24
+ */
25
+ error?: Error;
26
+ }
5
27
  /**
6
28
  * @generated from protobuf message stream.video.sfu.signal.UpdateMuteStatesRequest
7
29
  */
@@ -175,6 +197,26 @@ export interface SetPublisherResponse {
175
197
  */
176
198
  error?: Error;
177
199
  }
200
+ declare class ICERestartRequest$Type extends MessageType<ICERestartRequest> {
201
+ constructor();
202
+ create(value?: PartialMessage<ICERestartRequest>): ICERestartRequest;
203
+ internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ICERestartRequest): ICERestartRequest;
204
+ internalBinaryWrite(message: ICERestartRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
205
+ }
206
+ /**
207
+ * @generated MessageType for protobuf message stream.video.sfu.signal.ICERestartRequest
208
+ */
209
+ export declare const ICERestartRequest: ICERestartRequest$Type;
210
+ declare class ICERestartResponse$Type extends MessageType<ICERestartResponse> {
211
+ constructor();
212
+ create(value?: PartialMessage<ICERestartResponse>): ICERestartResponse;
213
+ internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ICERestartResponse): ICERestartResponse;
214
+ internalBinaryWrite(message: ICERestartResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
215
+ }
216
+ /**
217
+ * @generated MessageType for protobuf message stream.video.sfu.signal.ICERestartResponse
218
+ */
219
+ export declare const ICERestartResponse: ICERestartResponse$Type;
178
220
  declare class UpdateMuteStatesRequest$Type extends MessageType<UpdateMuteStatesRequest> {
179
221
  constructor();
180
222
  create(value?: PartialMessage<UpdateMuteStatesRequest>): UpdateMuteStatesRequest;
@@ -2,13 +2,16 @@ import { StreamSfuClient } from '../StreamSfuClient';
2
2
  import { TrackInfo, TrackType } from '../gen/video/sfu/models/models';
3
3
  import { CallState } from '../store';
4
4
  import { PublishOptions } from '../types';
5
+ import { Dispatcher } from './Dispatcher';
5
6
  export type PublisherOpts = {
6
7
  sfuClient: StreamSfuClient;
7
8
  state: CallState;
9
+ dispatcher: Dispatcher;
8
10
  connectionConfig?: RTCConfiguration;
9
11
  isDtxEnabled: boolean;
10
12
  isRedEnabled: boolean;
11
13
  preferredVideoCodec?: string;
14
+ iceRestartDelay?: number;
12
15
  };
13
16
  /**
14
17
  * The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
@@ -17,6 +20,7 @@ export type PublisherOpts = {
17
20
  export declare class Publisher {
18
21
  private pc;
19
22
  private readonly state;
23
+ private readonly dispatcher;
20
24
  private readonly transceiverRegistry;
21
25
  /**
22
26
  * An array maintaining the order how transceivers were added to the peer connection.
@@ -31,7 +35,9 @@ export declare class Publisher {
31
35
  private readonly isDtxEnabled;
32
36
  private readonly isRedEnabled;
33
37
  private readonly preferredVideoCodec?;
34
- private logger;
38
+ private readonly unsubscribeOnIceRestart;
39
+ private readonly iceRestartDelay;
40
+ private isIceRestarting;
35
41
  /**
36
42
  * The SFU client instance to use for publishing and signaling.
37
43
  */
@@ -42,11 +48,13 @@ export declare class Publisher {
42
48
  * @param connectionConfig the connection configuration to use.
43
49
  * @param sfuClient the SFU client to use.
44
50
  * @param state the call state to use.
51
+ * @param dispatcher the dispatcher to use.
45
52
  * @param isDtxEnabled whether DTX is enabled.
46
53
  * @param isRedEnabled whether RED is enabled.
47
54
  * @param preferredVideoCodec the preferred video codec.
55
+ * @param iceRestartDelay the delay in milliseconds to wait before restarting ICE once connection goes to `disconnected` state.
48
56
  */
49
- constructor({ connectionConfig, sfuClient, state, isDtxEnabled, isRedEnabled, preferredVideoCodec, }: PublisherOpts);
57
+ constructor({ connectionConfig, sfuClient, dispatcher, state, isDtxEnabled, isRedEnabled, preferredVideoCodec, iceRestartDelay, }: PublisherOpts);
50
58
  private createPeerConnection;
51
59
  /**
52
60
  * Closes the publisher PeerConnection and cleans up the resources.
@@ -6,6 +6,7 @@ export type SubscriberOpts = {
6
6
  dispatcher: Dispatcher;
7
7
  state: CallState;
8
8
  connectionConfig?: RTCConfiguration;
9
+ iceRestartDelay?: number;
9
10
  };
10
11
  /**
11
12
  * A wrapper around the `RTCPeerConnection` that handles the incoming
@@ -13,10 +14,13 @@ export type SubscriberOpts = {
13
14
  */
14
15
  export declare class Subscriber {
15
16
  private pc;
16
- private readonly unregisterOnSubscriberOffer;
17
17
  private sfuClient;
18
18
  private dispatcher;
19
19
  private state;
20
+ private readonly unregisterOnSubscriberOffer;
21
+ private readonly unregisterOnIceRestart;
22
+ private readonly iceRestartDelay;
23
+ private isIceRestarting;
20
24
  /**
21
25
  * Constructs a new `Subscriber` instance.
22
26
  *
@@ -24,8 +28,9 @@ export declare class Subscriber {
24
28
  * @param dispatcher the dispatcher to use.
25
29
  * @param state the state of the call.
26
30
  * @param connectionConfig the connection configuration to use.
31
+ * @param iceRestartDelay the delay in milliseconds to wait before restarting ICE when connection goes to `disconnected` state.
27
32
  */
28
- constructor({ sfuClient, dispatcher, state, connectionConfig, }: SubscriberOpts);
33
+ constructor({ sfuClient, dispatcher, state, connectionConfig, iceRestartDelay, }: SubscriberOpts);
29
34
  /**
30
35
  * Creates a new `RTCPeerConnection` instance with the given configuration.
31
36
  *
@@ -52,7 +57,7 @@ export declare class Subscriber {
52
57
  /**
53
58
  * Restarts the ICE connection and renegotiates with the SFU.
54
59
  */
55
- restartIce: () => void;
60
+ restartIce: () => Promise<void>;
56
61
  private handleOnTrack;
57
62
  private onIceCandidate;
58
63
  private negotiate;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "0.1.3";
1
+ export declare const version = "0.1.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
package/src/Call.ts CHANGED
@@ -72,6 +72,7 @@ import {
72
72
  import {
73
73
  BehaviorSubject,
74
74
  debounce,
75
+ filter,
75
76
  map,
76
77
  pairwise,
77
78
  Subject,
@@ -371,12 +372,15 @@ export class Call {
371
372
  * Leave the call and stop the media streams that were published by the call.
372
373
  */
373
374
  leave = async ({ reject = false }: CallLeaveOptions = {}) => {
374
- // TODO: handle case when leave is called during JOINING
375
375
  const callingState = this.state.callingState;
376
376
  if (callingState === CallingState.LEFT) {
377
377
  throw new Error('Cannot leave call that has already been left.');
378
378
  }
379
379
 
380
+ if (callingState === CallingState.JOINING) {
381
+ await this.assertCallJoined();
382
+ }
383
+
380
384
  if (this.ringing) {
381
385
  // I'm the one who started the call, so I should cancel it.
382
386
  const hasOtherParticipants = this.state.remoteParticipants.length > 0;
@@ -804,6 +808,7 @@ export class Call {
804
808
  if (!this.publisher) {
805
809
  this.publisher = new Publisher({
806
810
  sfuClient,
811
+ dispatcher: this.dispatcher,
807
812
  state: this.state,
808
813
  connectionConfig,
809
814
  isDtxEnabled,
@@ -848,6 +853,7 @@ export class Call {
848
853
  subscriberSdp: sdp || '',
849
854
  clientDetails: getClientDetails(),
850
855
  migration,
856
+ fastReconnect: false,
851
857
  });
852
858
  });
853
859
 
@@ -928,14 +934,6 @@ export class Call {
928
934
  });
929
935
  };
930
936
 
931
- private assertCallJoined = () => {
932
- return new Promise<void>((resolve) => {
933
- this.state.callingState$
934
- .pipe(takeWhile((state) => state !== CallingState.JOINED, true))
935
- .subscribe(() => resolve());
936
- });
937
- };
938
-
939
937
  /**
940
938
  * Starts publishing the given video stream to the call.
941
939
  * The stream will be stopped if the user changes an input device, or if the user leaves the call.
@@ -1221,6 +1219,17 @@ export class Call {
1221
1219
  return this.publisher?.updateVideoPublishQuality(enabledRids);
1222
1220
  };
1223
1221
 
1222
+ private assertCallJoined = () => {
1223
+ return new Promise<void>((resolve) => {
1224
+ this.state.callingState$
1225
+ .pipe(
1226
+ takeWhile((state) => state !== CallingState.JOINED, true),
1227
+ filter((s) => s === CallingState.JOINED),
1228
+ )
1229
+ .subscribe(() => resolve());
1230
+ });
1231
+ };
1232
+
1224
1233
  /**
1225
1234
  * Sends a reaction to the other call participants.
1226
1235
  *
@@ -16,6 +16,7 @@ import {
16
16
  } from './rtc';
17
17
  import { JoinRequest, SfuRequest } from './gen/video/sfu/event/events';
18
18
  import {
19
+ ICERestartRequest,
19
20
  SendAnswerRequest,
20
21
  SetPublisherRequest,
21
22
  TrackSubscriptionDetails,
@@ -131,15 +132,14 @@ export class StreamSfuClient {
131
132
  this.edgeName = sfuServer.edge_name;
132
133
  this.token = token;
133
134
  this.logger = getLogger(['sfu-client']);
134
- const logger = this.logger;
135
135
  const logInterceptor: RpcInterceptor = {
136
- interceptUnary(
136
+ interceptUnary: (
137
137
  next: NextUnaryFn,
138
138
  method: MethodInfo,
139
139
  input: object,
140
140
  options: RpcOptions,
141
- ): UnaryCall {
142
- logger('trace', `Calling SFU RPC method ${method.name}`, {
141
+ ): UnaryCall => {
142
+ this.logger('trace', `Calling SFU RPC method ${method.name}`, {
143
143
  input,
144
144
  options,
145
145
  });
@@ -190,6 +190,7 @@ export class StreamSfuClient {
190
190
  code: number = 1000,
191
191
  reason: string = 'Requested signal connection close',
192
192
  ) => {
193
+ this.logger('debug', 'Closing SFU WS connection', code, reason);
193
194
  this.signalWs.close(code, reason);
194
195
 
195
196
  this.unsubscribeIceTrickle();
@@ -241,6 +242,17 @@ export class StreamSfuClient {
241
242
  );
242
243
  };
243
244
 
245
+ iceRestart = async (data: Omit<ICERestartRequest, 'sessionId'>) => {
246
+ return retryable(
247
+ () =>
248
+ this.rpc.iceRestart({
249
+ ...data,
250
+ sessionId: this.sessionId,
251
+ }),
252
+ this.logger,
253
+ );
254
+ };
255
+
244
256
  updateMuteState = async (trackType: TrackType, muted: boolean) => {
245
257
  return this.updateMuteStates({
246
258
  muteStates: [
@@ -319,7 +331,6 @@ export class StreamSfuClient {
319
331
  new Date().getTime() - this.lastMessageTimestamp.getTime();
320
332
 
321
333
  if (timeSinceLastMessage > this.unhealthyTimeoutInMs) {
322
- this.logger('debug', 'SFU connection unhealthy, closing');
323
334
  this.close(
324
335
  4001,
325
336
  `SFU connection unhealthy. Didn't receive any healthcheck messages for ${this.unhealthyTimeoutInMs}ms`,
@@ -38,8 +38,7 @@ export class StreamVideoClient {
38
38
  * A reactive store that exposes all the state variables in a reactive manner - you can subscribe to changes of the different state variables. Our library is built in a way that all state changes are exposed in this store, so all UI changes in your application should be handled by subscribing to these variables.
39
39
  */
40
40
  readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
41
- readonly user?: User;
42
- readonly token?: TokenOrProvider;
41
+ readonly logLevel: LogLevel = 'warn';
43
42
  readonly logger: Logger;
44
43
 
45
44
  protected readonly writeableStateStore: StreamVideoWriteableStateStore;
@@ -109,17 +108,20 @@ export class StreamVideoClient {
109
108
  }.${sdkInfo.minor}.${sdkInfo.patch}`,
110
109
  );
111
110
  }
112
- this.user = apiKeyOrArgs.user;
113
- this.token = apiKeyOrArgs.token || apiKeyOrArgs.tokenProvider;
114
- if (this.user) {
115
- this.streamClient.startWaitingForConnection();
116
- }
117
111
  }
118
112
 
119
113
  this.writeableStateStore = new StreamVideoWriteableStateStore();
120
114
  this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(
121
115
  this.writeableStateStore,
122
116
  );
117
+
118
+ if (typeof apiKeyOrArgs !== 'string') {
119
+ const user = apiKeyOrArgs.user;
120
+ const token = apiKeyOrArgs.token || apiKeyOrArgs.tokenProvider;
121
+ if (user) {
122
+ this.connectUser(user, token);
123
+ }
124
+ }
123
125
  }
124
126
 
125
127
  /**
@@ -131,30 +133,30 @@ export class StreamVideoClient {
131
133
  * @param token a token or a function that returns a token.
132
134
  */
133
135
  async connectUser(
134
- user?: User,
136
+ user: User,
135
137
  token?: TokenOrProvider,
136
138
  ): Promise<void | ConnectedEvent> {
137
- const userToConnect = user || this.user;
138
- const tokenToUse = token || this.token;
139
- if (!userToConnect) {
140
- throw new Error('Connect user is called without user');
141
- }
142
- if (userToConnect.type === 'anonymous') {
143
- userToConnect.id = '!anon';
144
- return this.connectAnonymousUser(userToConnect as UserWithId, tokenToUse);
139
+ if (user.type === 'anonymous') {
140
+ user.id = '!anon';
141
+ return this.connectAnonymousUser(user as UserWithId, token);
145
142
  }
146
- if (userToConnect.type === 'guest') {
147
- const response = await this.createGuestUser({
148
- user: {
149
- ...userToConnect,
150
- role: 'guest',
151
- },
152
- });
153
- return this.connectUser(response.user, response.access_token);
154
- }
155
- const connectUser = () => {
156
- return this.streamClient.connectUser(userToConnect, tokenToUse);
143
+ let connectUser = () => {
144
+ return this.streamClient.connectUser(user, token);
157
145
  };
146
+ if (user.type === 'guest') {
147
+ connectUser = async () => {
148
+ const response = await this.createGuestUser({
149
+ user: {
150
+ ...user,
151
+ role: 'guest',
152
+ },
153
+ });
154
+ return this.streamClient.connectUser(
155
+ response.user,
156
+ response.access_token,
157
+ );
158
+ };
159
+ }
158
160
  this.connectionPromise = this.disconnectionPromise
159
161
  ? this.disconnectionPromise.then(() => connectUser())
160
162
  : connectUser();
@@ -199,7 +201,7 @@ export class StreamVideoClient {
199
201
  this.on('call.created', (event) => {
200
202
  if (event.type !== 'call.created') return;
201
203
  const { call, members } = event;
202
- if (userToConnect.id === call.created_by.id) {
204
+ if (user.id === call.created_by.id) {
203
205
  this.logger(
204
206
  'warn',
205
207
  'Received `call.created` sent by the current user',
@@ -225,7 +227,7 @@ export class StreamVideoClient {
225
227
  this.on('call.ring', async (event) => {
226
228
  if (event.type !== 'call.ring') return;
227
229
  const { call, members } = event;
228
- if (userToConnect.id === call.created_by.id) {
230
+ if (user.id === call.created_by.id) {
229
231
  this.logger(
230
232
  'debug',
231
233
  'Received `call.ring` sent by the current user so ignoring the event',
@@ -267,7 +269,7 @@ export class StreamVideoClient {
267
269
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
268
270
  */
269
271
  disconnectUser = async (timeout?: number) => {
270
- if (!this.streamClient.user) {
272
+ if (!this.streamClient.user && !this.connectionPromise) {
271
273
  return;
272
274
  }
273
275
  const disconnectUser = () => this.streamClient.disconnectUser(timeout);