stream-chat 9.48.0 → 9.50.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.
@@ -590,6 +590,12 @@ function isOwnUserBaseProperty(property) {
590
590
  };
591
591
  return ownUserBaseProperties[property];
592
592
  }
593
+ var channelHasReadEvents = (channel) => {
594
+ const ownCapabilities = channel?.data?.own_capabilities;
595
+ return !(Array.isArray(ownCapabilities) && !ownCapabilities.includes("read-events"));
596
+ };
597
+ var channelTracksReadLocally = (channel) => !channelHasReadEvents(channel) && !!channel?.getClient().options.isLocalUnreadCountEnabled;
598
+ var userHasReadReceipts = (client) => client.user?.privacy_settings?.read_receipts?.enabled ?? true;
593
599
  function addFileToFormData(uri, name, contentType) {
594
600
  const data = new import_form_data.default();
595
601
  if (isReadableStream(uri) || isBuffer(uri) || isFileWebAPI(uri) || isBlobWebAPI(uri)) {
@@ -8751,6 +8757,7 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
8751
8757
  * @param options
8752
8758
  */
8753
8759
  this.markRead = async (collection, options) => {
8760
+ if (!userHasReadReceipts(this.client)) return null;
8754
8761
  let result = null;
8755
8762
  if (isChannel(collection)) {
8756
8763
  result = await collection.markAsReadRequest(options);
@@ -10095,6 +10102,33 @@ var Channel = class {
10095
10102
  ...data
10096
10103
  });
10097
10104
  }
10105
+ /**
10106
+ * markReadLocally - Resets this user's unread count locally, without any backend call. Intended for
10107
+ * channels that have read events disabled (e.g. livestreams) when the client is created with the
10108
+ * `isLocalUnreadCountEnabled` option. Dispatches a dedicated, client-only `message.read_locally` event
10109
+ * that runs through the same `_handleChannelEvent` read logic as a real `message.read` (minus the
10110
+ * delivery-report network sync), so the read-state update lives in one place. When offline support
10111
+ * is enabled, the offline DB persists the reset for read-events-disabled channels, so the local
10112
+ * count stays consistent across app restarts.
10113
+ *
10114
+ * @return {Event | undefined} The dispatched `message.read_locally` event, or `undefined` if there is no connected user.
10115
+ */
10116
+ markReadLocally() {
10117
+ const client = this.getClient();
10118
+ if (!client.userID) return;
10119
+ const event = {
10120
+ channel_id: this.id,
10121
+ channel_type: this.type,
10122
+ cid: this.cid,
10123
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
10124
+ last_read_message_id: this.lastMessage()?.id,
10125
+ team: this.data?.team,
10126
+ type: "message.read_locally",
10127
+ user: client.user
10128
+ };
10129
+ client.dispatchEvent(event);
10130
+ return event;
10131
+ }
10098
10132
  /**
10099
10133
  * clean - Cleans the channel state and fires stop typing if needed
10100
10134
  */
@@ -10250,7 +10284,7 @@ var Channel = class {
10250
10284
  if (message.user?.id === this.getClient().userID) return false;
10251
10285
  if (message.user?.id && this.getClient().userMuteStatus(message.user.id))
10252
10286
  return false;
10253
- if (Array.isArray(this.data?.own_capabilities) && !this.data?.own_capabilities.includes("read-events")) {
10287
+ if (!this.getClient().options.isLocalUnreadCountEnabled && !channelHasReadEvents(this)) {
10254
10288
  return false;
10255
10289
  }
10256
10290
  if (this.getClient()._muteStatus(this.cid).muted) return false;
@@ -10661,6 +10695,11 @@ var Channel = class {
10661
10695
  delete channelState.typing[event.user.id];
10662
10696
  }
10663
10697
  break;
10698
+ // `message.read_locally` is the client-only event dispatched by `markReadLocally()` when read
10699
+ // events are disabled (e.g. livestreams with `isLocalUnreadCountEnabled`). It reuses the exact
10700
+ // `message.read` state logic so the read-state update lives in one place — only the
10701
+ // delivery-report network sync below is skipped for it.
10702
+ case "message.read_locally":
10664
10703
  case "message.read":
10665
10704
  if (event.user?.id && event.created_at) {
10666
10705
  const previousReadState = channelState.read[event.user.id];
@@ -10681,7 +10720,9 @@ var Channel = class {
10681
10720
  const isOwnEvent = event.user?.id === client.user?.id;
10682
10721
  if (isOwnEvent) {
10683
10722
  channelState.unreadCount = 0;
10684
- client.syncDeliveredCandidates([this]);
10723
+ if (event.type === "message.read") {
10724
+ client.syncDeliveredCandidates([this]);
10725
+ }
10685
10726
  }
10686
10727
  }
10687
10728
  break;
@@ -14796,6 +14837,31 @@ var StreamChat = class _StreamChat {
14796
14837
  * @return {Promise<APIResponse>}
14797
14838
  */
14798
14839
  this.markAllRead = this.markChannelsRead;
14840
+ this.getUserAgent = () => {
14841
+ if (this.userAgent) {
14842
+ return this.userAgent;
14843
+ }
14844
+ if (!this.cachedUserAgent) {
14845
+ const version = "9.50.0";
14846
+ const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14847
+ const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14848
+ const { os, model: deviceModel } = this.deviceIdentifier ?? {};
14849
+ const head = sdkName ? `stream-chat-${sdkName}-v${sdkVersion}-llc-v${version}` : `stream-chat-js-v${version}-${this.node ? "node" : "browser"}`;
14850
+ this.cachedUserAgent = [
14851
+ head,
14852
+ // reports the host app, the device OS, the device model, and the picked
14853
+ // exports bundle, each only when a value is present
14854
+ ...Object.entries({
14855
+ app: appName,
14856
+ app_version: appVersion,
14857
+ os,
14858
+ device_model: deviceModel,
14859
+ client_bundle: "node-cjs"
14860
+ }).filter(([, value]) => value && value.length > 0).map(([key, value]) => `${key}=${value}`)
14861
+ ].join("|");
14862
+ }
14863
+ return this.cachedUserAgent;
14864
+ };
14799
14865
  /**
14800
14866
  * _isUsingServerAuth - Returns true if we're using server side auth
14801
14867
  */
@@ -14834,6 +14900,7 @@ var StreamChat = class _StreamChat {
14834
14900
  warmUp: false,
14835
14901
  recoverStateOnReconnect: true,
14836
14902
  disableCache: false,
14903
+ isLocalUnreadCountEnabled: false,
14837
14904
  wsUrlParams: new URLSearchParams({}),
14838
14905
  ...inputOptions
14839
14906
  };
@@ -15248,6 +15315,13 @@ var StreamChat = class _StreamChat {
15248
15315
  delete this.activeChannels[cid];
15249
15316
  });
15250
15317
  }
15318
+ if (event.type === "notification.removed_from_channel" && event.cid) {
15319
+ const { cid } = event;
15320
+ this.activeChannels[cid]?._disconnect();
15321
+ postListenerCallbacks.push(() => {
15322
+ delete this.activeChannels[cid];
15323
+ });
15324
+ }
15251
15325
  return postListenerCallbacks;
15252
15326
  }
15253
15327
  _muteStatus(cid) {
@@ -16739,31 +16813,6 @@ var StreamChat = class _StreamChat {
16739
16813
  partialThreadObject
16740
16814
  );
16741
16815
  }
16742
- getUserAgent() {
16743
- if (this.userAgent) {
16744
- return this.userAgent;
16745
- }
16746
- const version = "9.48.0";
16747
- const clientBundle = "node-cjs";
16748
- let userAgentString = "";
16749
- if (this.sdkIdentifier) {
16750
- userAgentString = `stream-chat-${this.sdkIdentifier.name}-v${this.sdkIdentifier.version}-llc-v${version}`;
16751
- } else {
16752
- userAgentString = `stream-chat-js-v${version}-${this.node ? "node" : "browser"}`;
16753
- }
16754
- const { os, model } = this.deviceIdentifier ?? {};
16755
- return [
16756
- // reports the device OS, if provided
16757
- ["os", os],
16758
- // reports the device model, if provided
16759
- ["device_model", model],
16760
- // reports which bundle is being picked from the exports
16761
- ["client_bundle", clientBundle]
16762
- ].reduce(
16763
- (withArguments, [key, value]) => value && value.length > 0 ? withArguments.concat(`|${key}=${value}`) : withArguments,
16764
- userAgentString
16765
- );
16766
- }
16767
16816
  /**
16768
16817
  * @deprecated use sdkIdentifier instead
16769
16818
  * @param userAgent
@@ -18205,6 +18254,7 @@ var EVENT_MAP = {
18205
18254
  "ai_indicator.stop": true,
18206
18255
  "ai_indicator.clear": true,
18207
18256
  // local events
18257
+ "message.read_locally": true,
18208
18258
  "channels.queried": true,
18209
18259
  "offline_reactions.queried": true,
18210
18260
  "connection.changed": true,
@@ -18588,7 +18638,8 @@ var AbstractOfflineDB = class {
18588
18638
  if (cid && client.user && client.user.id !== user?.id) {
18589
18639
  const userId = client.user.id;
18590
18640
  const channel = client.activeChannels[cid];
18591
- if (channel) {
18641
+ const tracksReadLocally = channelTracksReadLocally(channel);
18642
+ if (channel && (channelHasReadEvents(channel) || tracksReadLocally)) {
18592
18643
  const ownReads = channel.state.read[userId];
18593
18644
  const unreadCount = channel.countUnread();
18594
18645
  const upsertReadsQueries = await this.upsertReads({
@@ -18596,8 +18647,8 @@ var AbstractOfflineDB = class {
18596
18647
  execute: false,
18597
18648
  reads: [
18598
18649
  {
18599
- last_read: ownReads.last_read.toISOString(),
18600
- last_read_message_id: ownReads.last_read_message_id,
18650
+ last_read: (ownReads?.last_read ?? /* @__PURE__ */ new Date(0)).toISOString(),
18651
+ last_read_message_id: ownReads?.last_read_message_id,
18601
18652
  unread_messages: unreadCount,
18602
18653
  user: client.user
18603
18654
  }
@@ -18796,27 +18847,30 @@ var AbstractOfflineDB = class {
18796
18847
  truncated_at,
18797
18848
  execute: false
18798
18849
  });
18850
+ let finalQueries = [...truncateQueries];
18799
18851
  const userId = ownUser.id;
18800
18852
  const activeChannel = this.client.activeChannels[cid];
18801
- const ownReads = activeChannel.state.read[userId];
18802
- let unreadCount = 0;
18803
- if (truncated_at) {
18804
- const truncatedAt = new Date(truncated_at);
18805
- unreadCount = activeChannel.countUnread(truncatedAt);
18853
+ const tracksReadLocally = channelTracksReadLocally(activeChannel);
18854
+ if (activeChannel && (channelHasReadEvents(activeChannel) || tracksReadLocally)) {
18855
+ const ownReads = activeChannel.state.read[userId];
18856
+ let unreadCount = 0;
18857
+ if (truncated_at) {
18858
+ unreadCount = activeChannel.countUnread(new Date(truncated_at));
18859
+ }
18860
+ const upsertReadQueries = await this.upsertReads({
18861
+ cid,
18862
+ execute: false,
18863
+ reads: [
18864
+ {
18865
+ last_read: (ownReads?.last_read ?? /* @__PURE__ */ new Date(0)).toString(),
18866
+ last_read_message_id: ownReads?.last_read_message_id,
18867
+ unread_messages: unreadCount,
18868
+ user: ownUser
18869
+ }
18870
+ ]
18871
+ });
18872
+ finalQueries = [...finalQueries, ...upsertReadQueries];
18806
18873
  }
18807
- const upsertReadQueries = await this.upsertReads({
18808
- cid,
18809
- execute: false,
18810
- reads: [
18811
- {
18812
- last_read: ownReads.last_read.toString(),
18813
- last_read_message_id: ownReads.last_read_message_id,
18814
- unread_messages: unreadCount,
18815
- user: ownUser
18816
- }
18817
- ]
18818
- });
18819
- const finalQueries = [...truncateQueries, ...upsertReadQueries];
18820
18874
  if (execute) {
18821
18875
  await this.executeSqlBatch(finalQueries);
18822
18876
  }
@@ -18917,6 +18971,13 @@ var AbstractOfflineDB = class {
18917
18971
  if (type === "message.read" || type === "notification.mark_read") {
18918
18972
  return this.handleRead({ event, unreadMessages: 0, execute });
18919
18973
  }
18974
+ if (type === "message.read_locally") {
18975
+ const localChannel = event.cid ? this.client.activeChannels[event.cid] : void 0;
18976
+ if (channelTracksReadLocally(localChannel)) {
18977
+ return this.handleRead({ event, unreadMessages: 0, execute });
18978
+ }
18979
+ return [];
18980
+ }
18920
18981
  if (type === "notification.mark_unread") {
18921
18982
  return this.handleRead({ event, execute });
18922
18983
  }