@stream-io/video-client 1.27.1 → 1.27.3

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.
package/dist/index.es.js CHANGED
@@ -5915,7 +5915,7 @@ const aggregate = (stats) => {
5915
5915
  return report;
5916
5916
  };
5917
5917
 
5918
- const version = "1.27.1";
5918
+ const version = "1.27.3";
5919
5919
  const [major, minor, patch] = version.split('.');
5920
5920
  let sdkInfo = {
5921
5921
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -7634,19 +7634,22 @@ class Subscriber extends BasePeerConnection {
7634
7634
  }
7635
7635
 
7636
7636
  const createWebSocketSignalChannel = (opts) => {
7637
- const { endpoint, onMessage, tag } = opts;
7637
+ const { endpoint, onMessage, tag, tracer } = opts;
7638
7638
  const logger = getLogger(['SfuClientWS', tag]);
7639
7639
  logger('debug', 'Creating signaling WS channel:', endpoint);
7640
7640
  const ws = new WebSocket(endpoint);
7641
7641
  ws.binaryType = 'arraybuffer'; // do we need this?
7642
7642
  ws.addEventListener('error', (e) => {
7643
7643
  logger('error', 'Signaling WS channel error', e);
7644
+ tracer?.trace('signal.ws.error', e);
7644
7645
  });
7645
7646
  ws.addEventListener('close', (e) => {
7646
7647
  logger('info', 'Signaling WS channel is closed', e);
7648
+ tracer?.trace('signal.ws.close', e);
7647
7649
  });
7648
7650
  ws.addEventListener('open', (e) => {
7649
7651
  logger('info', 'Signaling WS channel is open', e);
7652
+ tracer?.trace('signal.ws.open', e);
7650
7653
  });
7651
7654
  ws.addEventListener('message', (e) => {
7652
7655
  try {
@@ -7656,7 +7659,9 @@ const createWebSocketSignalChannel = (opts) => {
7656
7659
  onMessage(message);
7657
7660
  }
7658
7661
  catch (err) {
7659
- logger('error', 'Failed to decode a message. Check whether the Proto models match.', { event: e, error: err });
7662
+ const message = 'Failed to decode a message. Check whether the Proto models match.';
7663
+ logger('error', message, { event: e, error: err });
7664
+ tracer?.trace('signal.ws.message.error', message);
7660
7665
  }
7661
7666
  });
7662
7667
  return ws;
@@ -7928,6 +7933,7 @@ class StreamSfuClient {
7928
7933
  this.signalWs = createWebSocketSignalChannel({
7929
7934
  tag: this.tag,
7930
7935
  endpoint: `${this.credentials.server.ws_endpoint}?${new URLSearchParams(params).toString()}`,
7936
+ tracer: this.tracer,
7931
7937
  onMessage: (message) => {
7932
7938
  this.lastMessageTimestamp = new Date();
7933
7939
  this.scheduleConnectionCheck();
@@ -7938,9 +7944,13 @@ class StreamSfuClient {
7938
7944
  this.dispatcher.dispatch(message, this.tag);
7939
7945
  },
7940
7946
  });
7947
+ let timeoutId;
7941
7948
  this.signalReady = makeSafePromise(Promise.race([
7942
7949
  new Promise((resolve, reject) => {
7950
+ let didOpen = false;
7943
7951
  const onOpen = () => {
7952
+ didOpen = true;
7953
+ clearTimeout(timeoutId);
7944
7954
  this.signalWs.removeEventListener('open', onOpen);
7945
7955
  resolve(this.signalWs);
7946
7956
  };
@@ -7948,19 +7958,25 @@ class StreamSfuClient {
7948
7958
  this.signalWs.addEventListener('close', (e) => {
7949
7959
  this.handleWebSocketClose(e);
7950
7960
  // Normally, this shouldn't have any effect, because WS should never emit 'close'
7951
- // before emitting 'open'. However, strager things have happened, and we don't
7952
- // want to leave signalReady in pending state.
7953
- reject(new Error(`SFU WS closed or connection can't be established`));
7961
+ // before emitting 'open'. However, stranger things have happened, and we don't
7962
+ // want to leave signalReady in a pending state.
7963
+ const message = didOpen
7964
+ ? `SFU WS closed: ${e.code} ${e.reason}`
7965
+ : `SFU WS connection can't be established: ${e.code} ${e.reason}`;
7966
+ this.tracer?.trace('signal.close', message);
7967
+ clearTimeout(timeoutId);
7968
+ reject(new Error(message));
7954
7969
  });
7955
7970
  }),
7956
7971
  new Promise((resolve, reject) => {
7957
- setTimeout(() => reject(new Error('SFU WS connection timed out')), this.joinResponseTimeout);
7972
+ timeoutId = setTimeout(() => {
7973
+ const message = `SFU WS connection failed to open after ${this.joinResponseTimeout}ms`;
7974
+ this.tracer?.trace('signal.timeout', message);
7975
+ reject(new Error(message));
7976
+ }, this.joinResponseTimeout);
7958
7977
  }),
7959
7978
  ]));
7960
7979
  };
7961
- this.cleanUpWebSocket = () => {
7962
- this.signalWs.removeEventListener('close', this.handleWebSocketClose);
7963
- };
7964
7980
  this.handleWebSocketClose = (e) => {
7965
7981
  this.signalWs.removeEventListener('close', this.handleWebSocketClose);
7966
7982
  getTimers().clearInterval(this.keepAliveInterval);
@@ -7972,7 +7988,7 @@ class StreamSfuClient {
7972
7988
  if (this.signalWs.readyState === WebSocket.OPEN) {
7973
7989
  this.logger('debug', `Closing SFU WS connection: ${code} - ${reason}`);
7974
7990
  this.signalWs.close(code, `js-client: ${reason}`);
7975
- this.cleanUpWebSocket();
7991
+ this.signalWs.removeEventListener('close', this.handleWebSocketClose);
7976
7992
  }
7977
7993
  this.dispose();
7978
7994
  };
@@ -8070,7 +8086,6 @@ class StreamSfuClient {
8070
8086
  const current = this.joinResponseTask;
8071
8087
  let timeoutId = undefined;
8072
8088
  const unsubscribe = this.dispatcher.on('joinResponse', (joinResponse) => {
8073
- this.logger('debug', 'Received joinResponse', joinResponse);
8074
8089
  clearTimeout(timeoutId);
8075
8090
  unsubscribe();
8076
8091
  this.keepAlive();
@@ -8078,7 +8093,9 @@ class StreamSfuClient {
8078
8093
  });
8079
8094
  timeoutId = setTimeout(() => {
8080
8095
  unsubscribe();
8081
- current.reject(new Error('Waiting for "joinResponse" has timed out'));
8096
+ const message = `Waiting for "joinResponse" has timed out after ${this.joinResponseTimeout}ms`;
8097
+ this.tracer?.trace('joinRequestTimeout', message);
8098
+ current.reject(new Error(message));
8082
8099
  }, this.joinResponseTimeout);
8083
8100
  const joinRequest = SfuRequest.create({
8084
8101
  requestPayload: {
@@ -9422,6 +9439,25 @@ const CallTypes = new CallTypesRegistry([
9422
9439
  }),
9423
9440
  ]);
9424
9441
 
9442
+ /**
9443
+ * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
9444
+ *
9445
+ * @param stream MediaStream
9446
+ * @returns void
9447
+ */
9448
+ const disposeOfMediaStream = (stream) => {
9449
+ if (!stream.active)
9450
+ return;
9451
+ stream.getTracks().forEach((track) => {
9452
+ track.stop();
9453
+ });
9454
+ // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
9455
+ if (typeof stream.release === 'function') {
9456
+ // @ts-expect-error - release() is present in react-native-webrtc
9457
+ stream.release();
9458
+ }
9459
+ };
9460
+
9425
9461
  class BrowserPermission {
9426
9462
  constructor(permission) {
9427
9463
  this.permission = permission;
@@ -9819,24 +9855,6 @@ const deviceIds$ = typeof navigator !== 'undefined' &&
9819
9855
  typeof navigator.mediaDevices !== 'undefined'
9820
9856
  ? getDeviceChangeObserver().pipe(startWith(undefined), concatMap(() => navigator.mediaDevices.enumerateDevices()), shareReplay(1))
9821
9857
  : undefined;
9822
- /**
9823
- * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
9824
- *
9825
- * @param stream MediaStream
9826
- * @returns void
9827
- */
9828
- const disposeOfMediaStream = (stream) => {
9829
- if (!stream.active)
9830
- return;
9831
- stream.getTracks().forEach((track) => {
9832
- track.stop();
9833
- });
9834
- // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
9835
- if (typeof stream.release === 'function') {
9836
- // @ts-expect-error - release() is present in react-native-webrtc
9837
- stream.release();
9838
- }
9839
- };
9840
9858
  /**
9841
9859
  * Resolves `default` device id into the real device id. Some browsers (notably,
9842
9860
  * Chromium-based) report device with id `default` among audio input and output
@@ -11850,6 +11868,12 @@ class Call {
11850
11868
  }
11851
11869
  catch (err) {
11852
11870
  this.logger('warn', `Failed to join call (${attempt})`, this.cid);
11871
+ if (err instanceof ErrorFromResponse && err.unrecoverable) {
11872
+ // if the error is unrecoverable, we should not retry as that signals
11873
+ // that connectivity is good, but the coordinator doesn't allow the user
11874
+ // to join the call due to some reason (e.g., ended call, expired token...)
11875
+ throw err;
11876
+ }
11853
11877
  const sfuId = this.credentials?.server.edge_name || '';
11854
11878
  const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
11855
11879
  sfuJoinFailures.set(sfuId, failures);
@@ -14359,12 +14383,6 @@ class StreamClient {
14359
14383
  response,
14360
14384
  });
14361
14385
  };
14362
- this._logApiError = (type, url, error) => {
14363
- this.logger('error', `client:${type} - Error - url: ${url}`, {
14364
- url,
14365
- error,
14366
- });
14367
- };
14368
14386
  this.doAxiosRequest = async (type, url, data, options = {}) => {
14369
14387
  if (!options.publicEndpoint) {
14370
14388
  await Promise.all([
@@ -14411,27 +14429,36 @@ class StreamClient {
14411
14429
  }
14412
14430
  this._logApiResponse(type, url, response);
14413
14431
  this.consecutiveFailures = 0;
14414
- return this.handleResponse(response);
14432
+ return response.data;
14415
14433
  }
14416
14434
  catch (e /**TODO: generalize error types */) {
14417
14435
  e.client_request_id = requestConfig.headers?.['x-client-request-id'];
14418
14436
  this.consecutiveFailures += 1;
14419
- if (e.response) {
14420
- this._logApiError(type, url, e.response);
14421
- /** connection_fallback depends on this token expiration logic */
14422
- if (e.response.data.code === KnownCodes.TOKEN_EXPIRED &&
14423
- !this.tokenManager.isStatic()) {
14424
- if (this.consecutiveFailures > 1) {
14425
- await sleep(retryInterval(this.consecutiveFailures));
14426
- }
14427
- await this.tokenManager.loadToken();
14428
- return await this.doAxiosRequest(type, url, data, options);
14437
+ const { response } = e;
14438
+ if (!response || !isErrorResponse(response)) {
14439
+ this.logger('error', `client:${type} url: ${url}`, e);
14440
+ throw e;
14441
+ }
14442
+ const { data: responseData, status } = response;
14443
+ const isTokenExpired = responseData.code === KnownCodes.TOKEN_EXPIRED;
14444
+ if (isTokenExpired && !this.tokenManager.isStatic()) {
14445
+ this.logger('warn', `client:${type}: url: ${url}`, response);
14446
+ if (this.consecutiveFailures > 1) {
14447
+ await sleep(retryInterval(this.consecutiveFailures));
14429
14448
  }
14430
- return this.handleResponse(e.response);
14449
+ // refresh and retry the request
14450
+ await this.tokenManager.loadToken();
14451
+ return await this.doAxiosRequest(type, url, data, options);
14431
14452
  }
14432
14453
  else {
14433
- this._logApiError(type, url, e);
14434
- throw e;
14454
+ this.logger('error', `client:${type} url: ${url}`, response);
14455
+ throw new ErrorFromResponse({
14456
+ message: `Stream error code ${responseData.code}: ${responseData.message}`,
14457
+ code: responseData.code ?? null,
14458
+ unrecoverable: responseData.unrecoverable ?? null,
14459
+ response: response,
14460
+ status: status,
14461
+ });
14435
14462
  }
14436
14463
  }
14437
14464
  };
@@ -14454,23 +14481,6 @@ class StreamClient {
14454
14481
  params,
14455
14482
  });
14456
14483
  };
14457
- this.errorFromResponse = (response) => {
14458
- const { data, status } = response;
14459
- return new ErrorFromResponse({
14460
- message: `Stream error code ${data.code}: ${data.message}`,
14461
- code: data.code ?? null,
14462
- unrecoverable: data.unrecoverable ?? null,
14463
- response: response,
14464
- status: status,
14465
- });
14466
- };
14467
- this.handleResponse = (response) => {
14468
- const data = response.data;
14469
- if (isErrorResponse(response)) {
14470
- throw this.errorFromResponse(response);
14471
- }
14472
- return data;
14473
- };
14474
14484
  this.dispatchEvent = (event) => {
14475
14485
  this.logger('debug', `Dispatching event: ${event.type}`, event);
14476
14486
  if (!this.listeners)
@@ -14503,7 +14513,7 @@ class StreamClient {
14503
14513
  this.getUserAgent = () => {
14504
14514
  if (!this.cachedUserAgent) {
14505
14515
  const { clientAppIdentifier = {} } = this.options;
14506
- const { sdkName = 'js', sdkVersion = "1.27.1", ...extras } = clientAppIdentifier;
14516
+ const { sdkName = 'js', sdkVersion = "1.27.3", ...extras } = clientAppIdentifier;
14507
14517
  this.cachedUserAgent = [
14508
14518
  `stream-video-${sdkName}-v${sdkVersion}`,
14509
14519
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),