@stream-io/video-client 1.11.5 → 1.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.browser.es.js +77 -569
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +62 -554
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +77 -569
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/coordinator/connection/client.d.ts +0 -18
  9. package/dist/src/coordinator/connection/connection.d.ts +4 -12
  10. package/dist/src/coordinator/connection/signing.d.ts +1 -7
  11. package/dist/src/coordinator/connection/token_manager.d.ts +0 -2
  12. package/dist/src/coordinator/connection/types.d.ts +5 -6
  13. package/dist/src/coordinator/connection/utils.d.ts +6 -8
  14. package/dist/src/rtc/codecs.d.ts +2 -1
  15. package/package.json +6 -10
  16. package/src/__tests__/Call.test.ts +3 -2
  17. package/src/coordinator/connection/client.ts +12 -149
  18. package/src/coordinator/connection/connection.ts +40 -109
  19. package/src/coordinator/connection/signing.ts +31 -17
  20. package/src/coordinator/connection/token_manager.ts +3 -9
  21. package/src/coordinator/connection/types.ts +5 -9
  22. package/src/coordinator/connection/utils.ts +18 -50
  23. package/src/devices/__tests__/InputMediaDeviceManagerState.test.ts +13 -8
  24. package/src/devices/__tests__/mocks.ts +0 -4
  25. package/src/rtc/Publisher.ts +8 -3
  26. package/src/rtc/codecs.ts +7 -3
  27. package/dist/src/coordinator/connection/base64.d.ts +0 -2
  28. package/dist/src/coordinator/connection/connection_fallback.d.ts +0 -39
  29. package/dist/src/coordinator/connection/errors.d.ts +0 -16
  30. package/dist/src/coordinator/connection/insights.d.ts +0 -57
  31. package/src/coordinator/connection/base64.ts +0 -80
  32. package/src/coordinator/connection/connection_fallback.ts +0 -242
  33. package/src/coordinator/connection/errors.ts +0 -80
  34. package/src/coordinator/connection/insights.ts +0 -88
package/dist/index.cjs.js CHANGED
@@ -9,8 +9,6 @@ var uaParserJs = require('ua-parser-js');
9
9
  var rxjs = require('rxjs');
10
10
  var SDP = require('sdp-transform');
11
11
  var https = require('https');
12
- var WebSocket$1 = require('isomorphic-ws');
13
- var base64Js = require('base64-js');
14
12
 
15
13
  function _interopNamespaceDefault(e) {
16
14
  var n = Object.create(null);
@@ -3195,42 +3193,6 @@ function getRandomBytes(length) {
3195
3193
  getRandomValues(bytes);
3196
3194
  return bytes;
3197
3195
  }
3198
- function convertErrorToJson(err) {
3199
- const jsonObj = {};
3200
- if (!err)
3201
- return jsonObj;
3202
- try {
3203
- Object.getOwnPropertyNames(err).forEach((key) => {
3204
- jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
3205
- });
3206
- }
3207
- catch (_) {
3208
- return {
3209
- error: 'failed to serialize the error',
3210
- };
3211
- }
3212
- return jsonObj;
3213
- }
3214
- /**
3215
- * isOnline safely return the navigator.online value for browser env
3216
- * if navigator is not in global object, it always return true
3217
- */
3218
- function isOnline(logger) {
3219
- const nav = typeof navigator !== 'undefined'
3220
- ? navigator
3221
- : typeof window !== 'undefined' && window.navigator
3222
- ? window.navigator
3223
- : undefined;
3224
- if (!nav) {
3225
- logger('warn', 'isOnline failed to access window.navigator and assume browser is online');
3226
- return true;
3227
- }
3228
- // RN navigator has undefined for onLine
3229
- if (typeof nav.onLine !== 'boolean') {
3230
- return true;
3231
- }
3232
- return nav.onLine;
3233
- }
3234
3196
  /**
3235
3197
  * listenForConnectionChanges - Adds an event listener fired on browser going online or offline
3236
3198
  */
@@ -3246,6 +3208,13 @@ function removeConnectionEventListeners(cb) {
3246
3208
  window.removeEventListener('online', cb);
3247
3209
  }
3248
3210
  }
3211
+ function isErrorResponse(res) {
3212
+ return !res.status || res.status < 200 || 300 <= res.status;
3213
+ }
3214
+ // Type guards to check WebSocket error type
3215
+ function isCloseEvent(res) {
3216
+ return res.code !== undefined;
3217
+ }
3249
3218
 
3250
3219
  /**
3251
3220
  * Checks whether we are using React Native
@@ -3349,7 +3318,7 @@ const retryable = async (rpc, signal) => {
3349
3318
  return result;
3350
3319
  };
3351
3320
 
3352
- const version = "1.11.5";
3321
+ const version = "1.11.7";
3353
3322
  const [major, minor, patch] = version.split('.');
3354
3323
  let sdkInfo = {
3355
3324
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -3453,11 +3422,13 @@ var browsers = /*#__PURE__*/Object.freeze({
3453
3422
  * @param kind the kind of codec to get.
3454
3423
  * @param preferredCodec the codec to prioritize (vp8, h264, vp9, av1...).
3455
3424
  * @param codecToRemove the codec to exclude from the list.
3425
+ * @param codecPreferencesSource the source of the codec preferences.
3456
3426
  */
3457
- const getPreferredCodecs = (kind, preferredCodec, codecToRemove) => {
3458
- if (!('getCapabilities' in RTCRtpReceiver))
3427
+ const getPreferredCodecs = (kind, preferredCodec, codecToRemove, codecPreferencesSource = 'receiver') => {
3428
+ const source = codecPreferencesSource === 'receiver' ? RTCRtpReceiver : RTCRtpSender;
3429
+ if (!('getCapabilities' in source))
3459
3430
  return;
3460
- const capabilities = RTCRtpReceiver.getCapabilities(kind);
3431
+ const capabilities = source.getCapabilities(kind);
3461
3432
  if (!capabilities)
3462
3433
  return;
3463
3434
  const preferred = [];
@@ -5895,9 +5866,9 @@ class Publisher {
5895
5866
  this.getStats = (selector) => {
5896
5867
  return this.pc.getStats(selector);
5897
5868
  };
5898
- this.getCodecPreferences = (trackType, preferredCodec) => {
5869
+ this.getCodecPreferences = (trackType, preferredCodec, codecPreferencesSource) => {
5899
5870
  if (trackType === TrackType.VIDEO) {
5900
- return getPreferredCodecs('video', preferredCodec || 'vp8');
5871
+ return getPreferredCodecs('video', preferredCodec || 'vp8', codecPreferencesSource);
5901
5872
  }
5902
5873
  if (trackType === TrackType.AUDIO) {
5903
5874
  const defaultAudioCodec = this.isRedEnabled ? 'red' : 'opus';
@@ -6129,8 +6100,8 @@ class Publisher {
6129
6100
  const opts = this.publishOptsForTrack.get(trackType);
6130
6101
  if (!opts || !opts.forceSingleCodec)
6131
6102
  return sdp;
6132
- const codec = opts.forceCodec || opts.preferredCodec;
6133
- const orderedCodecs = this.getCodecPreferences(trackType, codec);
6103
+ const codec = opts.forceCodec || getOptimalVideoCodec(opts.preferredCodec);
6104
+ const orderedCodecs = this.getCodecPreferences(trackType, codec, 'sender');
6134
6105
  if (!orderedCodecs || orderedCodecs.length === 0)
6135
6106
  return sdp;
6136
6107
  const transceiver = this.transceiverCache.get(trackType);
@@ -11519,68 +11490,6 @@ class Call {
11519
11490
  }
11520
11491
  }
11521
11492
 
11522
- class InsightMetrics {
11523
- constructor() {
11524
- this.connectionStartTimestamp = null;
11525
- this.wsTotalFailures = 0;
11526
- this.wsConsecutiveFailures = 0;
11527
- this.instanceClientId = randomId();
11528
- }
11529
- }
11530
- /**
11531
- * postInsights is not supposed to be used by end users directly within chat application, and thus is kept isolated
11532
- * from all the client/connection code/logic.
11533
- *
11534
- * @param insightType
11535
- * @param insights
11536
- */
11537
- const postInsights = async (insightType, insights) => {
11538
- const maxAttempts = 3;
11539
- for (let i = 0; i < maxAttempts; i++) {
11540
- try {
11541
- await axios.post(`https://chat-insights.getstream.io/insights/${insightType}`, insights);
11542
- }
11543
- catch (e) {
11544
- await sleep((i + 1) * 3000);
11545
- continue;
11546
- }
11547
- break;
11548
- }
11549
- };
11550
- function buildWsFatalInsight(connection, event) {
11551
- return {
11552
- ...event,
11553
- ...buildWsBaseInsight(connection),
11554
- };
11555
- }
11556
- function buildWsBaseInsight(connection) {
11557
- const { client } = connection;
11558
- return {
11559
- ready_state: connection.ws?.readyState,
11560
- url: connection._buildUrl(),
11561
- api_key: client.key,
11562
- start_ts: client.insightMetrics.connectionStartTimestamp,
11563
- end_ts: new Date().getTime(),
11564
- auth_type: client.getAuthType(),
11565
- token: client.tokenManager.token,
11566
- user_id: client.userID,
11567
- user_details: client._user,
11568
- // device: client.options.device,
11569
- device: 'browser',
11570
- client_id: connection.connectionID,
11571
- ws_details: connection.ws,
11572
- ws_consecutive_failures: client.insightMetrics.wsConsecutiveFailures,
11573
- ws_total_failures: client.insightMetrics.wsTotalFailures,
11574
- request_id: connection.requestID,
11575
- online: typeof navigator !== 'undefined' ? navigator?.onLine : null,
11576
- user_agent: typeof navigator !== 'undefined' ? navigator?.userAgent : null,
11577
- instance_client_id: client.insightMetrics.instanceClientId,
11578
- };
11579
- }
11580
- function buildWsSuccessAfterFailureInsight(connection) {
11581
- return buildWsBaseInsight(connection);
11582
- }
11583
-
11584
11493
  /**
11585
11494
  * Saving a long-lived reference to a promise that can reject can be unsafe,
11586
11495
  * since rejecting the promise causes an unhandled rejection error (even if the
@@ -11608,9 +11517,6 @@ function makeSafePromise(promise) {
11608
11517
  return unwrapPromise;
11609
11518
  }
11610
11519
 
11611
- // Type guards to check WebSocket error type
11612
- const isCloseEvent = (res) => res.code !== undefined;
11613
- const isErrorEvent = (res) => res.error !== undefined;
11614
11520
  /**
11615
11521
  * StableWSConnection - A WS connection that reconnects upon failure.
11616
11522
  * - the browser will sometimes report that you're online or offline
@@ -11645,12 +11551,9 @@ class StableWSConnection {
11645
11551
  */
11646
11552
  this._buildUrl = () => {
11647
11553
  const params = new URLSearchParams();
11648
- // const qs = encodeURIComponent(this.client._buildWSPayload(this.requestID));
11649
- // params.set('json', qs);
11650
11554
  params.set('api_key', this.client.key);
11651
11555
  params.set('stream-auth-type', this.client.getAuthType());
11652
11556
  params.set('X-Stream-Client', this.client.getUserAgent());
11653
- // params.append('authorization', this.client._getToken()!);
11654
11557
  return `${this.client.wsBaseURL}/connect?${params.toString()}`;
11655
11558
  };
11656
11559
  /**
@@ -11699,7 +11602,6 @@ class StableWSConnection {
11699
11602
  custom: user.custom,
11700
11603
  },
11701
11604
  };
11702
- this.authenticationSent = true;
11703
11605
  this.ws?.send(JSON.stringify(authMessage));
11704
11606
  this._log('onopen() - onopen callback', { wsID });
11705
11607
  };
@@ -11716,7 +11618,6 @@ class StableWSConnection {
11716
11618
  if (!this.isResolved && data && data.type === 'connection.error') {
11717
11619
  this.isResolved = true;
11718
11620
  if (data.error) {
11719
- // @ts-expect-error - the types of _errorFromWSEvent are incorrect
11720
11621
  this.rejectPromise?.(this._errorFromWSEvent(data, false));
11721
11622
  return;
11722
11623
  }
@@ -11758,9 +11659,13 @@ class StableWSConnection {
11758
11659
  // this is a permanent error raised by stream..
11759
11660
  // usually caused by invalid auth details
11760
11661
  const error = new Error(`WS connection reject with error ${event.reason}`);
11662
+ // @ts-expect-error
11761
11663
  error.reason = event.reason;
11664
+ // @ts-expect-error
11762
11665
  error.code = event.code;
11666
+ // @ts-expect-error
11763
11667
  error.wasClean = event.wasClean;
11668
+ // @ts-expect-error
11764
11669
  error.target = event.target;
11765
11670
  this.rejectPromise?.(error);
11766
11671
  this._log(`onclose() - WS connection reject with error ${event.reason}`, {
@@ -11787,7 +11692,7 @@ class StableWSConnection {
11787
11692
  this.totalFailures += 1;
11788
11693
  this._setHealth(false);
11789
11694
  this.isConnecting = false;
11790
- this.rejectPromise?.(this._errorFromWSEvent(event));
11695
+ this.rejectPromise?.(new Error(`WebSocket error: ${event}`));
11791
11696
  this._log(`onerror() - WS connection resulted into error`, { event });
11792
11697
  this._reconnect();
11793
11698
  };
@@ -11797,7 +11702,6 @@ class StableWSConnection {
11797
11702
  *
11798
11703
  * @param {boolean} healthy boolean indicating if the connection is healthy or not
11799
11704
  * @param {boolean} dispatchImmediately boolean indicating to dispatch event immediately even if the connection is unhealthy
11800
- *
11801
11705
  */
11802
11706
  this._setHealth = (healthy, dispatchImmediately = false) => {
11803
11707
  if (healthy === this.isHealthy)
@@ -11822,7 +11726,6 @@ class StableWSConnection {
11822
11726
  };
11823
11727
  /**
11824
11728
  * _errorFromWSEvent - Creates an error object for the WS event
11825
- *
11826
11729
  */
11827
11730
  this._errorFromWSEvent = (event, isWSFailure = true) => {
11828
11731
  let code;
@@ -11830,17 +11733,18 @@ class StableWSConnection {
11830
11733
  let message;
11831
11734
  if (isCloseEvent(event)) {
11832
11735
  code = event.code;
11833
- statusCode = 'unknown';
11834
11736
  message = event.reason;
11737
+ statusCode = 0;
11835
11738
  }
11836
- if (isErrorEvent(event)) {
11837
- code = event.error.code;
11838
- statusCode = event.error.StatusCode;
11839
- message = event.error.message;
11840
- }
11841
- // Keeping this `warn` level log, to avoid cluttering of error logs from ws failures.
11842
- this._log(`_errorFromWSEvent() - WS failed with code ${code}`, { event }, 'warn');
11843
- const error = new Error(`WS failed with code ${code} and reason - ${message}`);
11739
+ else {
11740
+ const { error } = event;
11741
+ code = error.code;
11742
+ message = error.message;
11743
+ statusCode = error.StatusCode;
11744
+ }
11745
+ const msg = `WS failed with code: ${code} and reason: ${message}`;
11746
+ this._log(msg, { event }, 'warn');
11747
+ const error = new Error(msg);
11844
11748
  error.code = code;
11845
11749
  /**
11846
11750
  * StatusCode does not exist on any event types but has been left
@@ -11865,10 +11769,8 @@ class StableWSConnection {
11865
11769
  * Schedules a next health check ping for websocket.
11866
11770
  */
11867
11771
  this.scheduleNextPing = () => {
11868
- if (this.healthCheckTimeoutRef) {
11869
- clearTimeout(this.healthCheckTimeoutRef);
11870
- }
11871
11772
  // 30 seconds is the recommended interval (messenger uses this)
11773
+ clearTimeout(this.healthCheckTimeoutRef);
11872
11774
  this.healthCheckTimeoutRef = setTimeout(() => {
11873
11775
  // send the healthcheck..., server replies with a health check event
11874
11776
  const data = [{ type: 'health.check', client_id: this.client.clientID }];
@@ -11887,9 +11789,7 @@ class StableWSConnection {
11887
11789
  * to be reconnected.
11888
11790
  */
11889
11791
  this.scheduleConnectionCheck = () => {
11890
- if (this.connectionCheckTimeoutRef) {
11891
- clearTimeout(this.connectionCheckTimeoutRef);
11892
- }
11792
+ clearTimeout(this.connectionCheckTimeoutRef);
11893
11793
  this.connectionCheckTimeoutRef = setTimeout(() => {
11894
11794
  const now = new Date();
11895
11795
  if (this.lastEvent &&
@@ -11907,8 +11807,6 @@ class StableWSConnection {
11907
11807
  this.totalFailures = 0;
11908
11808
  /** We only make 1 attempt to reconnect at the same time.. */
11909
11809
  this.isConnecting = false;
11910
- /** True after the auth payload is sent to the server */
11911
- this.authenticationSent = false;
11912
11810
  /** To avoid reconnect if client is disconnected */
11913
11811
  this.isDisconnected = false;
11914
11812
  /** Boolean that indicates if the connection promise is resolved */
@@ -12016,18 +11914,10 @@ class StableWSConnection {
12016
11914
  this.isConnecting = false;
12017
11915
  this.isDisconnected = true;
12018
11916
  // start by removing all the listeners
12019
- if (this.healthCheckTimeoutRef) {
12020
- clearInterval(this.healthCheckTimeoutRef);
12021
- }
12022
- if (this.connectionCheckTimeoutRef) {
12023
- clearInterval(this.connectionCheckTimeoutRef);
12024
- }
11917
+ clearInterval(this.healthCheckTimeoutRef);
11918
+ clearInterval(this.connectionCheckTimeoutRef);
12025
11919
  removeConnectionEventListeners(this.onlineStatusChanged);
12026
11920
  this.isHealthy = false;
12027
- // remove ws handlers...
12028
- if (this.ws && this.ws.removeAllListeners) {
12029
- this.ws.removeAllListeners();
12030
- }
12031
11921
  let isClosedPromise;
12032
11922
  // and finally close...
12033
11923
  // Assigning to local here because we will remove it from this before the
@@ -12060,12 +11950,10 @@ class StableWSConnection {
12060
11950
  * @return {ConnectAPIResponse<ConnectedEvent>} Promise that completes once the first health check message is received
12061
11951
  */
12062
11952
  async _connect() {
12063
- if (this.isConnecting ||
12064
- (this.isDisconnected && this.client.options.enableWSFallback))
11953
+ if (this.isConnecting)
12065
11954
  return; // simply ignore _connect if it's currently trying to connect
12066
11955
  this.isConnecting = true;
12067
11956
  this.requestID = randomId();
12068
- this.client.insightMetrics.connectionStartTimestamp = new Date().getTime();
12069
11957
  let isTokenReady = false;
12070
11958
  try {
12071
11959
  this._log(`_connect() - waiting for token`);
@@ -12089,7 +11977,8 @@ class StableWSConnection {
12089
11977
  wsURL,
12090
11978
  requestID: this.requestID,
12091
11979
  });
12092
- this.ws = new WebSocket$1(wsURL);
11980
+ const WS = this.client.options.WebSocketImpl ?? WebSocket;
11981
+ this.ws = new WS(wsURL);
12093
11982
  this.ws.onopen = this.onopen.bind(this, this.wsID);
12094
11983
  this.ws.onclose = this.onclose.bind(this, this.wsID);
12095
11984
  this.ws.onerror = this.onerror.bind(this, this.wsID);
@@ -12099,11 +11988,6 @@ class StableWSConnection {
12099
11988
  if (response) {
12100
11989
  this.connectionID = response.connection_id;
12101
11990
  this.client.resolveConnectionId?.(this.connectionID);
12102
- if (this.client.insightMetrics.wsConsecutiveFailures > 0 &&
12103
- this.client.options.enableInsights) {
12104
- postInsights('ws_success_after_failure', buildWsSuccessAfterFailureInsight(this));
12105
- this.client.insightMetrics.wsConsecutiveFailures = 0;
12106
- }
12107
11991
  return response;
12108
11992
  }
12109
11993
  }
@@ -12112,12 +11996,6 @@ class StableWSConnection {
12112
11996
  this.isConnecting = false;
12113
11997
  // @ts-ignore
12114
11998
  this._log(`_connect() - Error - `, err);
12115
- if (this.client.options.enableInsights) {
12116
- this.client.insightMetrics.wsConsecutiveFailures++;
12117
- this.client.insightMetrics.wsTotalFailures++;
12118
- const insights = buildWsFatalInsight(this, convertErrorToJson(err));
12119
- postInsights?.('ws_fatal', insights);
12120
- }
12121
11999
  this.client.rejectConnectionId?.(err);
12122
12000
  throw err;
12123
12001
  }
@@ -12151,7 +12029,7 @@ class StableWSConnection {
12151
12029
  this._log('_reconnect() - Abort (2) since already connecting or healthy');
12152
12030
  return;
12153
12031
  }
12154
- if (this.isDisconnected && this.client.options.enableWSFallback) {
12032
+ if (this.isDisconnected) {
12155
12033
  this._log('_reconnect() - Abort (3) since disconnect() is called');
12156
12034
  return;
12157
12035
  }
@@ -12193,7 +12071,6 @@ class StableWSConnection {
12193
12071
  // ws connection from now on.
12194
12072
  this.wsID += 1;
12195
12073
  try {
12196
- this?.ws?.removeAllListeners();
12197
12074
  this?.ws?.close();
12198
12075
  }
12199
12076
  catch (e) {
@@ -12205,36 +12082,16 @@ class StableWSConnection {
12205
12082
  }
12206
12083
  }
12207
12084
 
12208
- function isString(arrayOrString) {
12209
- return typeof arrayOrString === 'string';
12210
- }
12211
- function isMapStringCallback(arrayOrString, callback) {
12212
- return !!callback && isString(arrayOrString);
12213
- }
12214
- function map(arrayOrString, callback) {
12215
- const res = [];
12216
- if (isString(arrayOrString) && isMapStringCallback(arrayOrString, callback)) {
12217
- for (let k = 0, len = arrayOrString.length; k < len; k++) {
12218
- if (arrayOrString.charAt(k)) {
12219
- const kValue = arrayOrString.charAt(k);
12220
- const mappedValue = callback(kValue, k, arrayOrString);
12221
- res[k] = mappedValue;
12222
- }
12223
- }
12224
- }
12225
- else if (!isString(arrayOrString) &&
12226
- !isMapStringCallback(arrayOrString, callback)) {
12227
- for (let k = 0, len = arrayOrString.length; k < len; k++) {
12228
- if (k in arrayOrString) {
12229
- const kValue = arrayOrString[k];
12230
- const mappedValue = callback(kValue, k, arrayOrString);
12231
- res[k] = mappedValue;
12232
- }
12233
- }
12085
+ function getUserFromToken(token) {
12086
+ const fragments = token.split('.');
12087
+ if (fragments.length !== 3) {
12088
+ return '';
12234
12089
  }
12235
- return res;
12090
+ const b64Payload = fragments[1];
12091
+ const payload = decodeBase64(b64Payload);
12092
+ const data = JSON.parse(payload);
12093
+ return data.user_id;
12236
12094
  }
12237
- const encodeBase64 = (data) => base64Js.fromByteArray(new Uint8Array(map(data, (char) => char.charCodeAt(0))));
12238
12095
  // base-64 decoder throws exception if encoded string is not padded by '=' to make string length
12239
12096
  // in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility
12240
12097
  // https://github.com/beatgammit/base64-js/blob/master/index.js#L26
@@ -12256,29 +12113,6 @@ const decodeBase64 = (s) => {
12256
12113
  return r;
12257
12114
  };
12258
12115
 
12259
- /**
12260
- *
12261
- * @param {string} userId the id of the user
12262
- * @return {string}
12263
- */
12264
- function DevToken(userId) {
12265
- return [
12266
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', //{"alg": "HS256", "typ": "JWT"}
12267
- encodeBase64(JSON.stringify({ user_id: userId })),
12268
- 'devtoken', // hardcoded signature
12269
- ].join('.');
12270
- }
12271
- function UserFromToken(token) {
12272
- const fragments = token.split('.');
12273
- if (fragments.length !== 3) {
12274
- return '';
12275
- }
12276
- const b64Payload = fragments[1];
12277
- const payload = decodeBase64(b64Payload);
12278
- const data = JSON.parse(payload);
12279
- return data.user_id;
12280
- }
12281
-
12282
12116
  /**
12283
12117
  * TokenManager
12284
12118
  *
@@ -12287,8 +12121,6 @@ function UserFromToken(token) {
12287
12121
  class TokenManager {
12288
12122
  /**
12289
12123
  * Constructor
12290
- *
12291
- * @param {Secret} secret
12292
12124
  */
12293
12125
  constructor(secret) {
12294
12126
  /**
@@ -12341,7 +12173,7 @@ class TokenManager {
12341
12173
  // Allow empty token for anonymous users
12342
12174
  if (isAnonymous && tokenOrProvider === '')
12343
12175
  return;
12344
- const tokenUserId = UserFromToken(tokenOrProvider);
12176
+ const tokenUserId = getUserFromToken(tokenOrProvider);
12345
12177
  if (tokenOrProvider != null &&
12346
12178
  (tokenUserId == null ||
12347
12179
  tokenUserId === '' ||
@@ -12356,7 +12188,6 @@ class TokenManager {
12356
12188
  // Fetches a token from tokenProvider function and sets in tokenManager.
12357
12189
  // In case of static token, it will simply resolve to static token.
12358
12190
  this.loadToken = () => {
12359
- // eslint-disable-next-line no-async-promise-executor
12360
12191
  this.loadTokenPromise = new Promise(async (resolve, reject) => {
12361
12192
  if (this.type === 'static') {
12362
12193
  return resolve(this.token);
@@ -12385,236 +12216,11 @@ class TokenManager {
12385
12216
  };
12386
12217
  this.isStatic = () => this.type === 'static';
12387
12218
  this.loadTokenPromise = null;
12388
- if (secret) {
12389
- this.secret = secret;
12390
- }
12219
+ this.secret = secret;
12391
12220
  this.type = 'static';
12392
12221
  }
12393
12222
  }
12394
12223
 
12395
- const APIErrorCodes = {
12396
- '-1': { name: 'InternalSystemError', retryable: true },
12397
- '2': { name: 'AccessKeyError', retryable: false },
12398
- '3': { name: 'AuthenticationFailedError', retryable: true },
12399
- '4': { name: 'InputError', retryable: false },
12400
- '6': { name: 'DuplicateUsernameError', retryable: false },
12401
- '9': { name: 'RateLimitError', retryable: true },
12402
- '16': { name: 'DoesNotExistError', retryable: false },
12403
- '17': { name: 'NotAllowedError', retryable: false },
12404
- '18': { name: 'EventNotSupportedError', retryable: false },
12405
- '19': { name: 'ChannelFeatureNotSupportedError', retryable: false },
12406
- '20': { name: 'MessageTooLongError', retryable: false },
12407
- '21': { name: 'MultipleNestingLevelError', retryable: false },
12408
- '22': { name: 'PayloadTooBigError', retryable: false },
12409
- '23': { name: 'RequestTimeoutError', retryable: true },
12410
- '24': { name: 'MaxHeaderSizeExceededError', retryable: false },
12411
- '40': { name: 'AuthErrorTokenExpired', retryable: false },
12412
- '41': { name: 'AuthErrorTokenNotValidYet', retryable: false },
12413
- '42': { name: 'AuthErrorTokenUsedBeforeIssuedAt', retryable: false },
12414
- '43': { name: 'AuthErrorTokenSignatureInvalid', retryable: false },
12415
- '44': { name: 'CustomCommandEndpointMissingError', retryable: false },
12416
- '45': { name: 'CustomCommandEndpointCallError', retryable: true },
12417
- '46': { name: 'ConnectionIDNotFoundError', retryable: false },
12418
- '60': { name: 'CoolDownError', retryable: true },
12419
- '69': { name: 'ErrWrongRegion', retryable: false },
12420
- '70': { name: 'ErrQueryChannelPermissions', retryable: false },
12421
- '71': { name: 'ErrTooManyConnections', retryable: true },
12422
- '99': { name: 'AppSuspendedError', retryable: false },
12423
- };
12424
- function isAPIError(error) {
12425
- return error.code !== undefined;
12426
- }
12427
- function isErrorRetryable(error) {
12428
- if (!error.code)
12429
- return false;
12430
- const err = APIErrorCodes[`${error.code}`];
12431
- if (!err)
12432
- return false;
12433
- return err.retryable;
12434
- }
12435
- function isConnectionIDError(error) {
12436
- return error.code === 46; // ConnectionIDNotFoundError
12437
- }
12438
- function isWSFailure(err) {
12439
- if (typeof err.isWSFailure === 'boolean') {
12440
- return err.isWSFailure;
12441
- }
12442
- try {
12443
- return JSON.parse(err.message).isWSFailure;
12444
- }
12445
- catch (_) {
12446
- return false;
12447
- }
12448
- }
12449
- function isErrorResponse(res) {
12450
- return !res.status || res.status < 200 || 300 <= res.status;
12451
- }
12452
-
12453
- var ConnectionState;
12454
- (function (ConnectionState) {
12455
- ConnectionState["Closed"] = "CLOSED";
12456
- ConnectionState["Connected"] = "CONNECTED";
12457
- ConnectionState["Connecting"] = "CONNECTING";
12458
- ConnectionState["Disconnected"] = "DISCONNECTED";
12459
- ConnectionState["Init"] = "INIT";
12460
- })(ConnectionState || (ConnectionState = {}));
12461
- class WSConnectionFallback {
12462
- constructor(client) {
12463
- /** @private */
12464
- this._onlineStatusChanged = (event) => {
12465
- this._log(`_onlineStatusChanged() - ${event.type}`);
12466
- if (event.type === 'offline') {
12467
- this._setState(ConnectionState.Closed);
12468
- this.cancelToken?.cancel('disconnect() is called');
12469
- this.cancelToken = undefined;
12470
- return;
12471
- }
12472
- if (event.type === 'online' && this.state === ConnectionState.Closed) {
12473
- this.connect(true);
12474
- }
12475
- };
12476
- /** @private */
12477
- this._req = async (params, config, retry) => {
12478
- if (!this.cancelToken && !params.close) {
12479
- this.cancelToken = axios.CancelToken.source();
12480
- }
12481
- try {
12482
- const res = await this.client.doAxiosRequest('get', this.client.baseURL.replace(':3030', ':8900') + '/longpoll', // replace port if present for testing with local API
12483
- undefined, {
12484
- config: { ...config, cancelToken: this.cancelToken?.token },
12485
- params,
12486
- publicEndpoint: true,
12487
- });
12488
- this.consecutiveFailures = 0; // always reset in case of no error
12489
- return res;
12490
- }
12491
- catch (err) {
12492
- this.consecutiveFailures += 1;
12493
- // @ts-ignore
12494
- if (retry && isErrorRetryable(err)) {
12495
- this._log(`_req() - Retryable error, retrying request`);
12496
- await sleep(retryInterval(this.consecutiveFailures));
12497
- return this._req(params, config, retry);
12498
- }
12499
- throw err;
12500
- }
12501
- };
12502
- /** @private */
12503
- this._poll = async () => {
12504
- while (this.state === ConnectionState.Connected) {
12505
- try {
12506
- const data = await this._req({}, {
12507
- timeout: 30000,
12508
- }, true); // 30s => API responds in 20s if there is no event
12509
- if (data.events?.length) {
12510
- for (let i = 0; i < data.events.length; i++) {
12511
- this.client.dispatchEvent(data.events[i]);
12512
- }
12513
- }
12514
- }
12515
- catch (err) {
12516
- if (axios.isCancel(err)) {
12517
- this._log(`_poll() - axios canceled request`);
12518
- return;
12519
- }
12520
- /** client.doAxiosRequest will take care of TOKEN_EXPIRED error */
12521
- // @ts-ignore
12522
- if (isConnectionIDError(err)) {
12523
- this._log(`_poll() - ConnectionID error, connecting without ID...`);
12524
- this._setState(ConnectionState.Disconnected);
12525
- this.connect(true);
12526
- return;
12527
- }
12528
- // @ts-ignore
12529
- if (isAPIError(err) && !isErrorRetryable(err)) {
12530
- this._setState(ConnectionState.Closed);
12531
- return;
12532
- }
12533
- await sleep(retryInterval(this.consecutiveFailures));
12534
- }
12535
- }
12536
- };
12537
- /**
12538
- * connect try to open a longpoll request
12539
- * @param reconnect should be false for first call and true for subsequent calls to keep the connection alive and call recoverState
12540
- */
12541
- this.connect = async (reconnect = false) => {
12542
- if (this.state === ConnectionState.Connecting) {
12543
- this._log('connect() - connecting already in progress', { reconnect }, 'warn');
12544
- return;
12545
- }
12546
- if (this.state === ConnectionState.Connected) {
12547
- this._log('connect() - already connected and polling', { reconnect }, 'warn');
12548
- return;
12549
- }
12550
- this._setState(ConnectionState.Connecting);
12551
- this.connectionID = undefined; // connect should be sent with empty connection_id so API creates one
12552
- try {
12553
- const { event } = await this._req({ json: this.client._buildWSPayload() }, {
12554
- timeout: 8000, // 8s
12555
- }, reconnect);
12556
- this._setState(ConnectionState.Connected);
12557
- this.connectionID = event.connection_id;
12558
- this.client.resolveConnectionId?.();
12559
- // @ts-expect-error
12560
- this.client.dispatchEvent(event);
12561
- this._poll();
12562
- return event;
12563
- }
12564
- catch (err) {
12565
- this._setState(ConnectionState.Closed);
12566
- this.client.rejectConnectionId?.();
12567
- throw err;
12568
- }
12569
- };
12570
- /**
12571
- * isHealthy checks if there is a connectionID and connection is in Connected state
12572
- */
12573
- this.isHealthy = () => {
12574
- return !!this.connectionID && this.state === ConnectionState.Connected;
12575
- };
12576
- this.disconnect = async (timeout = 2000) => {
12577
- removeConnectionEventListeners(this._onlineStatusChanged);
12578
- this._setState(ConnectionState.Disconnected);
12579
- this.cancelToken?.cancel('disconnect() is called');
12580
- this.cancelToken = undefined;
12581
- const connection_id = this.connectionID;
12582
- this.connectionID = undefined;
12583
- try {
12584
- await this._req({ close: true, connection_id }, {
12585
- timeout,
12586
- }, false);
12587
- this._log(`disconnect() - Closed connectionID`);
12588
- }
12589
- catch (err) {
12590
- this._log(`disconnect() - Failed`, { err }, 'error');
12591
- }
12592
- };
12593
- this.client = client;
12594
- this.state = ConnectionState.Init;
12595
- this.consecutiveFailures = 0;
12596
- addConnectionEventListeners(this._onlineStatusChanged);
12597
- }
12598
- _log(msg, extra = {}, level = 'info') {
12599
- this.client.logger(level, 'WSConnectionFallback:' + msg, {
12600
- ...extra,
12601
- });
12602
- }
12603
- _setState(state) {
12604
- this._log(`_setState() - ${state}`);
12605
- // transition from connecting => connected
12606
- if (this.state === ConnectionState.Connecting &&
12607
- state === ConnectionState.Connected) {
12608
- this.client.dispatchEvent({ type: 'connection.changed', online: true });
12609
- }
12610
- if (state === ConnectionState.Closed ||
12611
- state === ConnectionState.Disconnected) {
12612
- this.client.dispatchEvent({ type: 'connection.changed', online: false });
12613
- }
12614
- this.state = state;
12615
- }
12616
- }
12617
-
12618
12224
  const getLocationHint = async (hintUrl = `https://hint.stream-io-video.com/`, timeout = 2000, maxAttempts = 3) => {
12619
12225
  const logger = getLogger(['location-hint']);
12620
12226
  let attempt = 0;
@@ -12657,9 +12263,6 @@ class StreamClient {
12657
12263
  */
12658
12264
  constructor(key, options) {
12659
12265
  this.listeners = {};
12660
- this.devToken = (userID) => {
12661
- return DevToken(userID);
12662
- };
12663
12266
  this.getAuthType = () => {
12664
12267
  return this.anonymous ? 'anonymous' : 'jwt';
12665
12268
  };
@@ -12677,7 +12280,7 @@ class StreamClient {
12677
12280
  }
12678
12281
  return hint;
12679
12282
  };
12680
- this._getConnectionID = () => this.wsConnection?.connectionID || this.wsFallback?.connectionID;
12283
+ this._getConnectionID = () => this.wsConnection?.connectionID;
12681
12284
  this._hasConnectionID = () => Boolean(this._getConnectionID());
12682
12285
  /**
12683
12286
  * connectUser - Set the current user and open a WebSocket connection
@@ -12753,11 +12356,7 @@ class StreamClient {
12753
12356
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
12754
12357
  */
12755
12358
  this.closeConnection = async (timeout) => {
12756
- await Promise.all([
12757
- this.wsConnection?.disconnect(timeout),
12758
- this.wsFallback?.disconnect(timeout),
12759
- ]);
12760
- return Promise.resolve();
12359
+ await this.wsConnection?.disconnect(timeout);
12761
12360
  };
12762
12361
  /**
12763
12362
  * Creates a new WebSocket connection with the current user. Returns empty promise, if there is an active connection
@@ -12771,12 +12370,11 @@ class StreamClient {
12771
12370
  this.logger('info', 'client:openConnection() - connection already in progress');
12772
12371
  return await wsPromise;
12773
12372
  }
12774
- if ((this.wsConnection?.isHealthy || this.wsFallback?.isHealthy()) &&
12775
- this._hasConnectionID()) {
12373
+ if (this.wsConnection?.isHealthy && this._hasConnectionID()) {
12776
12374
  this.logger('info', 'client:openConnection() - openConnection called twice, healthy connection already exists');
12777
12375
  return;
12778
12376
  }
12779
- this._setupConnectionIdPromise();
12377
+ await this._setupConnectionIdPromise();
12780
12378
  this.clientID = `${this.userID}--${randomId()}`;
12781
12379
  const newWsPromise = this.connect();
12782
12380
  this.wsPromiseSafe = makeSafePromise(newWsPromise);
@@ -12803,11 +12401,7 @@ class StreamClient {
12803
12401
  this.resolveConnectionId = undefined;
12804
12402
  };
12805
12403
  this.connectGuestUser = async (user) => {
12806
- this.guestUserCreatePromise = this.doAxiosRequest('post', '/guest', {
12807
- user: {
12808
- ...user,
12809
- },
12810
- }, { publicEndpoint: true });
12404
+ this.guestUserCreatePromise = this.doAxiosRequest('post', '/guest', { user }, { publicEndpoint: true });
12811
12405
  const response = await this.guestUserCreatePromise;
12812
12406
  this.guestUserCreatePromise.finally(() => (this.guestUserCreatePromise = undefined));
12813
12407
  return this.connectUser(response.user, response.access_token);
@@ -12817,7 +12411,7 @@ class StreamClient {
12817
12411
  */
12818
12412
  this.connectAnonymousUser = async (user, tokenOrProvider) => {
12819
12413
  addConnectionEventListeners(this.updateNetworkConnectionStatus);
12820
- this._setupConnectionIdPromise();
12414
+ await this._setupConnectionIdPromise();
12821
12415
  this.anonymous = true;
12822
12416
  await this._setToken(user, tokenOrProvider, this.anonymous);
12823
12417
  this._setUser(user);
@@ -13011,78 +12605,17 @@ class StreamClient {
13011
12605
  if (!this.userID || !this._user) {
13012
12606
  throw Error('Call connectUser or connectAnonymousUser before starting the connection');
13013
12607
  }
13014
- if (!this.wsBaseURL) {
12608
+ if (!this.wsBaseURL)
13015
12609
  throw Error('Websocket base url not set');
13016
- }
13017
- if (!this.clientID) {
12610
+ if (!this.clientID)
13018
12611
  throw Error('clientID is not set');
13019
- }
13020
- if (!this.wsConnection &&
13021
- (this.options.warmUp || this.options.enableInsights)) {
13022
- this._sayHi();
13023
- }
13024
12612
  // The StableWSConnection handles all the reconnection logic.
13025
- if (this.options.wsConnection && this.node) {
13026
- // Intentionally avoiding adding ts generics on wsConnection in options since its only useful for unit test purpose.
13027
- this.options.wsConnection.setClient(this);
13028
- this.wsConnection = this.options
13029
- .wsConnection;
13030
- }
13031
- else {
13032
- this.wsConnection = new StableWSConnection(this);
13033
- }
13034
- try {
13035
- // if fallback is used before, continue using it instead of waiting for WS to fail
13036
- if (this.wsFallback) {
13037
- return await this.wsFallback.connect();
13038
- }
13039
- this.logger('info', 'StreamClient.connect: this.wsConnection.connect()');
13040
- // if WSFallback is enabled, ws connect should timeout faster so fallback can try
13041
- return await this.wsConnection.connect(this.options.enableWSFallback
13042
- ? this.defaultWSTimeoutWithFallback
13043
- : this.defaultWSTimeout);
13044
- }
13045
- catch (err) {
13046
- // run fallback only if it's WS/Network error and not a normal API error
13047
- // make sure browser is online before even trying the longpoll
13048
- if (this.options.enableWSFallback &&
13049
- // @ts-ignore
13050
- isWSFailure(err) &&
13051
- isOnline(this.logger)) {
13052
- this.logger('warn', 'client:connect() - WS failed, fallback to longpoll');
13053
- this.dispatchEvent({ type: 'transport.changed', mode: 'longpoll' });
13054
- this.wsConnection._destroyCurrentWSConnection();
13055
- this.wsConnection.disconnect().then(); // close WS so no retry
13056
- this.wsFallback = new WSConnectionFallback(this);
13057
- return await this.wsFallback.connect();
13058
- }
13059
- throw err;
13060
- }
13061
- };
13062
- /**
13063
- * Check the connectivity with server for warmup purpose.
13064
- *
13065
- * @private
13066
- */
13067
- this._sayHi = () => {
13068
- const client_request_id = randomId();
13069
- const opts = {
13070
- headers: axios.AxiosHeaders.from({
13071
- 'x-client-request-id': client_request_id,
13072
- }),
13073
- };
13074
- this.doAxiosRequest('get', this.baseURL + '/hi', null, opts).catch((e) => {
13075
- if (this.options.enableInsights) {
13076
- postInsights('http_hi_failed', {
13077
- api_key: this.key,
13078
- err: e,
13079
- client_request_id,
13080
- });
13081
- }
13082
- });
12613
+ this.wsConnection = new StableWSConnection(this);
12614
+ this.logger('info', 'StreamClient.connect: this.wsConnection.connect()');
12615
+ return await this.wsConnection.connect(this.defaultWSTimeout);
13083
12616
  };
13084
12617
  this.getUserAgent = () => {
13085
- const version = "1.11.5";
12618
+ const version = "1.11.7";
13086
12619
  return (this.userAgent ||
13087
12620
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
13088
12621
  };
@@ -13130,18 +12663,6 @@ class StreamClient {
13130
12663
  return null;
13131
12664
  return this.tokenManager.getToken();
13132
12665
  };
13133
- /**
13134
- * encode ws url payload
13135
- * @private
13136
- * @returns json string
13137
- */
13138
- this._buildWSPayload = (client_request_id) => {
13139
- return JSON.stringify({
13140
- user_id: this.userID,
13141
- user_details: this._user,
13142
- client_request_id,
13143
- });
13144
- };
13145
12666
  this.updateNetworkConnectionStatus = (event) => {
13146
12667
  if (event.type === 'offline') {
13147
12668
  this.logger('debug', 'device went offline');
@@ -13170,7 +12691,6 @@ class StreamClient {
13170
12691
  this.options = {
13171
12692
  timeout: 5000,
13172
12693
  withCredentials: false, // making sure cookies are not sent
13173
- warmUp: false,
13174
12694
  ...inputOptions,
13175
12695
  };
13176
12696
  if (this.node && !this.options.httpsAgent) {
@@ -13180,16 +12700,6 @@ class StreamClient {
13180
12700
  });
13181
12701
  }
13182
12702
  this.setBaseURL(this.options.baseURL || 'https://video.stream-io-api.com/video');
13183
- if (typeof process !== 'undefined' &&
13184
- 'env' in process &&
13185
- process.env.STREAM_LOCAL_TEST_RUN) {
13186
- this.setBaseURL('http://localhost:3030/video');
13187
- }
13188
- if (typeof process !== 'undefined' &&
13189
- 'env' in process &&
13190
- process.env.STREAM_LOCAL_TEST_HOST) {
13191
- this.setBaseURL(`http://${process.env.STREAM_LOCAL_TEST_HOST}/video`);
13192
- }
13193
12703
  this.axiosInstance = axios.create({
13194
12704
  ...this.options,
13195
12705
  baseURL: this.baseURL,
@@ -13206,8 +12716,6 @@ class StreamClient {
13206
12716
  // generated from secret.
13207
12717
  this.tokenManager = new TokenManager(this.secret);
13208
12718
  this.consecutiveFailures = 0;
13209
- this.insightMetrics = new InsightMetrics();
13210
- this.defaultWSTimeoutWithFallback = 6000;
13211
12719
  this.defaultWSTimeout = 15000;
13212
12720
  this.logger = isFunction(inputOptions.logger)
13213
12721
  ? inputOptions.logger