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.
@@ -382,6 +382,7 @@ var Campaign = class {
382
382
  };
383
383
 
384
384
  // src/channel_batch_updater.ts
385
+ var isChannelBatchDataUpdateOptions = (update) => "data" in update || "custom_set" in update || "custom_unset" in update;
385
386
  var ChannelBatchUpdater = class {
386
387
  constructor(client) {
387
388
  this.client = client;
@@ -520,19 +521,14 @@ var ChannelBatchUpdater = class {
520
521
  filter
521
522
  });
522
523
  }
523
- // Data operations
524
- /**
525
- * updateData - Update data on channels matching the filter
526
- *
527
- * @param {UpdateChannelsBatchFilters} filter Filter to select channels
528
- * @param {BatchChannelDataUpdate} data Data to update
529
- * @return {Promise<APIResponse & UpdateChannelsBatchResponse>} The server response
530
- */
531
- async updateData(filter, data) {
524
+ async updateData(filter, update) {
525
+ const { data, custom_set, custom_unset } = isChannelBatchDataUpdateOptions(update) ? update : { data: update };
532
526
  return await this.client.updateChannelsBatch({
533
527
  operation: "updateData",
534
528
  filter,
535
- data
529
+ ...data && { data },
530
+ ...custom_set && { custom_set },
531
+ ...custom_unset && { custom_unset }
536
532
  });
537
533
  }
538
534
  };
@@ -587,6 +583,22 @@ function logChatPromiseExecution(promise, name) {
587
583
  console.warn(`failed to do ${name}, ran into error: `, error);
588
584
  });
589
585
  }
586
+ var noopLogger = () => null;
587
+ var logError = (logger, error, event) => {
588
+ const message = "Unhandled error in event listener";
589
+ logger("error", message, { error, event });
590
+ if (logger === noopLogger) console.error(message, error, event);
591
+ };
592
+ var invokeEventListener = (listener, event, logger) => {
593
+ try {
594
+ const result = listener(event);
595
+ if (result && typeof result.then === "function") {
596
+ Promise.resolve(result).catch((error) => logError(logger, error, event));
597
+ }
598
+ } catch (error) {
599
+ logError(logger, error, event);
600
+ }
601
+ };
590
602
  var sleep = (m) => new Promise((r) => setTimeout(r, m));
591
603
  function isFunction(value) {
592
604
  return typeof value === "function" || value instanceof Function || Object.prototype.toString.call(value) === "[object Function]";
@@ -634,7 +646,6 @@ var channelHasReadEvents = (channel) => {
634
646
  return !(Array.isArray(ownCapabilities) && !ownCapabilities.includes("read-events"));
635
647
  };
636
648
  var channelTracksReadLocally = (channel) => !channelHasReadEvents(channel) && !!channel?.getClient().options.isLocalUnreadCountEnabled;
637
- var userHasReadReceipts = (client) => client.user?.privacy_settings?.read_receipts?.enabled ?? true;
638
649
  function addFileToFormData(uri, name, contentType) {
639
650
  const data = new import_form_data.default();
640
651
  if (isReadableStream(uri) || isBuffer(uri) || isFileWebAPI(uri) || isBlobWebAPI(uri)) {
@@ -9187,7 +9198,6 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
9187
9198
  * @param options
9188
9199
  */
9189
9200
  this.markRead = async (collection, options) => {
9190
- if (!userHasReadReceipts(this.client)) return null;
9191
9201
  let result = null;
9192
9202
  if (isChannel(collection)) {
9193
9203
  result = await collection.markAsReadRequest(options);
@@ -9197,7 +9207,7 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
9197
9207
  thread_id: collection.id
9198
9208
  });
9199
9209
  }
9200
- this.removeCandidateFor(collection);
9210
+ if (result) this.removeCandidateFor(collection);
9201
9211
  return result;
9202
9212
  };
9203
9213
  /**
@@ -9582,18 +9592,14 @@ var Channel = class {
9582
9592
  return await this.query(defaultOptions, "latest");
9583
9593
  };
9584
9594
  this._callChannelListeners = (event) => {
9585
- const channel = this;
9586
- const listeners = [];
9587
- if (channel.listeners.all) {
9588
- listeners.push(...channel.listeners.all);
9589
- }
9590
- if (channel.listeners[event.type]) {
9591
- listeners.push(...channel.listeners[event.type]);
9592
- }
9595
+ const listeners = [
9596
+ ...this.listeners.all ?? [],
9597
+ ...this.listeners[event.type] ?? []
9598
+ ];
9599
+ const { logger } = this._client;
9593
9600
  for (const listener of listeners) {
9594
- if (typeof listener !== "string") {
9595
- listener(event);
9596
- }
9601
+ if (typeof listener === "string") continue;
9602
+ invokeEventListener(listener, event, logger);
9597
9603
  }
9598
9604
  };
9599
9605
  /**
@@ -10507,14 +10513,15 @@ var Channel = class {
10507
10513
  return await this.getClient().messageDeliveryReporter.markRead(this, data);
10508
10514
  }
10509
10515
  /**
10510
- * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
10516
+ * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
10517
+ * enabled and the user holds the `read-events` capability on this channel
10511
10518
  *
10512
10519
  * @param {MarkReadOptions} data
10513
10520
  * @return {Promise<EventAPIResponse | null>} Description
10514
10521
  */
10515
10522
  async markAsReadRequest(data = {}) {
10516
10523
  this._checkInitialized();
10517
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10524
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10518
10525
  return null;
10519
10526
  }
10520
10527
  return await this.getClient().post(this._channelURL() + "/read", {
@@ -10522,14 +10529,15 @@ var Channel = class {
10522
10529
  });
10523
10530
  }
10524
10531
  /**
10525
- * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is enabled
10532
+ * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
10533
+ * enabled and the user holds the `read-events` capability on this channel
10526
10534
  *
10527
10535
  * @param {MarkUnreadOptions} data
10528
10536
  * @return {APIResponse} An API response
10529
10537
  */
10530
10538
  async markUnread(data) {
10531
10539
  this._checkInitialized();
10532
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10540
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10533
10541
  return Promise.resolve(null);
10534
10542
  }
10535
10543
  return await this.getClient().post(this._channelURL() + "/unread", {
@@ -14575,6 +14583,8 @@ var StreamChat = class _StreamChat {
14575
14583
  * The main use case for SDKs who do independent state recovery for channels.
14576
14584
  */
14577
14585
  this.preventThreadCleanup = false;
14586
+ this._wsPromiseSettled = true;
14587
+ this._wsConnectId = 0;
14578
14588
  this.nextRequestAbortController = null;
14579
14589
  /**
14580
14590
  * @private
@@ -14620,17 +14630,20 @@ var StreamChat = class _StreamChat {
14620
14630
  const setTokenPromise = this._setToken(user, userTokenOrProvider);
14621
14631
  this._setUser(user);
14622
14632
  const wsPromise = this.openConnection();
14623
- this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
14633
+ const setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
14624
14634
  (result) => result[1]
14625
14635
  // We only return connection promise;
14626
14636
  );
14637
+ this.setUserPromise = setUserPromise;
14627
14638
  try {
14628
- return await this.setUserPromise;
14639
+ return await setUserPromise;
14629
14640
  } catch (err) {
14630
- if (this.persistUserOnConnectionFailure) {
14631
- this.closeConnection();
14632
- } else {
14633
- this.disconnectUser();
14641
+ if (this.setUserPromise === setUserPromise && this.userID === user.id) {
14642
+ if (this.persistUserOnConnectionFailure) {
14643
+ this.closeConnection();
14644
+ } else {
14645
+ this.disconnectUser();
14646
+ }
14634
14647
  }
14635
14648
  throw err;
14636
14649
  }
@@ -14726,9 +14739,50 @@ var StreamChat = class _StreamChat {
14726
14739
  return;
14727
14740
  }
14728
14741
  this.clientID = `${this.userID}--${randomId()}`;
14729
- this.wsPromise = this.connect();
14742
+ const wsPromise = this._bindWsPromise(this.connect());
14730
14743
  this._startCleaning();
14731
- return this.wsPromise;
14744
+ return wsPromise;
14745
+ };
14746
+ /**
14747
+ * Rejects the shared `wsPromise`, invalidates any in-flight connect attempt
14748
+ */
14749
+ this._rejectPendingWsPromise = (reason) => {
14750
+ if (!this._wsPromiseSettled) {
14751
+ this._wsConnectId += 1;
14752
+ this._wsPromiseSettled = true;
14753
+ this.wsPromise?.catch(() => {
14754
+ });
14755
+ this._rejectWsPromise?.(reason);
14756
+ }
14757
+ };
14758
+ /**
14759
+ * Keep a single pending `wsPromise` across close/reopen so callers that already
14760
+ * `await this.wsPromise` are not stranded when `openConnection` starts a new connect.
14761
+ */
14762
+ this._bindWsPromise = (connectPromise) => {
14763
+ const connectId = ++this._wsConnectId;
14764
+ let pending = this.wsPromise;
14765
+ if (this._wsPromiseSettled || !pending) {
14766
+ this._wsPromiseSettled = false;
14767
+ pending = new Promise((resolve, reject) => {
14768
+ this._resolveWsPromise = resolve;
14769
+ this._rejectWsPromise = reject;
14770
+ });
14771
+ this.wsPromise = pending;
14772
+ }
14773
+ connectPromise.then(
14774
+ (value) => {
14775
+ if (connectId !== this._wsConnectId) return;
14776
+ this._wsPromiseSettled = true;
14777
+ this._resolveWsPromise?.(value);
14778
+ },
14779
+ (error) => {
14780
+ if (connectId !== this._wsConnectId) return;
14781
+ this._wsPromiseSettled = true;
14782
+ this._rejectWsPromise?.(error);
14783
+ }
14784
+ );
14785
+ return pending;
14732
14786
  };
14733
14787
  /**
14734
14788
  * @deprecated Please use client.openConnction instead.
@@ -14762,6 +14816,10 @@ var StreamChat = class _StreamChat {
14762
14816
  delete this._user;
14763
14817
  delete this.userID;
14764
14818
  this.anonymous = false;
14819
+ this._rejectPendingWsPromise(
14820
+ new Error("Connection was closed because disconnectUser() was called")
14821
+ );
14822
+ this.wsPromise = null;
14765
14823
  const closePromise = this.closeConnection(timeout);
14766
14824
  for (const channel of Object.values(this.activeChannels)) {
14767
14825
  channel._disconnect();
@@ -14991,16 +15049,12 @@ var StreamChat = class _StreamChat {
14991
15049
  }
14992
15050
  };
14993
15051
  this._callClientListeners = (event) => {
14994
- const client = this;
14995
- const listeners = [];
14996
- if (client.listeners.all) {
14997
- listeners.push(...client.listeners.all);
14998
- }
14999
- if (client.listeners[event.type]) {
15000
- listeners.push(...client.listeners[event.type]);
15001
- }
15052
+ const listeners = [
15053
+ ...this.listeners.all ?? [],
15054
+ ...this.listeners[event.type] ?? []
15055
+ ];
15002
15056
  for (const listener of listeners) {
15003
- listener(event);
15057
+ invokeEventListener(listener, event, this.logger);
15004
15058
  }
15005
15059
  };
15006
15060
  this.recoverState = async () => {
@@ -15036,7 +15090,9 @@ var StreamChat = class _StreamChat {
15036
15090
  type: "connection.recovered"
15037
15091
  });
15038
15092
  }
15039
- this.wsPromise = Promise.resolve();
15093
+ if (this._wsPromiseSettled) {
15094
+ this.wsPromise = Promise.resolve();
15095
+ }
15040
15096
  this.setUserPromise = Promise.resolve();
15041
15097
  };
15042
15098
  /**
@@ -15154,7 +15210,7 @@ var StreamChat = class _StreamChat {
15154
15210
  return this.userAgent;
15155
15211
  }
15156
15212
  if (!this.cachedUserAgent) {
15157
- const version = "9.52.0";
15213
+ const version = "9.53.0";
15158
15214
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
15159
15215
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
15160
15216
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -15243,7 +15299,7 @@ var StreamChat = class _StreamChat {
15243
15299
  this.defaultWSTimeoutWithFallback = 6 * 1e3;
15244
15300
  this.defaultWSTimeout = 15 * 1e3;
15245
15301
  this.axiosInstance.defaults.paramsSerializer = axiosParamsSerializer;
15246
- this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : () => null;
15302
+ this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : noopLogger;
15247
15303
  this.recoverStateOnReconnect = this.options.recoverStateOnReconnect;
15248
15304
  this.threads = new ThreadManager({ client: this });
15249
15305
  this.polls = new PollManager({ client: this });
@@ -18396,6 +18452,10 @@ var StreamChat = class _StreamChat {
18396
18452
  /**
18397
18453
  * Update Channels Batch
18398
18454
  *
18455
+ * For the `updateData` operation, `data.custom` replaces the channel's whole
18456
+ * custom object, while the root-level `custom_set` / `custom_unset` patch
18457
+ * individual keys and leave the rest untouched.
18458
+ *
18399
18459
  * @param {UpdateChannelsBatchOptions} payload for updating channels in batch
18400
18460
  * @return {Promise<APIResponse & UpdateChannelsBatchResponse>} The server response
18401
18461
  */