stream-chat 9.52.0 → 9.53.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.
@@ -159,6 +159,7 @@ var Campaign = class {
159
159
  };
160
160
 
161
161
  // src/channel_batch_updater.ts
162
+ var isChannelBatchDataUpdateOptions = (update) => "data" in update || "custom_set" in update || "custom_unset" in update;
162
163
  var ChannelBatchUpdater = class {
163
164
  constructor(client) {
164
165
  this.client = client;
@@ -297,19 +298,14 @@ var ChannelBatchUpdater = class {
297
298
  filter
298
299
  });
299
300
  }
300
- // Data operations
301
- /**
302
- * updateData - Update data on channels matching the filter
303
- *
304
- * @param {UpdateChannelsBatchFilters} filter Filter to select channels
305
- * @param {BatchChannelDataUpdate} data Data to update
306
- * @return {Promise<APIResponse & UpdateChannelsBatchResponse>} The server response
307
- */
308
- async updateData(filter, data) {
301
+ async updateData(filter, update) {
302
+ const { data, custom_set, custom_unset } = isChannelBatchDataUpdateOptions(update) ? update : { data: update };
309
303
  return await this.client.updateChannelsBatch({
310
304
  operation: "updateData",
311
305
  filter,
312
- data
306
+ ...data && { data },
307
+ ...custom_set && { custom_set },
308
+ ...custom_unset && { custom_unset }
313
309
  });
314
310
  }
315
311
  };
@@ -364,6 +360,22 @@ function logChatPromiseExecution(promise, name) {
364
360
  console.warn(`failed to do ${name}, ran into error: `, error);
365
361
  });
366
362
  }
363
+ var noopLogger = () => null;
364
+ var logError = (logger, error, event) => {
365
+ const message = "Unhandled error in event listener";
366
+ logger("error", message, { error, event });
367
+ if (logger === noopLogger) console.error(message, error, event);
368
+ };
369
+ var invokeEventListener = (listener, event, logger) => {
370
+ try {
371
+ const result = listener(event);
372
+ if (result && typeof result.then === "function") {
373
+ Promise.resolve(result).catch((error) => logError(logger, error, event));
374
+ }
375
+ } catch (error) {
376
+ logError(logger, error, event);
377
+ }
378
+ };
367
379
  var sleep = (m) => new Promise((r) => setTimeout(r, m));
368
380
  function isFunction(value) {
369
381
  return typeof value === "function" || value instanceof Function || Object.prototype.toString.call(value) === "[object Function]";
@@ -411,7 +423,6 @@ var channelHasReadEvents = (channel) => {
411
423
  return !(Array.isArray(ownCapabilities) && !ownCapabilities.includes("read-events"));
412
424
  };
413
425
  var channelTracksReadLocally = (channel) => !channelHasReadEvents(channel) && !!channel?.getClient().options.isLocalUnreadCountEnabled;
414
- var userHasReadReceipts = (client) => client.user?.privacy_settings?.read_receipts?.enabled ?? true;
415
426
  function addFileToFormData(uri, name, contentType) {
416
427
  const data = new FormData();
417
428
  if (isReadableStream(uri) || isBuffer(uri) || isFileWebAPI(uri) || isBlobWebAPI(uri)) {
@@ -8964,7 +8975,6 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
8964
8975
  * @param options
8965
8976
  */
8966
8977
  this.markRead = async (collection, options) => {
8967
- if (!userHasReadReceipts(this.client)) return null;
8968
8978
  let result = null;
8969
8979
  if (isChannel(collection)) {
8970
8980
  result = await collection.markAsReadRequest(options);
@@ -8974,7 +8984,7 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
8974
8984
  thread_id: collection.id
8975
8985
  });
8976
8986
  }
8977
- this.removeCandidateFor(collection);
8987
+ if (result) this.removeCandidateFor(collection);
8978
8988
  return result;
8979
8989
  };
8980
8990
  /**
@@ -9359,18 +9369,14 @@ var Channel = class {
9359
9369
  return await this.query(defaultOptions, "latest");
9360
9370
  };
9361
9371
  this._callChannelListeners = (event) => {
9362
- const channel = this;
9363
- const listeners = [];
9364
- if (channel.listeners.all) {
9365
- listeners.push(...channel.listeners.all);
9366
- }
9367
- if (channel.listeners[event.type]) {
9368
- listeners.push(...channel.listeners[event.type]);
9369
- }
9372
+ const listeners = [
9373
+ ...this.listeners.all ?? [],
9374
+ ...this.listeners[event.type] ?? []
9375
+ ];
9376
+ const { logger } = this._client;
9370
9377
  for (const listener of listeners) {
9371
- if (typeof listener !== "string") {
9372
- listener(event);
9373
- }
9378
+ if (typeof listener === "string") continue;
9379
+ invokeEventListener(listener, event, logger);
9374
9380
  }
9375
9381
  };
9376
9382
  /**
@@ -10284,14 +10290,15 @@ var Channel = class {
10284
10290
  return await this.getClient().messageDeliveryReporter.markRead(this, data);
10285
10291
  }
10286
10292
  /**
10287
- * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
10293
+ * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
10294
+ * enabled and the user holds the `read-events` capability on this channel
10288
10295
  *
10289
10296
  * @param {MarkReadOptions} data
10290
10297
  * @return {Promise<EventAPIResponse | null>} Description
10291
10298
  */
10292
10299
  async markAsReadRequest(data = {}) {
10293
10300
  this._checkInitialized();
10294
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10301
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10295
10302
  return null;
10296
10303
  }
10297
10304
  return await this.getClient().post(this._channelURL() + "/read", {
@@ -10299,14 +10306,15 @@ var Channel = class {
10299
10306
  });
10300
10307
  }
10301
10308
  /**
10302
- * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is enabled
10309
+ * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
10310
+ * enabled and the user holds the `read-events` capability on this channel
10303
10311
  *
10304
10312
  * @param {MarkUnreadOptions} data
10305
10313
  * @return {APIResponse} An API response
10306
10314
  */
10307
10315
  async markUnread(data) {
10308
10316
  this._checkInitialized();
10309
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10317
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10310
10318
  return Promise.resolve(null);
10311
10319
  }
10312
10320
  return await this.getClient().post(this._channelURL() + "/unread", {
@@ -14352,6 +14360,8 @@ var StreamChat = class _StreamChat {
14352
14360
  * The main use case for SDKs who do independent state recovery for channels.
14353
14361
  */
14354
14362
  this.preventThreadCleanup = false;
14363
+ this._wsPromiseSettled = true;
14364
+ this._wsConnectId = 0;
14355
14365
  this.nextRequestAbortController = null;
14356
14366
  /**
14357
14367
  * @private
@@ -14397,17 +14407,20 @@ var StreamChat = class _StreamChat {
14397
14407
  const setTokenPromise = this._setToken(user, userTokenOrProvider);
14398
14408
  this._setUser(user);
14399
14409
  const wsPromise = this.openConnection();
14400
- this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
14410
+ const setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
14401
14411
  (result) => result[1]
14402
14412
  // We only return connection promise;
14403
14413
  );
14414
+ this.setUserPromise = setUserPromise;
14404
14415
  try {
14405
- return await this.setUserPromise;
14416
+ return await setUserPromise;
14406
14417
  } catch (err) {
14407
- if (this.persistUserOnConnectionFailure) {
14408
- this.closeConnection();
14409
- } else {
14410
- this.disconnectUser();
14418
+ if (this.setUserPromise === setUserPromise && this.userID === user.id) {
14419
+ if (this.persistUserOnConnectionFailure) {
14420
+ this.closeConnection();
14421
+ } else {
14422
+ this.disconnectUser();
14423
+ }
14411
14424
  }
14412
14425
  throw err;
14413
14426
  }
@@ -14503,9 +14516,50 @@ var StreamChat = class _StreamChat {
14503
14516
  return;
14504
14517
  }
14505
14518
  this.clientID = `${this.userID}--${randomId()}`;
14506
- this.wsPromise = this.connect();
14519
+ const wsPromise = this._bindWsPromise(this.connect());
14507
14520
  this._startCleaning();
14508
- return this.wsPromise;
14521
+ return wsPromise;
14522
+ };
14523
+ /**
14524
+ * Rejects the shared `wsPromise`, invalidates any in-flight connect attempt
14525
+ */
14526
+ this._rejectPendingWsPromise = (reason) => {
14527
+ if (!this._wsPromiseSettled) {
14528
+ this._wsConnectId += 1;
14529
+ this._wsPromiseSettled = true;
14530
+ this.wsPromise?.catch(() => {
14531
+ });
14532
+ this._rejectWsPromise?.(reason);
14533
+ }
14534
+ };
14535
+ /**
14536
+ * Keep a single pending `wsPromise` across close/reopen so callers that already
14537
+ * `await this.wsPromise` are not stranded when `openConnection` starts a new connect.
14538
+ */
14539
+ this._bindWsPromise = (connectPromise) => {
14540
+ const connectId = ++this._wsConnectId;
14541
+ let pending = this.wsPromise;
14542
+ if (this._wsPromiseSettled || !pending) {
14543
+ this._wsPromiseSettled = false;
14544
+ pending = new Promise((resolve, reject) => {
14545
+ this._resolveWsPromise = resolve;
14546
+ this._rejectWsPromise = reject;
14547
+ });
14548
+ this.wsPromise = pending;
14549
+ }
14550
+ connectPromise.then(
14551
+ (value) => {
14552
+ if (connectId !== this._wsConnectId) return;
14553
+ this._wsPromiseSettled = true;
14554
+ this._resolveWsPromise?.(value);
14555
+ },
14556
+ (error) => {
14557
+ if (connectId !== this._wsConnectId) return;
14558
+ this._wsPromiseSettled = true;
14559
+ this._rejectWsPromise?.(error);
14560
+ }
14561
+ );
14562
+ return pending;
14509
14563
  };
14510
14564
  /**
14511
14565
  * @deprecated Please use client.openConnction instead.
@@ -14539,6 +14593,10 @@ var StreamChat = class _StreamChat {
14539
14593
  delete this._user;
14540
14594
  delete this.userID;
14541
14595
  this.anonymous = false;
14596
+ this._rejectPendingWsPromise(
14597
+ new Error("Connection was closed because disconnectUser() was called")
14598
+ );
14599
+ this.wsPromise = null;
14542
14600
  const closePromise = this.closeConnection(timeout);
14543
14601
  for (const channel of Object.values(this.activeChannels)) {
14544
14602
  channel._disconnect();
@@ -14768,16 +14826,12 @@ var StreamChat = class _StreamChat {
14768
14826
  }
14769
14827
  };
14770
14828
  this._callClientListeners = (event) => {
14771
- const client = this;
14772
- const listeners = [];
14773
- if (client.listeners.all) {
14774
- listeners.push(...client.listeners.all);
14775
- }
14776
- if (client.listeners[event.type]) {
14777
- listeners.push(...client.listeners[event.type]);
14778
- }
14829
+ const listeners = [
14830
+ ...this.listeners.all ?? [],
14831
+ ...this.listeners[event.type] ?? []
14832
+ ];
14779
14833
  for (const listener of listeners) {
14780
- listener(event);
14834
+ invokeEventListener(listener, event, this.logger);
14781
14835
  }
14782
14836
  };
14783
14837
  this.recoverState = async () => {
@@ -14813,7 +14867,9 @@ var StreamChat = class _StreamChat {
14813
14867
  type: "connection.recovered"
14814
14868
  });
14815
14869
  }
14816
- this.wsPromise = Promise.resolve();
14870
+ if (this._wsPromiseSettled) {
14871
+ this.wsPromise = Promise.resolve();
14872
+ }
14817
14873
  this.setUserPromise = Promise.resolve();
14818
14874
  };
14819
14875
  /**
@@ -14931,7 +14987,7 @@ var StreamChat = class _StreamChat {
14931
14987
  return this.userAgent;
14932
14988
  }
14933
14989
  if (!this.cachedUserAgent) {
14934
- const version = "9.52.0";
14990
+ const version = "9.53.0";
14935
14991
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14936
14992
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14937
14993
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -15020,7 +15076,7 @@ var StreamChat = class _StreamChat {
15020
15076
  this.defaultWSTimeoutWithFallback = 6 * 1e3;
15021
15077
  this.defaultWSTimeout = 15 * 1e3;
15022
15078
  this.axiosInstance.defaults.paramsSerializer = axiosParamsSerializer;
15023
- this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : () => null;
15079
+ this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : noopLogger;
15024
15080
  this.recoverStateOnReconnect = this.options.recoverStateOnReconnect;
15025
15081
  this.threads = new ThreadManager({ client: this });
15026
15082
  this.polls = new PollManager({ client: this });
@@ -18173,6 +18229,10 @@ var StreamChat = class _StreamChat {
18173
18229
  /**
18174
18230
  * Update Channels Batch
18175
18231
  *
18232
+ * For the `updateData` operation, `data.custom` replaces the channel's whole
18233
+ * custom object, while the root-level `custom_set` / `custom_unset` patch
18234
+ * individual keys and leave the rest untouched.
18235
+ *
18176
18236
  * @param {UpdateChannelsBatchOptions} payload for updating channels in batch
18177
18237
  * @return {Promise<APIResponse & UpdateChannelsBatchResponse>} The server response
18178
18238
  */