@stream-io/video-client 0.0.1-alpha.81 → 0.0.1-alpha.83

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 (42) hide show
  1. package/dist/index.browser.es.js +501 -290
  2. package/dist/index.browser.es.js.map +1 -1
  3. package/dist/index.cjs.js +499 -288
  4. package/dist/index.cjs.js.map +1 -1
  5. package/dist/index.es.js +501 -290
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/src/StreamSfuClient.d.ts +26 -4
  8. package/dist/src/StreamVideoClient.d.ts +2 -2
  9. package/dist/src/coordinator/connection/client.d.ts +4 -4
  10. package/dist/src/coordinator/connection/types.d.ts +20 -9
  11. package/dist/src/events/call-permissions.d.ts +3 -3
  12. package/dist/src/events/call.d.ts +5 -5
  13. package/dist/src/events/moderation.d.ts +3 -3
  14. package/dist/src/events/reactions.d.ts +2 -2
  15. package/dist/src/events/recording.d.ts +3 -3
  16. package/dist/src/rtc/Call.d.ts +10 -8
  17. package/dist/src/rtc/Dispatcher.d.ts +2 -0
  18. package/dist/src/rtc/publisher.d.ts +7 -2
  19. package/dist/src/rtc/signal.d.ts +1 -1
  20. package/dist/src/rtc/subscriber.d.ts +3 -1
  21. package/dist/src/store/CallState.d.ts +43 -0
  22. package/package.json +1 -1
  23. package/src/StreamSfuClient.ts +70 -27
  24. package/src/StreamVideoClient.ts +7 -32
  25. package/src/coordinator/connection/client.ts +6 -6
  26. package/src/coordinator/connection/connection.ts +2 -1
  27. package/src/coordinator/connection/connection_fallback.ts +2 -2
  28. package/src/coordinator/connection/types.ts +50 -11
  29. package/src/coordinator/connection/utils.ts +3 -3
  30. package/src/events/call-permissions.ts +9 -6
  31. package/src/events/call.ts +17 -10
  32. package/src/events/moderation.ts +9 -3
  33. package/src/events/reactions.ts +5 -2
  34. package/src/events/recording.ts +9 -6
  35. package/src/rtc/Call.ts +193 -52
  36. package/src/rtc/Dispatcher.ts +24 -0
  37. package/src/rtc/publisher.ts +39 -25
  38. package/src/rtc/signal.ts +23 -23
  39. package/src/rtc/subscriber.ts +39 -23
  40. package/src/store/CallState.ts +54 -0
  41. package/dist/src/coordinator/connection/events.d.ts +0 -12
  42. package/src/coordinator/connection/events.ts +0 -15
@@ -3,7 +3,7 @@ import { MessageType, isJsonObject, typeofJsonValue, reflectionMergePartial, Unk
3
3
  import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
4
4
  import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
5
5
  import { v4 } from 'uuid';
6
- import { ReplaySubject, BehaviorSubject, Subject, takeWhile, filter, debounceTime, startWith, pairwise, Observable, concatMap, from, shareReplay, merge, map as map$2, combineLatest, firstValueFrom } from 'rxjs';
6
+ import { ReplaySubject, BehaviorSubject, Subject, takeWhile, debounceTime, startWith, pairwise, Observable, concatMap, from, shareReplay, merge, map as map$2, combineLatest, filter, firstValueFrom } from 'rxjs';
7
7
  import { take, combineLatestWith, map as map$1, distinctUntilChanged } from 'rxjs/operators';
8
8
  import axios, { AxiosHeaders } from 'axios';
9
9
  import WebSocket$1 from 'isomorphic-ws';
@@ -3821,28 +3821,27 @@ const createSignalClient = (options) => {
3821
3821
  };
3822
3822
 
3823
3823
  const createWebSocketSignalChannel = (opts) => {
3824
- return new Promise((resolve) => {
3825
- const { endpoint, onMessage } = opts;
3826
- const ws = new WebSocket(endpoint);
3827
- ws.binaryType = 'arraybuffer'; // do we need this?
3828
- ws.addEventListener('open', () => {
3829
- return resolve(ws);
3830
- });
3831
- ws.addEventListener('error', (e) => {
3832
- console.error('Error', e);
3833
- });
3834
- ws.addEventListener('close', (e) => {
3835
- console.warn('Signalling channel is closed', e);
3836
- });
3837
- if (onMessage) {
3838
- ws.addEventListener('message', (e) => {
3839
- const message = e.data instanceof ArrayBuffer
3840
- ? SfuEvent.fromBinary(new Uint8Array(e.data))
3841
- : SfuEvent.fromJsonString(e.data);
3842
- onMessage(message);
3843
- });
3844
- }
3824
+ const { endpoint, onMessage } = opts;
3825
+ const ws = new WebSocket(endpoint);
3826
+ ws.binaryType = 'arraybuffer'; // do we need this?
3827
+ ws.addEventListener('error', (e) => {
3828
+ console.error('Error', e);
3829
+ });
3830
+ ws.addEventListener('close', (e) => {
3831
+ console.warn('Signalling channel is closed', e);
3832
+ });
3833
+ ws.addEventListener('open', (e) => {
3834
+ console.log('Signalling channel is open', e);
3845
3835
  });
3836
+ if (onMessage) {
3837
+ ws.addEventListener('message', (e) => {
3838
+ const message = e.data instanceof ArrayBuffer
3839
+ ? SfuEvent.fromBinary(new Uint8Array(e.data))
3840
+ : SfuEvent.fromJsonString(e.data);
3841
+ onMessage(message);
3842
+ });
3843
+ }
3844
+ return ws;
3846
3845
  };
3847
3846
 
3848
3847
  /**
@@ -3891,17 +3890,23 @@ const toURL = (url) => {
3891
3890
  return null;
3892
3891
  }
3893
3892
  };
3893
+ /**
3894
+ * The client used for exchanging information with the SFU.
3895
+ */
3894
3896
  class StreamSfuClient {
3895
3897
  constructor(dispatcher, url, token) {
3898
+ /**
3899
+ * A buffer for ICE Candidates that are received before
3900
+ * the PeerConnections are ready to handle them.
3901
+ */
3896
3902
  this.iceTrickleBuffer = new IceTrickleBuffer();
3897
- this.close = () => {
3898
- this.signalReady.then((ws) => {
3899
- this.signalReady = Promise.reject('Connection closed');
3900
- // TODO OL: re-connect flow
3901
- ws.close(1000, 'Requested signal connection close');
3902
- this.dispatcher.offAll();
3903
- clearInterval(this.keepAliveInterval);
3904
- });
3903
+ this.pingIntervalInMs = 25 * 1000;
3904
+ this.unhealthyTimeoutInMs = this.pingIntervalInMs + 5 * 1000;
3905
+ this.close = (code = 1000) => {
3906
+ this.signalWs.close(code, 'Requested signal connection close');
3907
+ this.unsubscribeIceTrickle();
3908
+ clearInterval(this.keepAliveInterval);
3909
+ clearTimeout(this.connectionCheckTimeout);
3905
3910
  };
3906
3911
  this.updateSubscriptions = (subscriptions) => __awaiter(this, void 0, void 0, function* () {
3907
3912
  return this.rpc.updateSubscriptions({
@@ -3945,7 +3950,35 @@ class StreamSfuClient {
3945
3950
  signal.send(SfuRequest.toBinary(message));
3946
3951
  });
3947
3952
  };
3948
- this.dispatcher = dispatcher;
3953
+ this.keepAlive = () => {
3954
+ if (this.keepAliveInterval) {
3955
+ clearInterval(this.keepAliveInterval);
3956
+ }
3957
+ this.keepAliveInterval = setInterval(() => {
3958
+ console.log('Sending healthCheckRequest to SFU');
3959
+ const message = SfuRequest.create({
3960
+ requestPayload: {
3961
+ oneofKind: 'healthCheckRequest',
3962
+ healthCheckRequest: {},
3963
+ },
3964
+ });
3965
+ void this.send(message);
3966
+ }, this.pingIntervalInMs);
3967
+ };
3968
+ this.scheduleConnectionCheck = () => {
3969
+ if (this.connectionCheckTimeout) {
3970
+ clearTimeout(this.connectionCheckTimeout);
3971
+ }
3972
+ this.connectionCheckTimeout = setTimeout(() => {
3973
+ if (this.lastMessageTimestamp) {
3974
+ const timeSinceLastMessage = new Date().getTime() - this.lastMessageTimestamp.getTime();
3975
+ if (timeSinceLastMessage > this.unhealthyTimeoutInMs) {
3976
+ console.log('SFU connection unhealthy, closing');
3977
+ this.close(4001);
3978
+ }
3979
+ }
3980
+ }, this.unhealthyTimeoutInMs);
3981
+ };
3949
3982
  this.sessionId = v4();
3950
3983
  this.token = token;
3951
3984
  this.rpc = createSignalClient({
@@ -3972,36 +4005,25 @@ class StreamSfuClient {
3972
4005
  // connection is established. In that case, those events (ICE candidates)
3973
4006
  // need to be buffered and later added to the appropriate PeerConnection
3974
4007
  // once the remoteDescription is known and set.
3975
- this.dispatcher.on('iceTrickle', (e) => {
4008
+ this.unsubscribeIceTrickle = dispatcher.on('iceTrickle', (e) => {
3976
4009
  if (e.eventPayload.oneofKind !== 'iceTrickle')
3977
4010
  return;
3978
4011
  const { iceTrickle } = e.eventPayload;
3979
4012
  this.iceTrickleBuffer.push(iceTrickle);
3980
4013
  });
3981
- this.signalReady = createWebSocketSignalChannel({
4014
+ this.signalWs = createWebSocketSignalChannel({
3982
4015
  endpoint: wsEndpoint,
3983
4016
  onMessage: (message) => {
3984
- this.dispatcher.dispatch(message);
4017
+ this.lastMessageTimestamp = new Date();
4018
+ this.scheduleConnectionCheck();
4019
+ dispatcher.dispatch(message);
3985
4020
  },
3986
4021
  });
3987
- }
3988
- // FIXME: make this private
3989
- keepAlive() {
3990
- return __awaiter(this, void 0, void 0, function* () {
3991
- yield this.signalReady;
3992
- console.log('Registering healthcheck for SFU');
3993
- this.keepAliveInterval = setInterval(() => {
3994
- const message = SfuRequest.create({
3995
- requestPayload: {
3996
- oneofKind: 'healthCheckRequest',
3997
- healthCheckRequest: {
3998
- sessionId: this.sessionId,
3999
- },
4000
- },
4001
- });
4002
- console.log('Sending healthCheckRequest to SFU', message);
4003
- this.send(message);
4004
- }, 27000);
4022
+ this.signalReady = new Promise((resolve) => {
4023
+ this.signalWs.addEventListener('open', () => {
4024
+ this.keepAlive();
4025
+ resolve(this.signalWs);
4026
+ });
4005
4027
  });
4006
4028
  }
4007
4029
  }
@@ -4019,8 +4041,9 @@ function getIceCandidate(candidate) {
4019
4041
  }
4020
4042
  }
4021
4043
 
4022
- const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4044
+ const createSubscriber = ({ rpcClient, dispatcher, connectionConfig, onTrack, }) => {
4023
4045
  const subscriber = new RTCPeerConnection(connectionConfig);
4046
+ attachDebugEventListeners(subscriber);
4024
4047
  subscriber.addEventListener('icecandidate', (e) => __awaiter(void 0, void 0, void 0, function* () {
4025
4048
  const { candidate } = e;
4026
4049
  if (!candidate) {
@@ -4032,22 +4055,11 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4032
4055
  peerType: PeerType.SUBSCRIBER,
4033
4056
  });
4034
4057
  }));
4035
- subscriber.addEventListener('icecandidateerror', (e) => {
4036
- const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
4037
- `${e.errorCode}: ${e.errorText}`;
4038
- console.error(`Subscriber: ICE Candidate error`, errorMessage, e);
4039
- });
4040
- subscriber.addEventListener('iceconnectionstatechange', (e) => {
4041
- console.log(`Subscriber: ICE Connection state changed`, subscriber.iceConnectionState, e);
4042
- });
4043
- subscriber.addEventListener('icegatheringstatechange', (e) => {
4044
- console.log(`Subscriber: ICE Gathering State`, subscriber.iceGatheringState, e);
4045
- });
4046
4058
  if (onTrack) {
4047
4059
  subscriber.addEventListener('track', onTrack);
4048
4060
  }
4049
- const { dispatcher, iceTrickleBuffer } = rpcClient;
4050
- dispatcher.on('subscriberOffer', (message) => __awaiter(void 0, void 0, void 0, function* () {
4061
+ const { iceTrickleBuffer } = rpcClient;
4062
+ const unsubscribe = dispatcher.on('subscriberOffer', (message) => __awaiter(void 0, void 0, void 0, function* () {
4051
4063
  if (message.eventPayload.oneofKind !== 'subscriberOffer')
4052
4064
  return;
4053
4065
  const { subscriberOffer } = message.eventPayload;
@@ -4062,7 +4074,7 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4062
4074
  yield subscriber.addIceCandidate(iceCandidate);
4063
4075
  }
4064
4076
  catch (e) {
4065
- console.error(`[Subscriber] ICE candidate error`, e, candidate);
4077
+ console.error(`Subscriber: ICE candidate error`, e, candidate);
4066
4078
  }
4067
4079
  }));
4068
4080
  // apply ice candidates
@@ -4073,8 +4085,30 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4073
4085
  sdp: answer.sdp || '',
4074
4086
  });
4075
4087
  }));
4088
+ // we replace the close method of the subscriber PeerConnection
4089
+ // so that we can preform some cleanups before closing the connection.
4090
+ // We are doing this as currently there is no event that is fired
4091
+ // when the subscriber PeerConnection is closed.
4092
+ const originalClose = subscriber.close;
4093
+ subscriber.close = () => {
4094
+ unsubscribe();
4095
+ originalClose.call(subscriber);
4096
+ };
4076
4097
  return subscriber;
4077
4098
  };
4099
+ const attachDebugEventListeners = (subscriber) => {
4100
+ subscriber.addEventListener('icecandidateerror', (e) => {
4101
+ const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
4102
+ `${e.errorCode}: ${e.errorText}`;
4103
+ console.error(`Subscriber: ICE Candidate error`, errorMessage);
4104
+ });
4105
+ subscriber.addEventListener('iceconnectionstatechange', () => {
4106
+ console.log(`Subscriber: ICE Connection state changed`, subscriber.iceConnectionState);
4107
+ });
4108
+ subscriber.addEventListener('icegatheringstatechange', () => {
4109
+ console.log(`Subscriber: ICE Gathering State`, subscriber.iceGatheringState);
4110
+ });
4111
+ };
4078
4112
 
4079
4113
  const findOptimalVideoLayers = (videoTrack) => {
4080
4114
  const steps = [
@@ -4221,8 +4255,8 @@ const getGenericSdp = (direction, preferredCodec) => __awaiter(void 0, void 0, v
4221
4255
  });
4222
4256
 
4223
4257
  /**
4224
- * @internal
4225
4258
  * The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
4259
+ * @internal
4226
4260
  */
4227
4261
  class Publisher {
4228
4262
  constructor({ connectionConfig, rpcClient, isDtxEnabled }) {
@@ -4276,7 +4310,7 @@ class Publisher {
4276
4310
  if (trackType === TrackType.VIDEO) {
4277
4311
  const codecPreferences = getPreferredCodecs('video', opts.preferredCodec || 'vp8');
4278
4312
  if ('setCodecPreferences' in transceiver && codecPreferences) {
4279
- console.log(`set codec preferences`, codecPreferences);
4313
+ console.log(`set video codec preferences`, codecPreferences);
4280
4314
  // @ts-ignore
4281
4315
  transceiver.setCodecPreferences(codecPreferences);
4282
4316
  }
@@ -4286,14 +4320,17 @@ class Publisher {
4286
4320
  const codecPreferences = getPreferredCodecs('audio', opts.preferredCodec, returnOnlyMatched);
4287
4321
  console.log('Preferred codec', opts.preferredCodec);
4288
4322
  if ('setCodecPreferences' in transceiver && codecPreferences) {
4289
- console.log(`set codec preferences`, codecPreferences);
4323
+ console.log(`set audio codec preferences`, codecPreferences);
4290
4324
  // @ts-ignore
4291
4325
  transceiver.setCodecPreferences(codecPreferences);
4292
4326
  }
4293
4327
  }
4294
4328
  }
4295
4329
  else {
4296
- (_a = transceiver.sender.track) === null || _a === void 0 ? void 0 : _a.stop();
4330
+ // don't stop the track if we are re-publishing the same track
4331
+ if (transceiver.sender.track !== track) {
4332
+ (_a = transceiver.sender.track) === null || _a === void 0 ? void 0 : _a.stop();
4333
+ }
4297
4334
  yield transceiver.sender.replaceTrack(track);
4298
4335
  }
4299
4336
  });
@@ -4317,16 +4354,21 @@ class Publisher {
4317
4354
  };
4318
4355
  /**
4319
4356
  * Stops publishing all tracks and stop all tracks.
4357
+ *
4358
+ * @param options - Options
4359
+ * @param options.stopTracks - If `true` (default), all tracks will be stopped.
4320
4360
  */
4321
- this.stopPublishing = () => {
4322
- this.publisher.getSenders().forEach((s) => {
4323
- if (s.track) {
4324
- s.track.stop();
4325
- }
4326
- if (this.publisher.signalingState !== 'closed') {
4327
- this.publisher.removeTrack(s);
4328
- }
4329
- });
4361
+ this.stopPublishing = (options = {}) => {
4362
+ const { stopTracks = true } = options;
4363
+ if (stopTracks) {
4364
+ this.publisher.getSenders().forEach((s) => {
4365
+ var _a;
4366
+ (_a = s.track) === null || _a === void 0 ? void 0 : _a.stop();
4367
+ if (this.publisher.signalingState !== 'closed') {
4368
+ this.publisher.removeTrack(s);
4369
+ }
4370
+ });
4371
+ }
4330
4372
  this.publisher.close();
4331
4373
  };
4332
4374
  this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
@@ -4400,34 +4442,39 @@ class Publisher {
4400
4442
  };
4401
4443
  });
4402
4444
  // TODO debounce for 250ms
4403
- const response = yield this.rpcClient.setPublisher({
4445
+ const { response } = yield this.rpcClient.setPublisher({
4404
4446
  sdp: offer.sdp || '',
4405
4447
  tracks: trackInfos,
4406
4448
  });
4407
- yield this.publisher.setRemoteDescription({
4408
- type: 'answer',
4409
- sdp: response.response.sdp,
4410
- });
4449
+ try {
4450
+ yield this.publisher.setRemoteDescription({
4451
+ type: 'answer',
4452
+ sdp: response.sdp,
4453
+ });
4454
+ }
4455
+ catch (e) {
4456
+ console.error(`Publisher: setRemoteDescription error`, response.sdp, e);
4457
+ }
4411
4458
  this.rpcClient.iceTrickleBuffer.publisherCandidates.subscribe((candidate) => __awaiter(this, void 0, void 0, function* () {
4412
4459
  try {
4413
4460
  const iceCandidate = JSON.parse(candidate.iceCandidate);
4414
4461
  yield this.publisher.addIceCandidate(iceCandidate);
4415
4462
  }
4416
4463
  catch (e) {
4417
- console.error(`[Publisher] ICE candidate error`, e, candidate);
4464
+ console.error(`Publisher: ICE candidate error`, e, candidate);
4418
4465
  }
4419
4466
  }));
4420
4467
  });
4421
4468
  this.onIceCandidateError = (e) => {
4422
4469
  const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
4423
4470
  `${e.errorCode}: ${e.errorText}`;
4424
- console.error(`Publisher: ICE Candidate error`, errorMessage, e);
4471
+ console.error(`Publisher: ICE Candidate error`, errorMessage);
4425
4472
  };
4426
- this.onIceConnectionStateChange = (e) => {
4427
- console.log(`Publisher: ICE Connection state changed`, this.publisher.iceConnectionState, e);
4473
+ this.onIceConnectionStateChange = () => {
4474
+ console.log(`Publisher: ICE Connection state changed`, this.publisher.iceConnectionState);
4428
4475
  };
4429
- this.onIceGatheringStateChange = (e) => {
4430
- console.log(`Publisher: ICE Gathering State`, this.publisher.iceGatheringState, e);
4476
+ this.onIceGatheringStateChange = () => {
4477
+ console.log(`Publisher: ICE Gathering State`, this.publisher.iceGatheringState);
4431
4478
  };
4432
4479
  this.ridToVideoQuality = (rid) => {
4433
4480
  return rid === 'q'
@@ -4606,6 +4653,25 @@ const registerEventHandlers = (call, store, dispatcher) => {
4606
4653
  watchDominantSpeakerChanged(dispatcher, store);
4607
4654
  };
4608
4655
 
4656
+ const sfuEventKinds = {
4657
+ subscriberOffer: undefined,
4658
+ publisherAnswer: undefined,
4659
+ connectionQualityChanged: undefined,
4660
+ audioLevelChanged: undefined,
4661
+ iceTrickle: undefined,
4662
+ changePublishQuality: undefined,
4663
+ participantJoined: undefined,
4664
+ participantLeft: undefined,
4665
+ dominantSpeakerChanged: undefined,
4666
+ joinResponse: undefined,
4667
+ healthCheckResponse: undefined,
4668
+ trackPublished: undefined,
4669
+ trackUnpublished: undefined,
4670
+ error: undefined,
4671
+ };
4672
+ const isSfuEvent = (eventName) => {
4673
+ return Object.prototype.hasOwnProperty.call(sfuEventKinds, eventName);
4674
+ };
4609
4675
  class Dispatcher {
4610
4676
  constructor() {
4611
4677
  this.subscribers = {};
@@ -4959,6 +5025,40 @@ const speakerLayoutSortPreset = combineComparators(pinned, screenSharing, domina
4959
5025
  */
4960
5026
  const livestreamOrAudioRoomSortPreset = combineComparators(ifInvisibleBy(dominantSpeaker), ifInvisibleBy(speaking), ifInvisibleBy(reactionType('raised-hand')), ifInvisibleBy(publishingVideo), ifInvisibleBy(publishingAudio), role('admin', 'host', 'speaker'));
4961
5027
 
5028
+ /**
5029
+ * Represents the state of the current call.
5030
+ */
5031
+ var CallingState;
5032
+ (function (CallingState) {
5033
+ /**
5034
+ * The call is in an idle state.
5035
+ */
5036
+ CallingState["IDLE"] = "idle";
5037
+ /**
5038
+ * The call is in the process of joining.
5039
+ */
5040
+ CallingState["JOINING"] = "joining";
5041
+ /**
5042
+ * The call is currently active.
5043
+ */
5044
+ CallingState["JOINED"] = "joined";
5045
+ /**
5046
+ * The call has been left.
5047
+ */
5048
+ CallingState["LEFT"] = "left";
5049
+ /**
5050
+ * The call is in the process of reconnecting.
5051
+ */
5052
+ CallingState["RECONNECTING"] = "reconnecting";
5053
+ /**
5054
+ * The call has failed to reconnect.
5055
+ */
5056
+ CallingState["RECONNECTING_FAILED"] = "reconnecting-failed";
5057
+ /**
5058
+ * The call is in offline mode.
5059
+ */
5060
+ CallingState["OFFLINE"] = "offline";
5061
+ })(CallingState || (CallingState = {}));
4962
5062
  /**
4963
5063
  * Holds the state of the current call.
4964
5064
  * @react You don't have to use this class directly, as we are exposing the state through Hooks.
@@ -4982,6 +5082,12 @@ class CallState {
4982
5082
  * @internal
4983
5083
  */
4984
5084
  this.membersSubject = new BehaviorSubject([]);
5085
+ /**
5086
+ * The calling state.
5087
+ *
5088
+ * @internal
5089
+ */
5090
+ this.callingStateSubject = new BehaviorSubject(CallingState.IDLE);
4985
5091
  /**
4986
5092
  * All participants of the current call (including the logged-in user).
4987
5093
  *
@@ -5123,6 +5229,7 @@ class CallState {
5123
5229
  return acc;
5124
5230
  }, {});
5125
5231
  }));
5232
+ this.callingState$ = this.callingStateSubject.asObservable();
5126
5233
  }
5127
5234
  }
5128
5235
 
@@ -5620,6 +5727,129 @@ const CallTypes = new CallTypesRegistry([
5620
5727
  }),
5621
5728
  ]);
5622
5729
 
5730
+ const sleep = (m) => new Promise((r) => setTimeout(r, m));
5731
+ function isFunction(value) {
5732
+ return (value &&
5733
+ (Object.prototype.toString.call(value) === '[object Function]' ||
5734
+ 'function' === typeof value ||
5735
+ value instanceof Function));
5736
+ }
5737
+ // todo: rename so that it does not contain word "chat"
5738
+ const chatCodes = {
5739
+ TOKEN_EXPIRED: 40,
5740
+ WS_CLOSED_SUCCESS: 1000,
5741
+ };
5742
+ /**
5743
+ * retryInterval - A retry interval which increases acc to number of failures
5744
+ *
5745
+ * @return {number} Duration to wait in milliseconds
5746
+ */
5747
+ function retryInterval(numberOfFailures) {
5748
+ // try to reconnect in 0.25-5 seconds (random to spread out the load from failures)
5749
+ const max = Math.min(500 + numberOfFailures * 2000, 5000);
5750
+ const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 5000);
5751
+ return Math.floor(Math.random() * (max - min) + min);
5752
+ }
5753
+ function randomId() {
5754
+ return generateUUIDv4();
5755
+ }
5756
+ function hex(bytes) {
5757
+ let s = '';
5758
+ for (let i = 0; i < bytes.length; i++) {
5759
+ s += bytes[i].toString(16).padStart(2, '0');
5760
+ }
5761
+ return s;
5762
+ }
5763
+ // https://tools.ietf.org/html/rfc4122
5764
+ function generateUUIDv4() {
5765
+ const bytes = getRandomBytes(16);
5766
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
5767
+ bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
5768
+ return (hex(bytes.subarray(0, 4)) +
5769
+ '-' +
5770
+ hex(bytes.subarray(4, 6)) +
5771
+ '-' +
5772
+ hex(bytes.subarray(6, 8)) +
5773
+ '-' +
5774
+ hex(bytes.subarray(8, 10)) +
5775
+ '-' +
5776
+ hex(bytes.subarray(10, 16)));
5777
+ }
5778
+ function getRandomValuesWithMathRandom(bytes) {
5779
+ const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
5780
+ for (let i = 0; i < bytes.length; i++) {
5781
+ bytes[i] = Math.random() * max;
5782
+ }
5783
+ }
5784
+ const getRandomValues = (() => {
5785
+ if (typeof crypto !== 'undefined' &&
5786
+ typeof (crypto === null || crypto === void 0 ? void 0 : crypto.getRandomValues) !== 'undefined') {
5787
+ return crypto.getRandomValues.bind(crypto);
5788
+ }
5789
+ else if (typeof msCrypto !== 'undefined') {
5790
+ return msCrypto.getRandomValues.bind(msCrypto);
5791
+ }
5792
+ else {
5793
+ return getRandomValuesWithMathRandom;
5794
+ }
5795
+ })();
5796
+ function getRandomBytes(length) {
5797
+ const bytes = new Uint8Array(length);
5798
+ getRandomValues(bytes);
5799
+ return bytes;
5800
+ }
5801
+ function convertErrorToJson(err) {
5802
+ const jsonObj = {};
5803
+ if (!err)
5804
+ return jsonObj;
5805
+ try {
5806
+ Object.getOwnPropertyNames(err).forEach((key) => {
5807
+ jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
5808
+ });
5809
+ }
5810
+ catch (_) {
5811
+ return {
5812
+ error: 'failed to serialize the error',
5813
+ };
5814
+ }
5815
+ return jsonObj;
5816
+ }
5817
+ /**
5818
+ * isOnline safely return the navigator.online value for browser env
5819
+ * if navigator is not in global object, it always return true
5820
+ */
5821
+ function isOnline() {
5822
+ const nav = typeof navigator !== 'undefined'
5823
+ ? navigator
5824
+ : typeof window !== 'undefined' && window.navigator
5825
+ ? window.navigator
5826
+ : undefined;
5827
+ if (!nav) {
5828
+ console.warn('isOnline failed to access window.navigator and assume browser is online');
5829
+ return true;
5830
+ }
5831
+ // RN navigator has undefined for onLine
5832
+ if (typeof nav.onLine !== 'boolean') {
5833
+ return true;
5834
+ }
5835
+ return nav.onLine;
5836
+ }
5837
+ /**
5838
+ * listenForConnectionChanges - Adds an event listener fired on browser going online or offline
5839
+ */
5840
+ function addConnectionEventListeners(cb) {
5841
+ if (typeof window !== 'undefined' && window.addEventListener) {
5842
+ window.addEventListener('offline', cb);
5843
+ window.addEventListener('online', cb);
5844
+ }
5845
+ }
5846
+ function removeConnectionEventListeners(cb) {
5847
+ if (typeof window !== 'undefined' && window.removeEventListener) {
5848
+ window.removeEventListener('offline', cb);
5849
+ window.removeEventListener('online', cb);
5850
+ }
5851
+ }
5852
+
5623
5853
  const UPDATE_SUBSCRIPTIONS_DEBOUNCE_DURATION = 600;
5624
5854
  /**
5625
5855
  * A `Call` object represents the active call the user is part of.
@@ -5628,7 +5858,7 @@ const UPDATE_SUBSCRIPTIONS_DEBOUNCE_DURATION = 600;
5628
5858
  class Call {
5629
5859
  get preferredAudioCodec() {
5630
5860
  var _a;
5631
- const audioSettings = (_a = this.state.getCurrentValue(this.state.metadata$)) === null || _a === void 0 ? void 0 : _a.settings.audio;
5861
+ const audioSettings = (_a = this.data) === null || _a === void 0 ? void 0 : _a.settings.audio;
5632
5862
  let preferredCodec = (audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.redundant_coding_enabled) === undefined
5633
5863
  ? 'opus'
5634
5864
  : audioSettings.redundant_coding_enabled
@@ -5647,7 +5877,7 @@ class Call {
5647
5877
  */
5648
5878
  constructor({ type, id, streamClient, metadata, members, sortParticipantsBy, clientStore, }) {
5649
5879
  /**
5650
- * ViewporTracker instance
5880
+ * ViewportTracker instance
5651
5881
  */
5652
5882
  this.viewportTracker = new ViewportTracker();
5653
5883
  /**
@@ -5656,17 +5886,7 @@ class Call {
5656
5886
  */
5657
5887
  this.dispatcher = new Dispatcher();
5658
5888
  this.trackSubscriptionsSubject = new Subject();
5659
- this.joined$ = new BehaviorSubject(false);
5660
- /**
5661
- * You can subscribe to WebSocket events provided by the API. To remove a subscription, call the `off` method.
5662
- * Please note that subscribing to WebSocket events is an advanced use-case, for most use-cases it should be enough to watch for changes in the [reactive state store](./StreamVideoClient.md/#readonlystatestore).
5663
- * @param eventName
5664
- * @param fn
5665
- * @returns
5666
- */
5667
- this.on = (eventName, fn) => {
5668
- return this.dispatcher.on(eventName, fn);
5669
- };
5889
+ this.reconnectAttempts = 0;
5670
5890
  /**
5671
5891
  * Remove subscription for WebSocket events that were created by the `on` method.
5672
5892
  * @param eventName
@@ -5681,10 +5901,9 @@ class Call {
5681
5901
  */
5682
5902
  this.leave = () => {
5683
5903
  var _a, _b, _c, _d;
5684
- if (!this.joined$.getValue()) {
5904
+ if (this.state.getCurrentValue(this.state.callingState$) === CallingState.LEFT) {
5685
5905
  throw new Error('Cannot leave call that has already been left.');
5686
5906
  }
5687
- this.joined$.next(false);
5688
5907
  (_a = this.statsReporter) === null || _a === void 0 ? void 0 : _a.stop();
5689
5908
  this.statsReporter = undefined;
5690
5909
  (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
@@ -5693,13 +5912,19 @@ class Call {
5693
5912
  this.publisher = undefined;
5694
5913
  (_d = this.sfuClient) === null || _d === void 0 ? void 0 : _d.close();
5695
5914
  this.sfuClient = undefined;
5915
+ this.dispatcher.offAll();
5696
5916
  this.clientStore.setCurrentValue(this.clientStore.activeCallSubject, undefined);
5917
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.LEFT);
5697
5918
  };
5698
- this.waitForJoinResponse = (timeout = 10000) => new Promise((resolve, reject) => {
5919
+ this.waitForJoinResponse = (timeout = 5000) => new Promise((resolve, reject) => {
5699
5920
  const unsubscribe = this.on('joinResponse', (event) => {
5700
- resolve(event);
5921
+ if (event.eventPayload.oneofKind !== 'joinResponse')
5922
+ return;
5923
+ clearTimeout(timeoutId);
5924
+ unsubscribe();
5925
+ resolve(event.eventPayload.joinResponse);
5701
5926
  });
5702
- setTimeout(() => {
5927
+ const timeoutId = setTimeout(() => {
5703
5928
  unsubscribe();
5704
5929
  reject(new Error('Waiting for "joinResponse" has timed out'));
5705
5930
  }, timeout);
@@ -5722,9 +5947,10 @@ class Call {
5722
5947
  */
5723
5948
  this.join = (data) => __awaiter(this, void 0, void 0, function* () {
5724
5949
  var _a;
5725
- if (this.joined$.getValue()) {
5950
+ if ([CallingState.JOINED, CallingState.JOINING].includes(this.state.getCurrentValue(this.state.callingState$))) {
5726
5951
  throw new Error(`Illegal State: Already joined.`);
5727
5952
  }
5953
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.JOINING);
5728
5954
  const call = yield join(this.streamClient, this.type, this.id, data);
5729
5955
  this.state.setCurrentValue(this.state.metadataSubject, call.metadata);
5730
5956
  this.state.setCurrentValue(this.state.membersSubject, call.members);
@@ -5740,12 +5966,79 @@ class Call {
5740
5966
  sfuUrl = sfuUrlParam || call.sfuServer.url;
5741
5967
  }
5742
5968
  const sfuClient = (this.sfuClient = new StreamSfuClient(this.dispatcher, sfuUrl, call.token));
5969
+ /**
5970
+ * A closure which hides away the re-connection logic.
5971
+ */
5972
+ const rejoin = () => __awaiter(this, void 0, void 0, function* () {
5973
+ var _b, _c, _d;
5974
+ console.log(`Rejoining call ${this.cid} (${this.reconnectAttempts})...`);
5975
+ this.reconnectAttempts++;
5976
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING);
5977
+ // take a snapshot of the current "local participant" state
5978
+ // we'll need it for restoring the previous publishing state later
5979
+ const localParticipant = this.state.getCurrentValue(this.state.localParticipant$);
5980
+ (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
5981
+ (_c = this.publisher) === null || _c === void 0 ? void 0 : _c.stopPublishing({ stopTracks: false });
5982
+ (_d = this.statsReporter) === null || _d === void 0 ? void 0 : _d.stop();
5983
+ sfuClient.close(); // clean up previous connection
5984
+ yield sleep(retryInterval(this.reconnectAttempts));
5985
+ yield this.join(data);
5986
+ console.log(`Rejoin: ${this.reconnectAttempts} successful!`);
5987
+ if (localParticipant) {
5988
+ const { audioStream, videoStream, screenShareStream: screenShare, } = localParticipant;
5989
+ // restore previous publishing state
5990
+ if (audioStream)
5991
+ yield this.publishAudioStream(audioStream);
5992
+ if (videoStream)
5993
+ yield this.publishVideoStream(videoStream);
5994
+ if (screenShare)
5995
+ yield this.publishScreenShareStream(screenShare);
5996
+ }
5997
+ console.log(`Rejoin: state restored ${this.reconnectAttempts}`);
5998
+ });
5999
+ // reconnect if the connection was closed unexpectedly. example:
6000
+ // - SFU crash or restart
6001
+ // - network change
6002
+ sfuClient.signalReady.then(() => {
6003
+ sfuClient.signalWs.addEventListener('close', (e) => {
6004
+ // do nothing if the connection was closed on purpose
6005
+ if (e.code === 1000)
6006
+ return;
6007
+ if (this.reconnectAttempts >= 10) {
6008
+ console.log('Reconnect attempts exceeded. Giving up...');
6009
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING_FAILED);
6010
+ }
6011
+ else {
6012
+ void rejoin();
6013
+ }
6014
+ });
6015
+ });
6016
+ // handlers for connection online/offline events
6017
+ // Note: window.addEventListener is not available in React Native, hence the check
6018
+ if (typeof window !== 'undefined' && window.addEventListener) {
6019
+ const handleOnOffline = () => {
6020
+ window.removeEventListener('offline', handleOnOffline);
6021
+ console.log('Join: Going offline...');
6022
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.OFFLINE);
6023
+ };
6024
+ const handleOnOnline = () => {
6025
+ window.removeEventListener('online', handleOnOnline);
6026
+ if (this.state.getCurrentValue(this.state.callingState$) ===
6027
+ CallingState.OFFLINE) {
6028
+ console.log('Join: Going online...');
6029
+ rejoin();
6030
+ }
6031
+ };
6032
+ window.addEventListener('offline', handleOnOffline);
6033
+ window.addEventListener('online', handleOnOnline);
6034
+ }
5743
6035
  this.subscriber = createSubscriber({
5744
6036
  rpcClient: sfuClient,
6037
+ dispatcher: this.dispatcher,
5745
6038
  connectionConfig: call.connectionConfig,
5746
6039
  onTrack: this.handleOnTrack,
5747
6040
  });
5748
- const audioSettings = (_a = this.state.getCurrentValue(this.state.metadata$)) === null || _a === void 0 ? void 0 : _a.settings.audio;
6041
+ const audioSettings = (_a = this.data) === null || _a === void 0 ? void 0 : _a.settings.audio;
5749
6042
  let isDtxEnabled = (audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.opus_dtx_enabled) === undefined
5750
6043
  ? false
5751
6044
  : audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.opus_dtx_enabled;
@@ -5768,8 +6061,19 @@ class Call {
5768
6061
  store: this.state,
5769
6062
  edgeName: call.sfuServer.edge_name,
5770
6063
  });
5771
- const joinResponsePromise = this.waitForJoinResponse().then((event) => {
5772
- const { callState } = event.eventPayload.joinResponse;
6064
+ try {
6065
+ // 1. wait for the signal server to be ready before sending "joinRequest"
6066
+ sfuClient.signalReady
6067
+ .catch((err) => console.warn('Signal ready failed', err))
6068
+ // prepare a generic SDP and send it to the SFU.
6069
+ // this is a throw-away SDP that the SFU will use to determine
6070
+ // the capabilities of the client (codec support, etc.)
6071
+ .then(() => getGenericSdp('recvonly', this.preferredAudioCodec))
6072
+ .then((sdp) => sfuClient.join({ subscriberSdp: sdp || '' }));
6073
+ // 2. in parallel, wait for the SFU to send us the "joinResponse"
6074
+ // this will throw an error if the SFU rejects the join request or
6075
+ // fails to respond in time
6076
+ const { callState } = yield this.waitForJoinResponse();
5773
6077
  const currentParticipants = (callState === null || callState === void 0 ? void 0 : callState.participants) || [];
5774
6078
  const ownCapabilities = {
5775
6079
  ownCapabilities: call.metadata.own_capabilities,
@@ -5778,14 +6082,22 @@ class Call {
5778
6082
  ? ownCapabilities
5779
6083
  : {})))));
5780
6084
  this.clientStore.setCurrentValue(this.clientStore.activeCallSubject, this);
5781
- sfuClient.keepAlive();
5782
- this.joined$.next(true);
5783
- });
5784
- const genericSdp = yield getGenericSdp('recvonly', this.preferredAudioCodec);
5785
- yield sfuClient.join({
5786
- subscriberSdp: genericSdp || '',
5787
- });
5788
- return joinResponsePromise;
6085
+ this.reconnectAttempts = 0; // reset the reconnect attempts counter
6086
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.JOINED);
6087
+ console.log(`Joined call ${this.cid}`);
6088
+ }
6089
+ catch (err) {
6090
+ // join failed, try to rejoin
6091
+ if (this.reconnectAttempts < 10) {
6092
+ yield rejoin();
6093
+ console.log(`Rejoin ${this.reconnectAttempts} successful!`);
6094
+ }
6095
+ else {
6096
+ console.log(`Rejoin failed for ${this.reconnectAttempts} times. Giving up.`);
6097
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING_FAILED);
6098
+ throw new Error('Join failed');
6099
+ }
6100
+ }
5789
6101
  });
5790
6102
  this.updateCallMembers = (data) => __awaiter(this, void 0, void 0, function* () {
5791
6103
  return yield this.streamClient.post(`${this.streamClientBasePath}/members`, data);
@@ -5805,7 +6117,7 @@ class Call {
5805
6117
  * @param opts the options to use when publishing the stream.
5806
6118
  */
5807
6119
  this.publishVideoStream = (videoStream, opts = {}) => __awaiter(this, void 0, void 0, function* () {
5808
- var _b;
6120
+ var _e;
5809
6121
  // we should wait until we get a JoinResponse from the SFU,
5810
6122
  // otherwise we risk breaking the ICETrickle flow.
5811
6123
  yield this.assertCallJoined();
@@ -5819,7 +6131,7 @@ class Call {
5819
6131
  const trackType = TrackType.VIDEO;
5820
6132
  try {
5821
6133
  yield this.publisher.publishStream(videoStream, videoTrack, trackType, opts);
5822
- yield ((_b = this.sfuClient) === null || _b === void 0 ? void 0 : _b.updateMuteState(trackType, false));
6134
+ yield ((_e = this.sfuClient) === null || _e === void 0 ? void 0 : _e.updateMuteState(trackType, false));
5823
6135
  }
5824
6136
  catch (e) {
5825
6137
  throw e;
@@ -6004,8 +6316,8 @@ class Call {
6004
6316
  * @returns
6005
6317
  */
6006
6318
  this.getStats = (kind, selector) => __awaiter(this, void 0, void 0, function* () {
6007
- var _c;
6008
- return (_c = this.statsReporter) === null || _c === void 0 ? void 0 : _c.getRawStatsForTrack(kind, selector);
6319
+ var _f;
6320
+ return (_f = this.statsReporter) === null || _f === void 0 ? void 0 : _f.getRawStatsForTrack(kind, selector);
6009
6321
  });
6010
6322
  /**
6011
6323
  * Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
@@ -6093,8 +6405,8 @@ class Call {
6093
6405
  * @returns
6094
6406
  */
6095
6407
  this.updatePublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
6096
- var _d;
6097
- return (_d = this.publisher) === null || _d === void 0 ? void 0 : _d.updateVideoPublishQuality(enabledRids);
6408
+ var _g;
6409
+ return (_g = this.publisher) === null || _g === void 0 ? void 0 : _g.updateVideoPublishQuality(enabledRids);
6098
6410
  });
6099
6411
  this.handleOnTrack = (e) => {
6100
6412
  const [primaryStream] = e.streams;
@@ -6140,11 +6452,9 @@ class Call {
6140
6452
  };
6141
6453
  this.assertCallJoined = () => {
6142
6454
  return new Promise((resolve) => {
6143
- this.joined$
6144
- .pipe(takeWhile((isJoined) => !isJoined, true), filter((isJoined) => isJoined))
6145
- .subscribe(() => {
6146
- resolve();
6147
- });
6455
+ this.state.callingState$
6456
+ .pipe(takeWhile((state) => state !== CallingState.JOINED, true))
6457
+ .subscribe(() => resolve());
6148
6458
  });
6149
6459
  };
6150
6460
  this.sendReaction = (reaction) => __awaiter(this, void 0, void 0, function* () {
@@ -6349,6 +6659,19 @@ class Call {
6349
6659
  (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions);
6350
6660
  });
6351
6661
  }
6662
+ on(eventName, fn) {
6663
+ if (isSfuEvent(eventName)) {
6664
+ return this.dispatcher.on(eventName, fn);
6665
+ }
6666
+ else {
6667
+ const eventHandler = (event) => {
6668
+ if (event.call_cid && event.call_cid === this.cid) {
6669
+ fn(event);
6670
+ }
6671
+ };
6672
+ return this.streamClient.on(eventName, eventHandler);
6673
+ }
6674
+ }
6352
6675
  get data() {
6353
6676
  return this.state.getCurrentValue(this.state.metadata$);
6354
6677
  }
@@ -6361,6 +6684,9 @@ class Call {
6361
6684
  */
6362
6685
  const watchCallCreated = (store, streamClient) => {
6363
6686
  return function onCallCreated(event) {
6687
+ if (event.type !== 'call.created') {
6688
+ return;
6689
+ }
6364
6690
  const { call, members } = event;
6365
6691
  if (!call) {
6366
6692
  console.warn("Can't find call in CallCreatedEvent");
@@ -6391,6 +6717,9 @@ const watchCallCreated = (store, streamClient) => {
6391
6717
  */
6392
6718
  const watchCallAccepted = (store) => {
6393
6719
  return function onCallAccepted(event) {
6720
+ if (event.type !== 'call.accepted') {
6721
+ return;
6722
+ }
6394
6723
  const { call_cid } = event;
6395
6724
  if (!call_cid) {
6396
6725
  console.warn("Can't find call_cid in CallAcceptedEvent");
@@ -6429,6 +6758,9 @@ const watchCallAccepted = (store) => {
6429
6758
  */
6430
6759
  const watchCallRejected = (store) => {
6431
6760
  return function onCallRejected(event) {
6761
+ if (event.type !== 'call.rejected') {
6762
+ return;
6763
+ }
6432
6764
  const { call_cid } = event;
6433
6765
  if (!call_cid) {
6434
6766
  console.warn("Can't find call_cid in CallRejectedEvent");
@@ -6462,6 +6794,9 @@ const watchCallRejected = (store) => {
6462
6794
  */
6463
6795
  const watchCallCancelled = (store) => {
6464
6796
  return function onCallCancelled(event) {
6797
+ if (event.type !== 'call.ended') {
6798
+ return;
6799
+ }
6465
6800
  const { call_cid } = event;
6466
6801
  if (!call_cid) {
6467
6802
  console.log("Can't find call in CallEndedEvent");
@@ -6488,6 +6823,9 @@ const watchCallCancelled = (store) => {
6488
6823
  */
6489
6824
  const watchCallPermissionRequest = (store) => {
6490
6825
  return function onCallPermissionRequest(event) {
6826
+ if (event.type !== 'call.permission_request') {
6827
+ return;
6828
+ }
6491
6829
  const activeCall = store.getCurrentValue(store.activeCallSubject);
6492
6830
  if (!activeCall) {
6493
6831
  console.warn(`Ignoring "call.permission_request" as there is no active call`, event);
@@ -6512,6 +6850,9 @@ const watchCallPermissionRequest = (store) => {
6512
6850
  */
6513
6851
  const watchCallPermissionsUpdated = (store) => {
6514
6852
  return function onCallPermissionsUpdated(event) {
6853
+ if (event.type !== 'call.permissions_updated') {
6854
+ return;
6855
+ }
6515
6856
  const activeCall = store.getCurrentValue(store.activeCallSubject);
6516
6857
  if (!activeCall) {
6517
6858
  console.warn(`Ignoring "call.permissions_updated" as there is no active call`, event);
@@ -6539,6 +6880,9 @@ const watchCallPermissionsUpdated = (store) => {
6539
6880
  */
6540
6881
  const watchNewReactions = (store) => {
6541
6882
  return function onNewReactions(event) {
6883
+ if (event.type !== 'call.reaction_new') {
6884
+ return;
6885
+ }
6542
6886
  const { call_cid, reaction } = event;
6543
6887
  const activeCall = store.getCurrentValue(store.activeCallSubject);
6544
6888
  if (!activeCall || activeCall.cid !== call_cid) {
@@ -6571,6 +6915,9 @@ const watchNewReactions = (store) => {
6571
6915
  */
6572
6916
  const watchCallRecordingStarted = (store) => {
6573
6917
  return function onCallRecordingStarted(event) {
6918
+ if (event.type !== 'call.recording_started') {
6919
+ return;
6920
+ }
6574
6921
  const { call_cid } = event;
6575
6922
  const activeCall = store.getCurrentValue(store.activeCallSubject);
6576
6923
  if (!activeCall || activeCall.cid !== call_cid) {
@@ -6586,6 +6933,9 @@ const watchCallRecordingStarted = (store) => {
6586
6933
  */
6587
6934
  const watchCallRecordingStopped = (store) => {
6588
6935
  return function onCallRecordingStopped(event) {
6936
+ if (event.type !== 'call.recording_stopped') {
6937
+ return;
6938
+ }
6589
6939
  const { call_cid } = event;
6590
6940
  const activeCall = store.getCurrentValue(store.activeCallSubject);
6591
6941
  if (!activeCall || activeCall.cid !== call_cid) {
@@ -6603,6 +6953,9 @@ const watchCallRecordingStopped = (store) => {
6603
6953
  * `event.user_id` to the list
6604
6954
  */
6605
6955
  const watchBlockedUser = (store) => (event) => {
6956
+ if (event.type !== 'call.blocked_user') {
6957
+ return;
6958
+ }
6606
6959
  const activeCall = store.getCurrentValue(store.activeCallSubject);
6607
6960
  if (!activeCall || activeCall.cid !== event.call_cid) {
6608
6961
  console.warn(`Received "call.blocked_user" for an inactive or unknown call`, event);
@@ -6622,6 +6975,9 @@ const watchBlockedUser = (store) => (event) => {
6622
6975
  * removing `event.user_id` from the list
6623
6976
  */
6624
6977
  const watchUnblockedUser = (store) => (event) => {
6978
+ if (event.type !== 'call.unblocked_user') {
6979
+ return;
6980
+ }
6625
6981
  const activeCall = store.getCurrentValue(store.activeCallSubject);
6626
6982
  if (!activeCall || activeCall.cid !== event.call_cid) {
6627
6983
  console.warn(`Received "call.unblocked_user" for an inactive or unknown call`, event);
@@ -6762,129 +7118,6 @@ CallDropScheduler.getLatestCall = (from, compareTo) => {
6762
7118
 
6763
7119
  var https = null;
6764
7120
 
6765
- const sleep = (m) => new Promise((r) => setTimeout(r, m));
6766
- function isFunction(value) {
6767
- return (value &&
6768
- (Object.prototype.toString.call(value) === '[object Function]' ||
6769
- 'function' === typeof value ||
6770
- value instanceof Function));
6771
- }
6772
- // todo: rename so that it does not contain word "chat"
6773
- const chatCodes = {
6774
- TOKEN_EXPIRED: 40,
6775
- WS_CLOSED_SUCCESS: 1000,
6776
- };
6777
- /**
6778
- * retryInterval - A retry interval which increases acc to number of failures
6779
- *
6780
- * @return {number} Duration to wait in milliseconds
6781
- */
6782
- function retryInterval(numberOfFailures) {
6783
- // try to reconnect in 0.25-25 seconds (random to spread out the load from failures)
6784
- const max = Math.min(500 + numberOfFailures * 2000, 25000);
6785
- const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 25000);
6786
- return Math.floor(Math.random() * (max - min) + min);
6787
- }
6788
- function randomId() {
6789
- return generateUUIDv4();
6790
- }
6791
- function hex(bytes) {
6792
- let s = '';
6793
- for (let i = 0; i < bytes.length; i++) {
6794
- s += bytes[i].toString(16).padStart(2, '0');
6795
- }
6796
- return s;
6797
- }
6798
- // https://tools.ietf.org/html/rfc4122
6799
- function generateUUIDv4() {
6800
- const bytes = getRandomBytes(16);
6801
- bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
6802
- bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
6803
- return (hex(bytes.subarray(0, 4)) +
6804
- '-' +
6805
- hex(bytes.subarray(4, 6)) +
6806
- '-' +
6807
- hex(bytes.subarray(6, 8)) +
6808
- '-' +
6809
- hex(bytes.subarray(8, 10)) +
6810
- '-' +
6811
- hex(bytes.subarray(10, 16)));
6812
- }
6813
- function getRandomValuesWithMathRandom(bytes) {
6814
- const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
6815
- for (let i = 0; i < bytes.length; i++) {
6816
- bytes[i] = Math.random() * max;
6817
- }
6818
- }
6819
- const getRandomValues = (() => {
6820
- if (typeof crypto !== 'undefined' &&
6821
- typeof (crypto === null || crypto === void 0 ? void 0 : crypto.getRandomValues) !== 'undefined') {
6822
- return crypto.getRandomValues.bind(crypto);
6823
- }
6824
- else if (typeof msCrypto !== 'undefined') {
6825
- return msCrypto.getRandomValues.bind(msCrypto);
6826
- }
6827
- else {
6828
- return getRandomValuesWithMathRandom;
6829
- }
6830
- })();
6831
- function getRandomBytes(length) {
6832
- const bytes = new Uint8Array(length);
6833
- getRandomValues(bytes);
6834
- return bytes;
6835
- }
6836
- function convertErrorToJson(err) {
6837
- const jsonObj = {};
6838
- if (!err)
6839
- return jsonObj;
6840
- try {
6841
- Object.getOwnPropertyNames(err).forEach((key) => {
6842
- jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
6843
- });
6844
- }
6845
- catch (_) {
6846
- return {
6847
- error: 'failed to serialize the error',
6848
- };
6849
- }
6850
- return jsonObj;
6851
- }
6852
- /**
6853
- * isOnline safely return the navigator.online value for browser env
6854
- * if navigator is not in global object, it always return true
6855
- */
6856
- function isOnline() {
6857
- const nav = typeof navigator !== 'undefined'
6858
- ? navigator
6859
- : typeof window !== 'undefined' && window.navigator
6860
- ? window.navigator
6861
- : undefined;
6862
- if (!nav) {
6863
- console.warn('isOnline failed to access window.navigator and assume browser is online');
6864
- return true;
6865
- }
6866
- // RN navigator has undefined for onLine
6867
- if (typeof nav.onLine !== 'boolean') {
6868
- return true;
6869
- }
6870
- return nav.onLine;
6871
- }
6872
- /**
6873
- * listenForConnectionChanges - Adds an event listener fired on browser going online or offline
6874
- */
6875
- function addConnectionEventListeners(cb) {
6876
- if (typeof window !== 'undefined' && window.addEventListener) {
6877
- window.addEventListener('offline', cb);
6878
- window.addEventListener('online', cb);
6879
- }
6880
- }
6881
- function removeConnectionEventListeners(cb) {
6882
- if (typeof window !== 'undefined' && window.removeEventListener) {
6883
- window.removeEventListener('offline', cb);
6884
- window.removeEventListener('online', cb);
6885
- }
6886
- }
6887
-
6888
7121
  class InsightMetrics {
6889
7122
  constructor() {
6890
7123
  this.connectionStartTimestamp = null;
@@ -8495,7 +8728,7 @@ class StreamClient {
8495
8728
  }
8496
8729
  getUserAgent() {
8497
8730
  return (this.userAgent ||
8498
- `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${"0.0.1-alpha.80"}`);
8731
+ `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${"0.0.1-alpha.82"}`);
8499
8732
  }
8500
8733
  setUserAgent(userAgent) {
8501
8734
  this.userAgent = userAgent;
@@ -8556,39 +8789,17 @@ class StreamVideoClient {
8556
8789
  // @ts-expect-error
8557
8790
  user, tokenOrProvider);
8558
8791
  this.callDropScheduler = new CallDropScheduler(this.writeableStateStore, this.callConfig);
8559
- this.on('call.created',
8560
- // @ts-expect-error until we sort out the types
8561
- watchCallCreated(this.writeableStateStore, this.streamClient));
8562
- this.on('call.accepted',
8563
- // @ts-expect-error until we sort out the types
8564
- watchCallAccepted(this.writeableStateStore));
8565
- this.on('call.rejected',
8566
- // @ts-expect-error until we sort out the types
8567
- watchCallRejected(this.writeableStateStore));
8568
- this.on('call.cancelled',
8569
- // @ts-expect-error until we sort out the types
8570
- watchCallCancelled(this.writeableStateStore));
8571
- this.on('call.permission_request',
8572
- // @ts-expect-error until we sort out the types
8573
- watchCallPermissionRequest(this.writeableStateStore));
8574
- this.on('call.permissions_updated',
8575
- // @ts-expect-error until we sort out the types
8576
- watchCallPermissionsUpdated(this.writeableStateStore));
8577
- this.on('call.blocked_user',
8578
- // @ts-expect-error until we sort out the types
8579
- watchBlockedUser(this.writeableStateStore));
8580
- this.on('call.unblocked_user',
8581
- // @ts-expect-error until we sort out the types
8582
- watchUnblockedUser(this.writeableStateStore));
8583
- this.on('call.recording_started',
8584
- // @ts-expect-error until we sort out the types
8585
- watchCallRecordingStarted(this.writeableStateStore));
8586
- this.on('call.recording_stopped',
8587
- // @ts-expect-error until we sort out the types
8588
- watchCallRecordingStopped(this.writeableStateStore));
8589
- this.on('call.reaction_new',
8590
- // @ts-expect-error until we sort out the types
8591
- watchNewReactions(this.writeableStateStore));
8792
+ this.on('call.created', watchCallCreated(this.writeableStateStore, this.streamClient));
8793
+ this.on('call.accepted', watchCallAccepted(this.writeableStateStore));
8794
+ this.on('call.rejected', watchCallRejected(this.writeableStateStore));
8795
+ this.on('call.ended', watchCallCancelled(this.writeableStateStore));
8796
+ this.on('call.permission_request', watchCallPermissionRequest(this.writeableStateStore));
8797
+ this.on('call.permissions_updated', watchCallPermissionsUpdated(this.writeableStateStore));
8798
+ this.on('call.blocked_user', watchBlockedUser(this.writeableStateStore));
8799
+ this.on('call.unblocked_user', watchUnblockedUser(this.writeableStateStore));
8800
+ this.on('call.recording_started', watchCallRecordingStarted(this.writeableStateStore));
8801
+ this.on('call.recording_stopped', watchCallRecordingStopped(this.writeableStateStore));
8802
+ this.on('call.reaction_new', watchNewReactions(this.writeableStateStore));
8592
8803
  this.writeableStateStore.setCurrentValue(this.writeableStateStore.connectedUserSubject, user);
8593
8804
  });
8594
8805
  /**
@@ -9045,5 +9256,5 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
9045
9256
  };
9046
9257
  };
9047
9258
 
9048
- export { browsers as Browsers, CALL_CONFIG, Call, CallState, CallType, CallTypes, DeviceFieldsRequestPushProviderEnum, ErrorFromResponse, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getScreenShareStream, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
9259
+ export { browsers as Browsers, CALL_CONFIG, Call, CallState, CallType, CallTypes, CallingState, DeviceFieldsRequestPushProviderEnum, ErrorFromResponse, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getScreenShareStream, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
9049
9260
  //# sourceMappingURL=index.browser.es.js.map