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.
@@ -351,6 +351,7 @@ var Campaign = class {
351
351
  };
352
352
 
353
353
  // src/channel_batch_updater.ts
354
+ var isChannelBatchDataUpdateOptions = (update) => "data" in update || "custom_set" in update || "custom_unset" in update;
354
355
  var ChannelBatchUpdater = class {
355
356
  constructor(client) {
356
357
  this.client = client;
@@ -489,19 +490,14 @@ var ChannelBatchUpdater = class {
489
490
  filter
490
491
  });
491
492
  }
492
- // Data operations
493
- /**
494
- * updateData - Update data on channels matching the filter
495
- *
496
- * @param {UpdateChannelsBatchFilters} filter Filter to select channels
497
- * @param {BatchChannelDataUpdate} data Data to update
498
- * @return {Promise<APIResponse & UpdateChannelsBatchResponse>} The server response
499
- */
500
- async updateData(filter, data) {
493
+ async updateData(filter, update) {
494
+ const { data, custom_set, custom_unset } = isChannelBatchDataUpdateOptions(update) ? update : { data: update };
501
495
  return await this.client.updateChannelsBatch({
502
496
  operation: "updateData",
503
497
  filter,
504
- data
498
+ ...data && { data },
499
+ ...custom_set && { custom_set },
500
+ ...custom_unset && { custom_unset }
505
501
  });
506
502
  }
507
503
  };
@@ -556,6 +552,22 @@ function logChatPromiseExecution(promise, name) {
556
552
  console.warn(`failed to do ${name}, ran into error: `, error);
557
553
  });
558
554
  }
555
+ var noopLogger = () => null;
556
+ var logError = (logger, error, event) => {
557
+ const message = "Unhandled error in event listener";
558
+ logger("error", message, { error, event });
559
+ if (logger === noopLogger) console.error(message, error, event);
560
+ };
561
+ var invokeEventListener = (listener, event, logger) => {
562
+ try {
563
+ const result = listener(event);
564
+ if (result && typeof result.then === "function") {
565
+ Promise.resolve(result).catch((error) => logError(logger, error, event));
566
+ }
567
+ } catch (error) {
568
+ logError(logger, error, event);
569
+ }
570
+ };
559
571
  var sleep = (m) => new Promise((r) => setTimeout(r, m));
560
572
  function isFunction(value) {
561
573
  return typeof value === "function" || value instanceof Function || Object.prototype.toString.call(value) === "[object Function]";
@@ -603,7 +615,6 @@ var channelHasReadEvents = (channel) => {
603
615
  return !(Array.isArray(ownCapabilities) && !ownCapabilities.includes("read-events"));
604
616
  };
605
617
  var channelTracksReadLocally = (channel) => !channelHasReadEvents(channel) && !!channel?.getClient().options.isLocalUnreadCountEnabled;
606
- var userHasReadReceipts = (client) => client.user?.privacy_settings?.read_receipts?.enabled ?? true;
607
618
  function addFileToFormData(uri, name, contentType) {
608
619
  const data = new import_form_data.default();
609
620
  if (isReadableStream(uri) || isBuffer(uri) || isFileWebAPI(uri) || isBlobWebAPI(uri)) {
@@ -9156,7 +9167,6 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
9156
9167
  * @param options
9157
9168
  */
9158
9169
  this.markRead = async (collection, options) => {
9159
- if (!userHasReadReceipts(this.client)) return null;
9160
9170
  let result = null;
9161
9171
  if (isChannel(collection)) {
9162
9172
  result = await collection.markAsReadRequest(options);
@@ -9166,7 +9176,7 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
9166
9176
  thread_id: collection.id
9167
9177
  });
9168
9178
  }
9169
- this.removeCandidateFor(collection);
9179
+ if (result) this.removeCandidateFor(collection);
9170
9180
  return result;
9171
9181
  };
9172
9182
  /**
@@ -9551,18 +9561,14 @@ var Channel = class {
9551
9561
  return await this.query(defaultOptions, "latest");
9552
9562
  };
9553
9563
  this._callChannelListeners = (event) => {
9554
- const channel = this;
9555
- const listeners = [];
9556
- if (channel.listeners.all) {
9557
- listeners.push(...channel.listeners.all);
9558
- }
9559
- if (channel.listeners[event.type]) {
9560
- listeners.push(...channel.listeners[event.type]);
9561
- }
9564
+ const listeners = [
9565
+ ...this.listeners.all ?? [],
9566
+ ...this.listeners[event.type] ?? []
9567
+ ];
9568
+ const { logger } = this._client;
9562
9569
  for (const listener of listeners) {
9563
- if (typeof listener !== "string") {
9564
- listener(event);
9565
- }
9570
+ if (typeof listener === "string") continue;
9571
+ invokeEventListener(listener, event, logger);
9566
9572
  }
9567
9573
  };
9568
9574
  /**
@@ -10476,14 +10482,15 @@ var Channel = class {
10476
10482
  return await this.getClient().messageDeliveryReporter.markRead(this, data);
10477
10483
  }
10478
10484
  /**
10479
- * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
10485
+ * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
10486
+ * enabled and the user holds the `read-events` capability on this channel
10480
10487
  *
10481
10488
  * @param {MarkReadOptions} data
10482
10489
  * @return {Promise<EventAPIResponse | null>} Description
10483
10490
  */
10484
10491
  async markAsReadRequest(data = {}) {
10485
10492
  this._checkInitialized();
10486
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10493
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10487
10494
  return null;
10488
10495
  }
10489
10496
  return await this.getClient().post(this._channelURL() + "/read", {
@@ -10491,14 +10498,15 @@ var Channel = class {
10491
10498
  });
10492
10499
  }
10493
10500
  /**
10494
- * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is enabled
10501
+ * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
10502
+ * enabled and the user holds the `read-events` capability on this channel
10495
10503
  *
10496
10504
  * @param {MarkUnreadOptions} data
10497
10505
  * @return {APIResponse} An API response
10498
10506
  */
10499
10507
  async markUnread(data) {
10500
10508
  this._checkInitialized();
10501
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10509
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10502
10510
  return Promise.resolve(null);
10503
10511
  }
10504
10512
  return await this.getClient().post(this._channelURL() + "/unread", {
@@ -14544,6 +14552,8 @@ var StreamChat = class _StreamChat {
14544
14552
  * The main use case for SDKs who do independent state recovery for channels.
14545
14553
  */
14546
14554
  this.preventThreadCleanup = false;
14555
+ this._wsPromiseSettled = true;
14556
+ this._wsConnectId = 0;
14547
14557
  this.nextRequestAbortController = null;
14548
14558
  /**
14549
14559
  * @private
@@ -14589,17 +14599,20 @@ var StreamChat = class _StreamChat {
14589
14599
  const setTokenPromise = this._setToken(user, userTokenOrProvider);
14590
14600
  this._setUser(user);
14591
14601
  const wsPromise = this.openConnection();
14592
- this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
14602
+ const setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(
14593
14603
  (result) => result[1]
14594
14604
  // We only return connection promise;
14595
14605
  );
14606
+ this.setUserPromise = setUserPromise;
14596
14607
  try {
14597
- return await this.setUserPromise;
14608
+ return await setUserPromise;
14598
14609
  } catch (err) {
14599
- if (this.persistUserOnConnectionFailure) {
14600
- this.closeConnection();
14601
- } else {
14602
- this.disconnectUser();
14610
+ if (this.setUserPromise === setUserPromise && this.userID === user.id) {
14611
+ if (this.persistUserOnConnectionFailure) {
14612
+ this.closeConnection();
14613
+ } else {
14614
+ this.disconnectUser();
14615
+ }
14603
14616
  }
14604
14617
  throw err;
14605
14618
  }
@@ -14695,9 +14708,50 @@ var StreamChat = class _StreamChat {
14695
14708
  return;
14696
14709
  }
14697
14710
  this.clientID = `${this.userID}--${randomId()}`;
14698
- this.wsPromise = this.connect();
14711
+ const wsPromise = this._bindWsPromise(this.connect());
14699
14712
  this._startCleaning();
14700
- return this.wsPromise;
14713
+ return wsPromise;
14714
+ };
14715
+ /**
14716
+ * Rejects the shared `wsPromise`, invalidates any in-flight connect attempt
14717
+ */
14718
+ this._rejectPendingWsPromise = (reason) => {
14719
+ if (!this._wsPromiseSettled) {
14720
+ this._wsConnectId += 1;
14721
+ this._wsPromiseSettled = true;
14722
+ this.wsPromise?.catch(() => {
14723
+ });
14724
+ this._rejectWsPromise?.(reason);
14725
+ }
14726
+ };
14727
+ /**
14728
+ * Keep a single pending `wsPromise` across close/reopen so callers that already
14729
+ * `await this.wsPromise` are not stranded when `openConnection` starts a new connect.
14730
+ */
14731
+ this._bindWsPromise = (connectPromise) => {
14732
+ const connectId = ++this._wsConnectId;
14733
+ let pending = this.wsPromise;
14734
+ if (this._wsPromiseSettled || !pending) {
14735
+ this._wsPromiseSettled = false;
14736
+ pending = new Promise((resolve, reject) => {
14737
+ this._resolveWsPromise = resolve;
14738
+ this._rejectWsPromise = reject;
14739
+ });
14740
+ this.wsPromise = pending;
14741
+ }
14742
+ connectPromise.then(
14743
+ (value) => {
14744
+ if (connectId !== this._wsConnectId) return;
14745
+ this._wsPromiseSettled = true;
14746
+ this._resolveWsPromise?.(value);
14747
+ },
14748
+ (error) => {
14749
+ if (connectId !== this._wsConnectId) return;
14750
+ this._wsPromiseSettled = true;
14751
+ this._rejectWsPromise?.(error);
14752
+ }
14753
+ );
14754
+ return pending;
14701
14755
  };
14702
14756
  /**
14703
14757
  * @deprecated Please use client.openConnction instead.
@@ -14731,6 +14785,10 @@ var StreamChat = class _StreamChat {
14731
14785
  delete this._user;
14732
14786
  delete this.userID;
14733
14787
  this.anonymous = false;
14788
+ this._rejectPendingWsPromise(
14789
+ new Error("Connection was closed because disconnectUser() was called")
14790
+ );
14791
+ this.wsPromise = null;
14734
14792
  const closePromise = this.closeConnection(timeout);
14735
14793
  for (const channel of Object.values(this.activeChannels)) {
14736
14794
  channel._disconnect();
@@ -14960,16 +15018,12 @@ var StreamChat = class _StreamChat {
14960
15018
  }
14961
15019
  };
14962
15020
  this._callClientListeners = (event) => {
14963
- const client = this;
14964
- const listeners = [];
14965
- if (client.listeners.all) {
14966
- listeners.push(...client.listeners.all);
14967
- }
14968
- if (client.listeners[event.type]) {
14969
- listeners.push(...client.listeners[event.type]);
14970
- }
15021
+ const listeners = [
15022
+ ...this.listeners.all ?? [],
15023
+ ...this.listeners[event.type] ?? []
15024
+ ];
14971
15025
  for (const listener of listeners) {
14972
- listener(event);
15026
+ invokeEventListener(listener, event, this.logger);
14973
15027
  }
14974
15028
  };
14975
15029
  this.recoverState = async () => {
@@ -15005,7 +15059,9 @@ var StreamChat = class _StreamChat {
15005
15059
  type: "connection.recovered"
15006
15060
  });
15007
15061
  }
15008
- this.wsPromise = Promise.resolve();
15062
+ if (this._wsPromiseSettled) {
15063
+ this.wsPromise = Promise.resolve();
15064
+ }
15009
15065
  this.setUserPromise = Promise.resolve();
15010
15066
  };
15011
15067
  /**
@@ -15123,7 +15179,7 @@ var StreamChat = class _StreamChat {
15123
15179
  return this.userAgent;
15124
15180
  }
15125
15181
  if (!this.cachedUserAgent) {
15126
- const version = "9.52.0";
15182
+ const version = "9.53.0";
15127
15183
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
15128
15184
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
15129
15185
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -15212,7 +15268,7 @@ var StreamChat = class _StreamChat {
15212
15268
  this.defaultWSTimeoutWithFallback = 6 * 1e3;
15213
15269
  this.defaultWSTimeout = 15 * 1e3;
15214
15270
  this.axiosInstance.defaults.paramsSerializer = axiosParamsSerializer;
15215
- this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : () => null;
15271
+ this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : noopLogger;
15216
15272
  this.recoverStateOnReconnect = this.options.recoverStateOnReconnect;
15217
15273
  this.threads = new ThreadManager({ client: this });
15218
15274
  this.polls = new PollManager({ client: this });
@@ -18365,6 +18421,10 @@ var StreamChat = class _StreamChat {
18365
18421
  /**
18366
18422
  * Update Channels Batch
18367
18423
  *
18424
+ * For the `updateData` operation, `data.custom` replaces the channel's whole
18425
+ * custom object, while the root-level `custom_set` / `custom_unset` patch
18426
+ * individual keys and leave the rest untouched.
18427
+ *
18368
18428
  * @param {UpdateChannelsBatchOptions} payload for updating channels in batch
18369
18429
  * @return {Promise<APIResponse & UpdateChannelsBatchResponse>} The server response
18370
18430
  */