@stream-io/video-client 1.35.1 → 1.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/index.browser.es.js +382 -329
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +404 -333
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +382 -329
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +3 -2
  9. package/dist/src/StreamVideoClient.d.ts +3 -2
  10. package/dist/src/coordinator/connection/client.d.ts +3 -2
  11. package/dist/src/coordinator/connection/connection.d.ts +2 -1
  12. package/dist/src/coordinator/connection/types.d.ts +17 -1
  13. package/dist/src/devices/DeviceManager.d.ts +2 -2
  14. package/dist/src/logger.d.ts +9 -6
  15. package/dist/src/rpc/createClient.d.ts +3 -2
  16. package/dist/src/rtc/BasePeerConnection.d.ts +7 -4
  17. package/dist/src/rtc/Publisher.d.ts +3 -3
  18. package/dist/src/rtc/codecs.d.ts +3 -1
  19. package/dist/src/rtc/helpers/sdp.d.ts +8 -0
  20. package/dist/src/rtc/types.d.ts +4 -5
  21. package/dist/src/store/CallState.d.ts +1 -1
  22. package/dist/src/types.d.ts +6 -0
  23. package/package.json +3 -2
  24. package/src/Call.ts +49 -68
  25. package/src/StreamSfuClient.ts +11 -11
  26. package/src/StreamVideoClient.ts +19 -21
  27. package/src/coordinator/connection/client.ts +21 -30
  28. package/src/coordinator/connection/connection.ts +5 -4
  29. package/src/coordinator/connection/location.ts +4 -4
  30. package/src/coordinator/connection/types.ts +21 -2
  31. package/src/devices/BrowserPermission.ts +5 -5
  32. package/src/devices/CameraManager.ts +3 -4
  33. package/src/devices/DeviceManager.ts +11 -11
  34. package/src/devices/MicrophoneManager.ts +8 -8
  35. package/src/devices/devices.ts +18 -14
  36. package/src/events/call.ts +6 -9
  37. package/src/events/internal.ts +4 -4
  38. package/src/events/mutes.ts +3 -8
  39. package/src/helpers/DynascaleManager.ts +9 -9
  40. package/src/helpers/RNSpeechDetector.ts +5 -5
  41. package/src/helpers/clientUtils.ts +1 -3
  42. package/src/helpers/ensureExhausted.ts +2 -2
  43. package/src/logger.ts +9 -34
  44. package/src/rpc/__tests__/createClient.test.ts +5 -1
  45. package/src/rpc/createClient.ts +4 -3
  46. package/src/rpc/retryable.ts +4 -2
  47. package/src/rtc/BasePeerConnection.ts +26 -24
  48. package/src/rtc/Dispatcher.ts +4 -4
  49. package/src/rtc/IceTrickleBuffer.ts +5 -5
  50. package/src/rtc/Publisher.ts +21 -13
  51. package/src/rtc/Subscriber.ts +22 -17
  52. package/src/rtc/__tests__/Publisher.test.ts +12 -8
  53. package/src/rtc/codecs.ts +13 -2
  54. package/src/rtc/helpers/__tests__/sdp.codecs.test.ts +628 -0
  55. package/src/rtc/helpers/sdp.ts +82 -0
  56. package/src/rtc/signal.ts +7 -7
  57. package/src/rtc/types.ts +4 -4
  58. package/src/stats/CallStateStatsReporter.ts +4 -4
  59. package/src/stats/SfuStatsReporter.ts +6 -6
  60. package/src/store/CallState.ts +3 -3
  61. package/src/store/rxUtils.ts +4 -2
  62. package/src/store/stateStore.ts +6 -6
  63. package/src/types.ts +6 -0
package/src/Call.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { StreamSfuClient } from './StreamSfuClient';
2
2
  import {
3
+ BasePeerConnectionOpts,
3
4
  Dispatcher,
4
5
  getGenericSdp,
5
6
  isAudioTrackType,
@@ -25,6 +26,7 @@ import {
25
26
  createSubscription,
26
27
  getCurrentValue,
27
28
  } from './store/rxUtils';
29
+ import { ScopedLogger, videoLoggerSystem } from './logger';
28
30
  import type {
29
31
  AcceptCallResponse,
30
32
  BlockUserRequest,
@@ -117,6 +119,7 @@ import {
117
119
  ClientDetails,
118
120
  Codec,
119
121
  ParticipantSource,
122
+ PeerType,
120
123
  PublishOption,
121
124
  SubscribeOption,
122
125
  TrackType,
@@ -139,12 +142,10 @@ import {
139
142
  AllCallEvents,
140
143
  CallEventListener,
141
144
  ErrorFromResponse,
142
- Logger,
143
145
  RejectReason,
144
146
  StreamCallEvent,
145
147
  } from './coordinator/connection/types';
146
148
  import { getClientDetails } from './helpers/client-details';
147
- import { getLogger } from './logger';
148
149
  import {
149
150
  CameraManager,
150
151
  MicrophoneManager,
@@ -229,7 +230,7 @@ export class Call {
229
230
  */
230
231
  readonly permissionsContext = new PermissionsContext();
231
232
  readonly tracer = new Tracer(null);
232
- readonly logger: Logger;
233
+ readonly logger: ScopedLogger;
233
234
 
234
235
  /**
235
236
  * The event dispatcher instance dedicated to this Call instance.
@@ -312,7 +313,7 @@ export class Call {
312
313
  this.streamClient = streamClient;
313
314
  this.clientStore = clientStore;
314
315
  this.streamClientBasePath = `/call/${this.type}/${this.id}`;
315
- this.logger = getLogger(['Call']);
316
+ this.logger = videoLoggerSystem.getLogger('Call');
316
317
 
317
318
  const callTypeConfig = CallTypes.get(type);
318
319
  const participantSorter =
@@ -396,9 +397,9 @@ export class Call {
396
397
  if (!blockedUserIds || blockedUserIds.length === 0) return;
397
398
  const currentUserId = this.currentUserId;
398
399
  if (currentUserId && blockedUserIds.includes(currentUserId)) {
399
- this.logger('info', 'Leaving call because of being blocked');
400
+ this.logger.info('Leaving call because of being blocked');
400
401
  await this.leave({ message: 'user blocked' }).catch((err) => {
401
- this.logger('error', 'Error leaving call after being blocked', err);
402
+ this.logger.error('Error leaving call after being blocked', err);
402
403
  });
403
404
  }
404
405
  }),
@@ -442,8 +443,7 @@ export class Call {
442
443
  !hasPending(this.joinLeaveConcurrencyTag)
443
444
  ) {
444
445
  this.leave().catch(() => {
445
- this.logger(
446
- 'error',
446
+ this.logger.error(
447
447
  'Could not leave a call that was accepted or rejected elsewhere',
448
448
  );
449
449
  });
@@ -526,8 +526,7 @@ export class Call {
526
526
  break;
527
527
  }
528
528
  } catch (err) {
529
- this.logger(
530
- 'error',
529
+ this.logger.error(
531
530
  `Can't disable mic/camera/screenshare after revoked permissions`,
532
531
  err,
533
532
  );
@@ -886,12 +885,12 @@ export class Call {
886
885
  maxJoinRetries = Math.max(maxJoinRetries, 1);
887
886
  for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
888
887
  try {
889
- this.logger('trace', `Joining call (${attempt})`, this.cid);
888
+ this.logger.trace(`Joining call (${attempt})`, this.cid);
890
889
  await this.doJoin(data);
891
890
  delete joinData.migrating_from;
892
891
  break;
893
892
  } catch (err) {
894
- this.logger('warn', `Failed to join call (${attempt})`, this.cid);
893
+ this.logger.warn(`Failed to join call (${attempt})`, this.cid);
895
894
  if (err instanceof ErrorFromResponse && err.unrecoverable) {
896
895
  // if the error is unrecoverable, we should not retry as that signals
897
896
  // that connectivity is good, but the coordinator doesn't allow the user
@@ -927,7 +926,7 @@ export class Call {
927
926
 
928
927
  this.joinCallData = data;
929
928
 
930
- this.logger('debug', 'Starting join flow');
929
+ this.logger.debug('Starting join flow');
931
930
  this.state.setCallingState(CallingState.JOINING);
932
931
 
933
932
  const performingMigration =
@@ -990,9 +989,11 @@ export class Call {
990
989
  // prepare a generic SDP and send it to the SFU.
991
990
  // these are throw-away SDPs that the SFU will use to determine
992
991
  // the capabilities of the client (codec support, etc.)
992
+ const { dangerouslyForceCodec, fmtpLine, subscriberFmtpLine } =
993
+ this.clientPublishOptions || {};
993
994
  const [subscriberSdp, publisherSdp] = await Promise.all([
994
- getGenericSdp('recvonly'),
995
- getGenericSdp('sendonly'),
995
+ getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
996
+ getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
996
997
  ]);
997
998
  const isReconnecting =
998
999
  this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
@@ -1031,7 +1032,7 @@ export class Call {
1031
1032
  );
1032
1033
  }
1033
1034
  } catch (error) {
1034
- this.logger('warn', 'Join SFU request failed', error);
1035
+ this.logger.warn('Join SFU request failed', error);
1035
1036
  sfuClient.close(
1036
1037
  StreamSfuClient.JOIN_FAILED,
1037
1038
  'Join request failed, connection considered unhealthy',
@@ -1103,7 +1104,7 @@ export class Call {
1103
1104
  this.reconnectStrategy = WebsocketReconnectStrategy.UNSPECIFIED;
1104
1105
  this.reconnectReason = '';
1105
1106
 
1106
- this.logger('info', `Joined call ${this.cid}`);
1107
+ this.logger.info(`Joined call ${this.cid}`);
1107
1108
  };
1108
1109
 
1109
1110
  /**
@@ -1240,20 +1241,23 @@ export class Call {
1240
1241
  if (closePreviousInstances && this.subscriber) {
1241
1242
  this.subscriber.dispose();
1242
1243
  }
1243
- this.subscriber = new Subscriber({
1244
+ const basePeerConnectionOptions: BasePeerConnectionOpts = {
1244
1245
  sfuClient,
1245
1246
  dispatcher: this.dispatcher,
1246
1247
  state: this.state,
1247
1248
  connectionConfig,
1248
1249
  tag: sfuClient.tag,
1249
1250
  enableTracing,
1250
- onReconnectionNeeded: (kind, reason) => {
1251
+ clientPublishOptions: this.clientPublishOptions,
1252
+ onReconnectionNeeded: (kind, reason, peerType) => {
1251
1253
  this.reconnect(kind, reason).catch((err) => {
1252
- const message = `[Reconnect] Error reconnecting after a subscriber error: ${reason}`;
1253
- this.logger('warn', message, err);
1254
+ const message = `[Reconnect] Error reconnecting, after a ${PeerType[peerType]} error: ${reason}`;
1255
+ this.logger.warn(message, err);
1254
1256
  });
1255
1257
  },
1256
- });
1258
+ };
1259
+
1260
+ this.subscriber = new Subscriber(basePeerConnectionOptions);
1257
1261
 
1258
1262
  // anonymous users can't publish anything hence, there is no need
1259
1263
  // to create Publisher Peer Connection for them
@@ -1262,21 +1266,7 @@ export class Call {
1262
1266
  if (closePreviousInstances && this.publisher) {
1263
1267
  this.publisher.dispose();
1264
1268
  }
1265
- this.publisher = new Publisher({
1266
- sfuClient,
1267
- dispatcher: this.dispatcher,
1268
- state: this.state,
1269
- connectionConfig,
1270
- publishOptions,
1271
- tag: sfuClient.tag,
1272
- enableTracing,
1273
- onReconnectionNeeded: (kind, reason) => {
1274
- this.reconnect(kind, reason).catch((err) => {
1275
- const message = `[Reconnect] Error reconnecting after a publisher error: ${reason}`;
1276
- this.logger('warn', message, err);
1277
- });
1278
- },
1279
- });
1269
+ this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
1280
1270
  }
1281
1271
 
1282
1272
  this.statsReporter?.stop();
@@ -1358,7 +1348,7 @@ export class Call {
1358
1348
  sfuClient: StreamSfuClient,
1359
1349
  reason: string,
1360
1350
  ) => {
1361
- this.logger('debug', '[Reconnect] SFU signal connection closed');
1351
+ this.logger.debug('[Reconnect] SFU signal connection closed');
1362
1352
  const { callingState } = this.state;
1363
1353
  if (
1364
1354
  // SFU WS closed before we finished current join,
@@ -1381,7 +1371,7 @@ export class Call {
1381
1371
  ? WebsocketReconnectStrategy.FAST
1382
1372
  : WebsocketReconnectStrategy.REJOIN;
1383
1373
  this.reconnect(strategy, reason).catch((err) => {
1384
- this.logger('warn', '[Reconnect] Error reconnecting', err);
1374
+ this.logger.warn('[Reconnect] Error reconnecting', err);
1385
1375
  });
1386
1376
  };
1387
1377
 
@@ -1427,8 +1417,7 @@ export class Call {
1427
1417
  reconnectingTime / 1000 > this.disconnectionTimeoutSeconds;
1428
1418
 
1429
1419
  if (shouldGiveUpReconnecting) {
1430
- this.logger(
1431
- 'warn',
1420
+ this.logger.warn(
1432
1421
  '[Reconnect] Stopping reconnection attempts after reaching disconnection timeout',
1433
1422
  );
1434
1423
  await markAsReconnectingFailed();
@@ -1445,16 +1434,14 @@ export class Call {
1445
1434
  // wait until the network is available
1446
1435
  await this.networkAvailableTask?.promise;
1447
1436
 
1448
- this.logger(
1449
- 'info',
1437
+ this.logger.info(
1450
1438
  `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`,
1451
1439
  );
1452
1440
 
1453
1441
  switch (this.reconnectStrategy) {
1454
1442
  case WebsocketReconnectStrategy.UNSPECIFIED:
1455
1443
  case WebsocketReconnectStrategy.DISCONNECT:
1456
- this.logger(
1457
- 'debug',
1444
+ this.logger.debug(
1458
1445
  `[Reconnect] No-op strategy ${currentStrategy}`,
1459
1446
  );
1460
1447
  break;
@@ -1477,8 +1464,7 @@ export class Call {
1477
1464
  break; // do-while loop, reconnection worked, exit the loop
1478
1465
  } catch (error) {
1479
1466
  if (this.state.callingState === CallingState.OFFLINE) {
1480
- this.logger(
1481
- 'debug',
1467
+ this.logger.debug(
1482
1468
  `[Reconnect] Can't reconnect while offline, stopping reconnection attempts`,
1483
1469
  );
1484
1470
  break;
@@ -1486,8 +1472,7 @@ export class Call {
1486
1472
  // network change event will trigger the reconnection
1487
1473
  }
1488
1474
  if (error instanceof ErrorFromResponse && error.unrecoverable) {
1489
- this.logger(
1490
- 'warn',
1475
+ this.logger.warn(
1491
1476
  `[Reconnect] Can't reconnect due to coordinator unrecoverable error`,
1492
1477
  error,
1493
1478
  );
@@ -1520,8 +1505,7 @@ export class Call {
1520
1505
  : WebsocketReconnectStrategy.FAST;
1521
1506
  this.reconnectStrategy = nextStrategy;
1522
1507
 
1523
- this.logger(
1524
- 'info',
1508
+ this.logger.info(
1525
1509
  `[Reconnect] ${currentStrategy} (${this.reconnectAttempts}) failed. Attempting with ${WebsocketReconnectStrategy[nextStrategy]}`,
1526
1510
  error,
1527
1511
  );
@@ -1531,7 +1515,7 @@ export class Call {
1531
1515
  this.state.callingState !== CallingState.RECONNECTING_FAILED &&
1532
1516
  this.state.callingState !== CallingState.LEFT
1533
1517
  );
1534
- this.logger('info', '[Reconnect] Reconnection flow finished');
1518
+ this.logger.info('[Reconnect] Reconnection flow finished');
1535
1519
  });
1536
1520
  };
1537
1521
 
@@ -1633,7 +1617,7 @@ export class Call {
1633
1617
  // handles the legacy "goAway" event
1634
1618
  const unregisterGoAway = this.on('goAway', () => {
1635
1619
  this.reconnect(WebsocketReconnectStrategy.MIGRATE, 'goAway').catch(
1636
- (err) => this.logger('warn', '[Reconnect] Error reconnecting', err),
1620
+ (err) => this.logger.warn('[Reconnect] Error reconnecting', err),
1637
1621
  );
1638
1622
  });
1639
1623
 
@@ -1643,11 +1627,11 @@ export class Call {
1643
1627
  if (strategy === WebsocketReconnectStrategy.UNSPECIFIED) return;
1644
1628
  if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
1645
1629
  this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
1646
- this.logger('warn', `Can't leave call after disconnect request`, err);
1630
+ this.logger.warn(`Can't leave call after disconnect request`, err);
1647
1631
  });
1648
1632
  } else {
1649
1633
  this.reconnect(strategy, error?.message || 'SFU Error').catch((err) => {
1650
- this.logger('warn', '[Reconnect] Error reconnecting', err);
1634
+ this.logger.warn('[Reconnect] Error reconnecting', err);
1651
1635
  });
1652
1636
  }
1653
1637
  });
@@ -1657,7 +1641,7 @@ export class Call {
1657
1641
  (e) => {
1658
1642
  this.tracer.trace('network.changed', e);
1659
1643
  if (!e.online) {
1660
- this.logger('debug', '[Reconnect] Going offline');
1644
+ this.logger.debug('[Reconnect] Going offline');
1661
1645
  if (!this.hasJoinedOnce) return;
1662
1646
  this.lastOfflineTimestamp = Date.now();
1663
1647
  // create a new task that would resolve when the network is available
@@ -1674,8 +1658,7 @@ export class Call {
1674
1658
  }
1675
1659
 
1676
1660
  this.reconnect(strategy, 'Going online').catch((err) => {
1677
- this.logger(
1678
- 'warn',
1661
+ this.logger.warn(
1679
1662
  '[Reconnect] Error reconnecting after going online',
1680
1663
  err,
1681
1664
  );
@@ -1685,7 +1668,7 @@ export class Call {
1685
1668
  this.sfuStatsReporter?.stop();
1686
1669
  this.state.setCallingState(CallingState.OFFLINE);
1687
1670
  } else {
1688
- this.logger('debug', '[Reconnect] Going online');
1671
+ this.logger.debug('[Reconnect] Going online');
1689
1672
  this.sfuClient?.close(
1690
1673
  StreamSfuClient.DISPOSE_OLD_SOCKET,
1691
1674
  'Closing WS to reconnect after going online',
@@ -1875,14 +1858,12 @@ export class Call {
1875
1858
  * @param options the options to use.
1876
1859
  */
1877
1860
  updatePublishOptions = (options: ClientPublishOptions) => {
1878
- this.logger(
1879
- 'warn',
1861
+ this.logger.warn(
1880
1862
  '[call.updatePublishOptions]: You are manually overriding the publish options for this call. ' +
1881
1863
  'This is not recommended, and it can cause call stability/compatibility issues. Use with caution.',
1882
1864
  );
1883
1865
  if (this.state.callingState === CallingState.JOINED) {
1884
- this.logger(
1885
- 'warn',
1866
+ this.logger.warn(
1886
1867
  'Updating publish options after joining the call does not have an effect',
1887
1868
  );
1888
1869
  }
@@ -1896,7 +1877,7 @@ export class Call {
1896
1877
  */
1897
1878
  notifyNoiseCancellationStarting = async () => {
1898
1879
  return this.sfuClient?.startNoiseCancellation().catch((err) => {
1899
- this.logger('warn', 'Failed to notify start of noise cancellation', err);
1880
+ this.logger.warn('Failed to notify start of noise cancellation', err);
1900
1881
  });
1901
1882
  };
1902
1883
 
@@ -1907,7 +1888,7 @@ export class Call {
1907
1888
  */
1908
1889
  notifyNoiseCancellationStopped = async () => {
1909
1890
  return this.sfuClient?.stopNoiseCancellation().catch((err) => {
1910
- this.logger('warn', 'Failed to notify stop of noise cancellation', err);
1891
+ this.logger.warn('Failed to notify stop of noise cancellation', err);
1911
1892
  });
1912
1893
  };
1913
1894
 
@@ -2489,7 +2470,7 @@ export class Call {
2489
2470
  reason: 'timeout',
2490
2471
  message: `ringing timeout - ${this.isCreatedByMe ? 'no one accepted' : `user didn't interact with incoming call screen`}`,
2491
2472
  }).catch((err) => {
2492
- this.logger('error', 'Failed to drop call', err);
2473
+ this.logger.error('Failed to drop call', err);
2493
2474
  });
2494
2475
  }, timeoutInMs);
2495
2476
  };
@@ -2653,10 +2634,10 @@ export class Call {
2653
2634
  publish: boolean,
2654
2635
  ) => {
2655
2636
  await this.camera.apply(settings.video, publish).catch((err) => {
2656
- this.logger('warn', 'Camera init failed', err);
2637
+ this.logger.warn('Camera init failed', err);
2657
2638
  });
2658
2639
  await this.microphone.apply(settings.audio, publish).catch((err) => {
2659
- this.logger('warn', 'Mic init failed', err);
2640
+ this.logger.warn('Mic init failed', err);
2660
2641
  });
2661
2642
  };
2662
2643
 
@@ -29,8 +29,7 @@ import { ICETrickle } from './gen/video/sfu/models/models';
29
29
  import { StreamClient } from './coordinator/connection/client';
30
30
  import { generateUUIDv4 } from './coordinator/connection/utils';
31
31
  import { Credentials } from './gen/coordinator';
32
- import { Logger } from './coordinator/connection/types';
33
- import { getLogger, getLogLevel } from './logger';
32
+ import { ScopedLogger, videoLoggerSystem } from './logger';
34
33
  import {
35
34
  makeSafePromise,
36
35
  PromiseWithResolvers,
@@ -152,7 +151,7 @@ export class StreamSfuClient {
152
151
  private readonly unsubscribeIceTrickle: () => void;
153
152
  private readonly unsubscribeNetworkChanged: () => void;
154
153
  private readonly onSignalClose: ((reason: string) => void) | undefined;
155
- private readonly logger: Logger;
154
+ private readonly logger: ScopedLogger;
156
155
  readonly tag: string;
157
156
  private readonly credentials: Credentials;
158
157
  private readonly dispatcher: Dispatcher;
@@ -219,7 +218,7 @@ export class StreamSfuClient {
219
218
  this.edgeName = server.edge_name;
220
219
  this.joinResponseTimeout = joinResponseTimeout;
221
220
  this.tag = tag;
222
- this.logger = getLogger(['SfuClient', tag]);
221
+ this.logger = videoLoggerSystem.getLogger('SfuClient', { tags: [tag] });
223
222
  this.tracer = enableTracing
224
223
  ? new Tracer(`${tag}-${this.edgeName}`)
225
224
  : undefined;
@@ -228,7 +227,8 @@ export class StreamSfuClient {
228
227
  interceptors: [
229
228
  withHeaders({ Authorization: `Bearer ${token}` }),
230
229
  this.tracer && withRequestTracer(this.tracer.trace),
231
- getLogLevel() === 'trace' && withRequestLogger(this.logger, 'trace'),
230
+ this.logger.getLogLevel() === 'trace' &&
231
+ withRequestLogger(this.logger, 'trace'),
232
232
  ].filter((v) => !!v),
233
233
  });
234
234
 
@@ -346,7 +346,7 @@ export class StreamSfuClient {
346
346
  close = (code: number = StreamSfuClient.NORMAL_CLOSURE, reason?: string) => {
347
347
  this.isClosingClean = code !== StreamSfuClient.ERROR_CONNECTION_UNHEALTHY;
348
348
  if (this.signalWs.readyState === WebSocket.OPEN) {
349
- this.logger('debug', `Closing SFU WS connection: ${code} - ${reason}`);
349
+ this.logger.debug(`Closing SFU WS connection: ${code} - ${reason}`);
350
350
  this.signalWs.close(code, `js-client: ${reason}`);
351
351
  this.signalWs.removeEventListener('close', this.handleWebSocketClose);
352
352
  }
@@ -354,7 +354,7 @@ export class StreamSfuClient {
354
354
  };
355
355
 
356
356
  private dispose = () => {
357
- this.logger('debug', 'Disposing SFU client');
357
+ this.logger.debug('Disposing SFU client');
358
358
  this.unsubscribeIceTrickle();
359
359
  this.unsubscribeNetworkChanged();
360
360
  clearInterval(this.keepAliveInterval);
@@ -375,7 +375,7 @@ export class StreamSfuClient {
375
375
  await this.joinTask;
376
376
  await this.notifyLeave(reason);
377
377
  } catch (err) {
378
- this.logger('debug', 'Error notifying SFU about leaving call', err);
378
+ this.logger.debug('Error notifying SFU about leaving call', err);
379
379
  }
380
380
 
381
381
  this.close(StreamSfuClient.NORMAL_CLOSURE, reason.substring(0, 115));
@@ -557,10 +557,10 @@ export class StreamSfuClient {
557
557
  await this.signalReady(); // wait for the signal ws to be open
558
558
  const msgJson = SfuRequest.toJson(message);
559
559
  if (this.signalWs.readyState !== WebSocket.OPEN) {
560
- this.logger('debug', 'Signal WS is not open. Skipping message', msgJson);
560
+ this.logger.debug('Signal WS is not open. Skipping message', msgJson);
561
561
  return;
562
562
  }
563
- this.logger('debug', `Sending message to: ${this.edgeName}`, msgJson);
563
+ this.logger.debug(`Sending message to: ${this.edgeName}`, msgJson);
564
564
  this.signalWs.send(SfuRequest.toBinary(message));
565
565
  };
566
566
 
@@ -569,7 +569,7 @@ export class StreamSfuClient {
569
569
  timers.clearInterval(this.keepAliveInterval);
570
570
  this.keepAliveInterval = timers.setInterval(() => {
571
571
  this.ping().catch((e) => {
572
- this.logger('error', 'Error sending healthCheckRequest to SFU', e);
572
+ this.logger.error('Error sending healthCheckRequest to SFU', e);
573
573
  });
574
574
  }, this.pingIntervalInMs);
575
575
  };
@@ -24,7 +24,6 @@ import type {
24
24
  import {
25
25
  AllClientEvents,
26
26
  ClientEventListener,
27
- Logger,
28
27
  StreamClientOptions,
29
28
  TokenOrProvider,
30
29
  TokenProvider,
@@ -38,7 +37,7 @@ import {
38
37
  getCallInitConcurrencyTag,
39
38
  getInstanceKey,
40
39
  } from './helpers/clientUtils';
41
- import { getLogger, logToConsole, setLogger } from './logger';
40
+ import { logToConsole, ScopedLogger, videoLoggerSystem } from './logger';
42
41
  import { withoutConcurrency } from './helpers/concurrency';
43
42
  import { enableTimerWorker } from './timers';
44
43
 
@@ -63,7 +62,7 @@ export class StreamVideoClient {
63
62
  * @deprecated use the `client.state` getter.
64
63
  */
65
64
  readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
66
- readonly logger: Logger;
65
+ readonly logger: ScopedLogger;
67
66
 
68
67
  protected readonly writeableStateStore: StreamVideoWriteableStateStore;
69
68
  streamClient: StreamClient;
@@ -94,9 +93,13 @@ export class StreamVideoClient {
94
93
  if (clientOptions?.enableTimerWorker) enableTimerWorker();
95
94
 
96
95
  const rootLogger = clientOptions?.logger || logToConsole;
97
- setLogger(rootLogger, clientOptions?.logLevel || 'warn');
98
96
 
99
- this.logger = getLogger(['client']);
97
+ videoLoggerSystem.configureLoggers({
98
+ default: { sink: rootLogger, level: clientOptions?.logLevel || 'warn' },
99
+ ...clientOptions?.logOptions,
100
+ });
101
+
102
+ this.logger = videoLoggerSystem.getLogger('client');
100
103
  this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
101
104
 
102
105
  this.streamClient = createCoordinatorClient(apiKey, clientOptions);
@@ -113,7 +116,7 @@ export class StreamVideoClient {
113
116
 
114
117
  const tokenOrProvider = createTokenOrProvider(apiKeyOrArgs);
115
118
  this.connectUser(user, tokenOrProvider).catch((err) => {
116
- this.logger('error', 'Failed to connect', err);
119
+ this.logger.error('Failed to connect', err);
117
120
  });
118
121
  }
119
122
  }
@@ -149,8 +152,7 @@ export class StreamVideoClient {
149
152
  private registerClientInstance = (apiKey: string, user: User) => {
150
153
  const instanceKey = getInstanceKey(apiKey, user);
151
154
  if (StreamVideoClient._instances.has(instanceKey)) {
152
- this.logger(
153
- 'warn',
155
+ this.logger.warn(
154
156
  `A StreamVideoClient already exists for ${user.id}; Prefer using getOrCreateInstance method`,
155
157
  );
156
158
  }
@@ -178,13 +180,13 @@ export class StreamVideoClient {
178
180
  .map((call) => call.cid);
179
181
  if (callsToReWatch.length <= 0) return;
180
182
 
181
- this.logger('info', `Rewatching calls ${callsToReWatch.join(', ')}`);
183
+ this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
182
184
  this.queryCalls({
183
185
  watch: true,
184
186
  filter_conditions: { cid: { $in: callsToReWatch } },
185
187
  sort: [{ field: 'cid', direction: 1 }],
186
188
  }).catch((err) => {
187
- this.logger('error', 'Failed to re-watch calls', err);
189
+ this.logger.error('Failed to re-watch calls', err);
188
190
  });
189
191
  }),
190
192
  );
@@ -198,7 +200,7 @@ export class StreamVideoClient {
198
200
  */
199
201
  private initCallFromEvent = async (e: CallCreatedEvent | CallRingEvent) => {
200
202
  if (this.state.connectedUser?.id === e.call.created_by.id) {
201
- this.logger('debug', `Ignoring ${e.type} event sent by the current user`);
203
+ this.logger.debug(`Ignoring ${e.type} event sent by the current user`);
202
204
  return;
203
205
  }
204
206
 
@@ -210,8 +212,7 @@ export class StreamVideoClient {
210
212
  if (call) {
211
213
  if (ringing) {
212
214
  if (this.shouldRejectCall(call.cid)) {
213
- this.logger(
214
- 'info',
215
+ this.logger.info(
215
216
  `Leaving call with busy reject reason ${call.cid} because user is busy`,
216
217
  );
217
218
  // remove the instance from the state store
@@ -238,10 +239,7 @@ export class StreamVideoClient {
238
239
 
239
240
  if (ringing) {
240
241
  if (this.shouldRejectCall(call.cid)) {
241
- this.logger(
242
- 'info',
243
- `Rejecting call ${call.cid} because user is busy`,
244
- );
242
+ this.logger.info(`Rejecting call ${call.cid} because user is busy`);
245
243
  // call is not in the state store yet, so just reject api is enough
246
244
  await call.reject('busy');
247
245
  } else {
@@ -251,11 +249,11 @@ export class StreamVideoClient {
251
249
  } else {
252
250
  call.state.updateFromCallResponse(e.call);
253
251
  this.writeableStateStore.registerCall(call);
254
- this.logger('info', `New call created and registered: ${call.cid}`);
252
+ this.logger.info(`New call created and registered: ${call.cid}`);
255
253
  }
256
254
  });
257
255
  } catch (err) {
258
- this.logger('error', `Failed to init call from event ${e.type}`, err);
256
+ this.logger.error(`Failed to init call from event ${e.type}`, err);
259
257
  }
260
258
  };
261
259
 
@@ -288,12 +286,12 @@ export class StreamVideoClient {
288
286
  const errorQueue: Error[] = [];
289
287
  for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
290
288
  try {
291
- this.logger('trace', `Connecting user (${attempt})`, user);
289
+ this.logger.trace(`Connecting user (${attempt})`, user);
292
290
  return user.type === 'guest'
293
291
  ? await client.connectGuestUser(user)
294
292
  : await client.connectUser(user, tokenOrProvider);
295
293
  } catch (err) {
296
- this.logger('warn', `Failed to connect a user (${attempt})`, err);
294
+ this.logger.warn(`Failed to connect a user (${attempt})`, err);
297
295
  errorQueue.push(err as Error);
298
296
  if (attempt === maxConnectUserRetries - 1) {
299
297
  onConnectUserError?.(err as Error, errorQueue);