@stream-io/video-client 1.16.7 → 1.17.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.17.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.16.7...@stream-io/video-client-1.17.0) (2025-02-17)
6
+
7
+
8
+ ### Features
9
+
10
+ * support static token and token provider at the same time ([#1685](https://github.com/GetStream/stream-video-js/issues/1685)) ([4365a3d](https://github.com/GetStream/stream-video-js/commit/4365a3dd0a14c98041982bde8be21258b8cfd571))
11
+
5
12
  ## [1.16.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.16.6...@stream-io/video-client-1.16.7) (2025-02-12)
6
13
 
7
14
 
@@ -4,7 +4,7 @@ import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc'
4
4
  import axios from 'axios';
5
5
  export { AxiosError } from 'axios';
6
6
  import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
7
- import { ReplaySubject, combineLatest, BehaviorSubject, map, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, from, fromEvent, debounceTime, merge, pairwise, of } from 'rxjs';
7
+ import { ReplaySubject, combineLatest, BehaviorSubject, map, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of } from 'rxjs';
8
8
  import { parse } from 'sdp-transform';
9
9
  import { UAParser } from 'ua-parser-js';
10
10
 
@@ -3302,10 +3302,7 @@ function isFunction(value) {
3302
3302
  */
3303
3303
  const KnownCodes = {
3304
3304
  TOKEN_EXPIRED: 40,
3305
- WS_CLOSED_SUCCESS: 1000,
3306
- WS_CLOSED_ABRUPTLY: 1006,
3307
- WS_POLICY_VIOLATION: 1008,
3308
- };
3305
+ WS_CLOSED_SUCCESS: 1000};
3309
3306
  /**
3310
3307
  * retryInterval - A retry interval which increases acc to number of failures
3311
3308
  *
@@ -7464,7 +7461,7 @@ const aggregate = (stats) => {
7464
7461
  return report;
7465
7462
  };
7466
7463
 
7467
- const version = "1.16.7";
7464
+ const version = "1.17.0";
7468
7465
  const [major, minor, patch] = version.split('.');
7469
7466
  let sdkInfo = {
7470
7467
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -12581,9 +12578,6 @@ const decodeBase64 = (s) => {
12581
12578
  * Handles all the operations around user token.
12582
12579
  */
12583
12580
  class TokenManager {
12584
- /**
12585
- * Constructor
12586
- */
12587
12581
  constructor(secret) {
12588
12582
  /**
12589
12583
  * Set the static string token or token provider.
@@ -12594,8 +12588,9 @@ class TokenManager {
12594
12588
  * @param {boolean} isAnonymous - whether the user is anonymous or not.
12595
12589
  */
12596
12590
  this.setTokenOrProvider = async (tokenOrProvider, user, isAnonymous) => {
12597
- this.validateToken(tokenOrProvider, user, isAnonymous);
12598
12591
  this.user = user;
12592
+ this.isAnonymous = isAnonymous;
12593
+ this.validateToken(tokenOrProvider);
12599
12594
  if (isFunction(tokenOrProvider)) {
12600
12595
  this.tokenProvider = tokenOrProvider;
12601
12596
  this.type = 'provider';
@@ -12618,28 +12613,28 @@ class TokenManager {
12618
12613
  this.loadTokenPromise = null;
12619
12614
  };
12620
12615
  // Validates the user token.
12621
- this.validateToken = (tokenOrProvider, user, isAnonymous) => {
12616
+ this.validateToken = (tokenOrProvider) => {
12622
12617
  // allow empty token for anon user
12623
- if (user && isAnonymous && !tokenOrProvider)
12618
+ if (this.user && this.isAnonymous && !tokenOrProvider)
12624
12619
  return;
12625
12620
  // Don't allow empty token for non-server side client.
12626
12621
  if (!this.secret && !tokenOrProvider) {
12627
- throw new Error('UserWithId token can not be empty');
12622
+ throw new Error('User token can not be empty');
12628
12623
  }
12629
12624
  if (tokenOrProvider &&
12630
12625
  typeof tokenOrProvider !== 'string' &&
12631
12626
  !isFunction(tokenOrProvider)) {
12632
- throw new Error('user token should either be a string or a function');
12627
+ throw new Error('User token should either be a string or a function');
12633
12628
  }
12634
12629
  if (typeof tokenOrProvider === 'string') {
12635
12630
  // Allow empty token for anonymous users
12636
- if (isAnonymous && tokenOrProvider === '')
12631
+ if (this.isAnonymous && tokenOrProvider === '')
12637
12632
  return;
12638
12633
  const tokenUserId = getUserFromToken(tokenOrProvider);
12639
12634
  if (tokenOrProvider != null &&
12640
12635
  (tokenUserId == null ||
12641
12636
  tokenUserId === '' ||
12642
- (!isAnonymous && tokenUserId !== user.id))) {
12637
+ (!this.isAnonymous && tokenUserId !== this.user.id))) {
12643
12638
  throw new Error('userToken does not have a user_id or is not matching with user.id');
12644
12639
  }
12645
12640
  }
@@ -12656,7 +12651,9 @@ class TokenManager {
12656
12651
  }
12657
12652
  if (this.tokenProvider && typeof this.tokenProvider !== 'string') {
12658
12653
  try {
12659
- this.token = await this.tokenProvider();
12654
+ const token = await this.tokenProvider();
12655
+ this.validateToken(token);
12656
+ this.token = token;
12660
12657
  }
12661
12658
  catch (e) {
12662
12659
  return reject(new Error(`Call to tokenProvider failed with message: ${e}`));
@@ -13077,7 +13074,7 @@ class StreamClient {
13077
13074
  return await this.wsConnection.connect(this.defaultWSTimeout);
13078
13075
  };
13079
13076
  this.getUserAgent = () => {
13080
- const version = "1.16.7";
13077
+ const version = "1.17.0";
13081
13078
  return (this.userAgent ||
13082
13079
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
13083
13080
  };
@@ -13194,14 +13191,68 @@ class StreamClient {
13194
13191
  }
13195
13192
  }
13196
13193
 
13194
+ /**
13195
+ * Utility function to get the instance key.
13196
+ */
13197
+ const getInstanceKey = (apiKey, user) => {
13198
+ return `${apiKey}/${user.id}`;
13199
+ };
13200
+ /**
13201
+ * Creates a coordinator client.
13202
+ */
13203
+ const createCoordinatorClient = (apiKey, options) => {
13204
+ const coordinatorLogger = getLogger(['coordinator']);
13205
+ const streamClient = new StreamClient(apiKey, {
13206
+ persistUserOnConnectionFailure: true,
13207
+ ...options,
13208
+ logger: coordinatorLogger,
13209
+ });
13210
+ const sdkInfo = getSdkInfo();
13211
+ if (sdkInfo) {
13212
+ const sdkName = SdkType[sdkInfo.type].toLowerCase();
13213
+ const sdkVersion = `${sdkInfo.major}.${sdkInfo.minor}.${sdkInfo.patch}`;
13214
+ const userAgent = streamClient.getUserAgent();
13215
+ streamClient.setUserAgent(`${userAgent}-video-${sdkName}-sdk-${sdkVersion}`);
13216
+ }
13217
+ return streamClient;
13218
+ };
13219
+ /**
13220
+ * Creates a token provider and allows integrators to provide
13221
+ * a static token and a token provider at the same time.
13222
+ *
13223
+ * When both of them are provided, this function will create an internal
13224
+ * token provider that will use the static token on the first invocation
13225
+ * and the token provider on the later invocations.
13226
+ */
13227
+ const createTokenOrProvider = (options) => {
13228
+ const { token, tokenProvider } = options;
13229
+ if (token && tokenProvider) {
13230
+ let initialTokenUsed = false;
13231
+ return async function wrappedTokenProvider() {
13232
+ if (!initialTokenUsed) {
13233
+ initialTokenUsed = true;
13234
+ return token;
13235
+ }
13236
+ return tokenProvider();
13237
+ };
13238
+ }
13239
+ return token || tokenProvider;
13240
+ };
13241
+
13197
13242
  /**
13198
13243
  * A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
13199
13244
  */
13200
13245
  class StreamVideoClient {
13201
13246
  constructor(apiKeyOrArgs, opts) {
13202
- this.logLevel = 'warn';
13203
13247
  this.eventHandlersToUnregister = [];
13204
13248
  this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
13249
+ this.registerClientInstance = (apiKey, user) => {
13250
+ const instanceKey = getInstanceKey(apiKey, user);
13251
+ if (StreamVideoClient._instances.has(instanceKey)) {
13252
+ this.logger('warn', `A StreamVideoClient already exists for ${user.id}; Prefer using getOrCreateInstance method`);
13253
+ }
13254
+ StreamVideoClient._instances.set(instanceKey, this);
13255
+ };
13205
13256
  /**
13206
13257
  * Connects the given user to the client.
13207
13258
  * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
@@ -13215,37 +13266,30 @@ class StreamVideoClient {
13215
13266
  user.id = '!anon';
13216
13267
  return this.connectAnonymousUser(user, token);
13217
13268
  }
13218
- let connectUser = () => {
13219
- return this.streamClient.connectUser(user, token);
13220
- };
13221
- if (user.type === 'guest') {
13222
- connectUser = async () => {
13223
- return this.streamClient.connectGuestUser(user);
13224
- };
13225
- }
13269
+ const connectUser = user.type === 'guest'
13270
+ ? () => this.streamClient.connectGuestUser(user)
13271
+ : () => this.streamClient.connectUser(user, token);
13226
13272
  const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, () => connectUser());
13227
13273
  // connectUserResponse will be void if connectUser called twice for the same user
13228
13274
  if (connectUserResponse?.me) {
13229
13275
  this.writeableStateStore.setConnectedUser(connectUserResponse.me);
13230
13276
  }
13231
13277
  this.eventHandlersToUnregister.push(this.on('connection.changed', (event) => {
13232
- if (event.online) {
13233
- const callsToReWatch = this.writeableStateStore.calls
13234
- .filter((call) => call.watching)
13235
- .map((call) => call.cid);
13236
- this.logger('info', `Rewatching calls after connection changed ${callsToReWatch.join(', ')}`);
13237
- if (callsToReWatch.length > 0) {
13238
- this.queryCalls({
13239
- watch: true,
13240
- filter_conditions: {
13241
- cid: { $in: callsToReWatch },
13242
- },
13243
- sort: [{ field: 'cid', direction: 1 }],
13244
- }).catch((err) => {
13245
- this.logger('error', 'Failed to re-watch calls', err);
13246
- });
13247
- }
13248
- }
13278
+ if (!event.online)
13279
+ return;
13280
+ const callsToReWatch = this.writeableStateStore.calls
13281
+ .filter((call) => call.watching)
13282
+ .map((call) => call.cid);
13283
+ if (callsToReWatch.length <= 0)
13284
+ return;
13285
+ this.logger('info', `Rewatching calls ${callsToReWatch.join(', ')}`);
13286
+ this.queryCalls({
13287
+ watch: true,
13288
+ filter_conditions: { cid: { $in: callsToReWatch } },
13289
+ sort: [{ field: 'cid', direction: 1 }],
13290
+ }).catch((err) => {
13291
+ this.logger('error', 'Failed to re-watch calls', err);
13292
+ });
13249
13293
  }));
13250
13294
  this.eventHandlersToUnregister.push(this.on('call.created', (event) => {
13251
13295
  const { call, members } = event;
@@ -13301,15 +13345,12 @@ class StreamVideoClient {
13301
13345
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
13302
13346
  */
13303
13347
  this.disconnectUser = async (timeout) => {
13304
- if (!this.streamClient.user) {
13348
+ const { user, key } = this.streamClient;
13349
+ if (!user)
13305
13350
  return;
13306
- }
13307
- const userId = this.streamClient.user?.id;
13308
- const apiKey = this.streamClient.key;
13309
- const disconnectUser = () => this.streamClient.disconnectUser(timeout);
13310
- await withoutConcurrency(this.connectionConcurrencyTag, () => disconnectUser());
13311
- if (userId) {
13312
- StreamVideoClient._instanceMap.delete(apiKey + userId);
13351
+ await withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.disconnectUser(timeout));
13352
+ if (user.id) {
13353
+ StreamVideoClient._instances.delete(getInstanceKey(key, user));
13313
13354
  }
13314
13355
  this.eventHandlersToUnregister.forEach((unregister) => unregister());
13315
13356
  this.eventHandlersToUnregister = [];
@@ -13490,92 +13531,46 @@ class StreamVideoClient {
13490
13531
  * @param tokenOrProvider a token or a function that returns a token.
13491
13532
  */
13492
13533
  this.connectAnonymousUser = async (user, tokenOrProvider) => {
13493
- const connectAnonymousUser = () => this.streamClient.connectAnonymousUser(user, tokenOrProvider);
13494
- return await withoutConcurrency(this.connectionConcurrencyTag, () => connectAnonymousUser());
13495
- };
13496
- let logger = logToConsole;
13497
- let logLevel = 'warn';
13498
- if (typeof apiKeyOrArgs === 'string') {
13499
- logLevel = opts?.logLevel || logLevel;
13500
- logger = opts?.logger || logger;
13501
- if (opts?.enableTimerWorker)
13502
- enableTimerWorker();
13503
- }
13504
- else {
13505
- logLevel = apiKeyOrArgs.options?.logLevel || logLevel;
13506
- logger = apiKeyOrArgs.options?.logger || logger;
13507
- if (apiKeyOrArgs.options?.enableTimerWorker)
13508
- enableTimerWorker();
13509
- }
13510
- setLogger(logger, logLevel);
13534
+ return withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.connectAnonymousUser(user, tokenOrProvider));
13535
+ };
13536
+ const apiKey = typeof apiKeyOrArgs === 'string' ? apiKeyOrArgs : apiKeyOrArgs.apiKey;
13537
+ const clientOptions = typeof apiKeyOrArgs === 'string' ? opts : apiKeyOrArgs.options;
13538
+ if (clientOptions?.enableTimerWorker)
13539
+ enableTimerWorker();
13540
+ const rootLogger = clientOptions?.logger || logToConsole;
13541
+ setLogger(rootLogger, clientOptions?.logLevel || 'warn');
13511
13542
  this.logger = getLogger(['client']);
13512
- const coordinatorLogger = getLogger(['coordinator']);
13513
- if (typeof apiKeyOrArgs === 'string') {
13514
- this.streamClient = new StreamClient(apiKeyOrArgs, {
13515
- persistUserOnConnectionFailure: true,
13516
- ...opts,
13517
- logLevel,
13518
- logger: coordinatorLogger,
13519
- });
13520
- }
13521
- else {
13522
- this.streamClient = new StreamClient(apiKeyOrArgs.apiKey, {
13523
- persistUserOnConnectionFailure: true,
13524
- ...apiKeyOrArgs.options,
13525
- logLevel,
13526
- logger: coordinatorLogger,
13527
- });
13528
- const sdkInfo = getSdkInfo();
13529
- if (sdkInfo) {
13530
- const sdkName = SdkType[sdkInfo.type].toLowerCase();
13531
- const sdkVersion = `${sdkInfo.major}.${sdkInfo.minor}.${sdkInfo.patch}`;
13532
- const userAgent = this.streamClient.getUserAgent();
13533
- this.streamClient.setUserAgent(`${userAgent}-video-${sdkName}-sdk-${sdkVersion}`);
13534
- }
13535
- }
13543
+ this.streamClient = createCoordinatorClient(apiKey, clientOptions);
13536
13544
  this.writeableStateStore = new StreamVideoWriteableStateStore();
13537
13545
  this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(this.writeableStateStore);
13538
- if (typeof apiKeyOrArgs !== 'string') {
13546
+ if (typeof apiKeyOrArgs !== 'string' && apiKeyOrArgs.user) {
13539
13547
  const user = apiKeyOrArgs.user;
13540
- const token = apiKeyOrArgs.token || apiKeyOrArgs.tokenProvider;
13541
- if (user) {
13542
- let id = user.id;
13543
- if (user.type === 'anonymous') {
13544
- id = '!anon';
13545
- }
13546
- if (id) {
13547
- if (StreamVideoClient._instanceMap.has(apiKeyOrArgs.apiKey + id)) {
13548
- this.logger('warn', `A StreamVideoClient already exists for ${user.type === 'anonymous' ? 'an anonymous user' : id}; Prefer using getOrCreateInstance method`);
13549
- }
13550
- user.id = id;
13551
- StreamVideoClient._instanceMap.set(apiKeyOrArgs.apiKey + id, this);
13552
- }
13553
- this.connectUser(user, token).catch((err) => {
13554
- this.logger('error', 'Failed to connect', err);
13555
- });
13556
- }
13548
+ if (user.type === 'anonymous')
13549
+ user.id = '!anon';
13550
+ if (user.id)
13551
+ this.registerClientInstance(apiKey, user);
13552
+ const tokenOrProvider = createTokenOrProvider(apiKeyOrArgs);
13553
+ this.connectUser(user, tokenOrProvider).catch((err) => {
13554
+ this.logger('error', 'Failed to connect', err);
13555
+ });
13557
13556
  }
13558
13557
  }
13558
+ /**
13559
+ * Gets or creates a StreamVideoClient instance based on the given options.
13560
+ */
13559
13561
  static getOrCreateInstance(args) {
13560
- const user = args.user;
13561
- if (!user.id) {
13562
- if (args.user.type === 'anonymous') {
13563
- user.id = '!anon';
13564
- }
13565
- else {
13566
- throw new Error('User ID is required for a non-anonymous user');
13567
- }
13568
- }
13569
- if (!args.token && !args.tokenProvider) {
13570
- if (args.user.type !== 'anonymous' && args.user.type !== 'guest') {
13571
- throw new Error('TokenProvider or token is required for a user that is not a guest or anonymous');
13572
- }
13562
+ const { apiKey, user, token, tokenProvider } = args;
13563
+ if (!user.id && user.type !== 'anonymous') {
13564
+ throw new Error('user.id is required for a non-anonymous user');
13573
13565
  }
13574
- let instance = StreamVideoClient._instanceMap.get(args.apiKey + user.id);
13575
- if (!instance) {
13576
- instance = new StreamVideoClient({ ...args, user });
13566
+ if (!token &&
13567
+ !tokenProvider &&
13568
+ user.type !== 'anonymous' &&
13569
+ user.type !== 'guest') {
13570
+ throw new Error('tokenProvider or token is required for a authenticated users');
13577
13571
  }
13578
- return instance;
13572
+ return (StreamVideoClient._instances.get(getInstanceKey(apiKey, user)) ||
13573
+ new StreamVideoClient(args));
13579
13574
  }
13580
13575
  /**
13581
13576
  * Return the reactive state store, use this if you want to be notified about changes to the client state
@@ -13584,7 +13579,7 @@ class StreamVideoClient {
13584
13579
  return this.readOnlyStateStore;
13585
13580
  }
13586
13581
  }
13587
- StreamVideoClient._instanceMap = new Map();
13582
+ StreamVideoClient._instances = new Map();
13588
13583
 
13589
13584
  export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsResponseModeEnum, InputMediaDeviceManager, InputMediaDeviceManagerState, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceInfo, getDeviceState, getLogLevel, getLogger, getOSInfo, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasScreenShare, hasScreenShareAudio, hasVideo, isPinned, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking };
13590
13585
  //# sourceMappingURL=index.browser.es.js.map