stream-chat 9.6.0 → 9.7.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.
Files changed (41) hide show
  1. package/dist/cjs/index.browser.cjs +383 -65
  2. package/dist/cjs/index.browser.cjs.map +3 -3
  3. package/dist/cjs/index.node.cjs +387 -68
  4. package/dist/cjs/index.node.cjs.map +3 -3
  5. package/dist/esm/index.js +383 -65
  6. package/dist/esm/index.js.map +3 -3
  7. package/dist/types/channel.d.ts +36 -4
  8. package/dist/types/client.d.ts +38 -0
  9. package/dist/types/messageComposer/messageComposer.d.ts +4 -1
  10. package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +5 -5
  11. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +1 -2
  12. package/dist/types/messageComposer/middleware/textComposer/types.d.ts +2 -2
  13. package/dist/types/offline-support/offline_support_api.d.ts +39 -0
  14. package/dist/types/offline-support/types.d.ts +36 -2
  15. package/dist/types/search/BaseSearchSource.d.ts +37 -31
  16. package/dist/types/search/ChannelSearchSource.d.ts +1 -1
  17. package/dist/types/search/MessageSearchSource.d.ts +1 -1
  18. package/dist/types/search/UserSearchSource.d.ts +1 -1
  19. package/dist/types/search/index.d.ts +1 -0
  20. package/dist/types/search/types.d.ts +20 -0
  21. package/dist/types/types.d.ts +5 -1
  22. package/dist/types/utils/WithSubscriptions.d.ts +7 -2
  23. package/dist/types/utils.d.ts +11 -2
  24. package/package.json +1 -1
  25. package/src/channel.ts +85 -10
  26. package/src/client.ts +61 -3
  27. package/src/messageComposer/messageComposer.ts +136 -32
  28. package/src/messageComposer/middleware/textComposer/commands.ts +6 -7
  29. package/src/messageComposer/middleware/textComposer/mentions.ts +1 -2
  30. package/src/messageComposer/middleware/textComposer/types.ts +2 -2
  31. package/src/offline-support/offline_support_api.ts +79 -0
  32. package/src/offline-support/types.ts +41 -1
  33. package/src/search/BaseSearchSource.ts +123 -52
  34. package/src/search/ChannelSearchSource.ts +1 -1
  35. package/src/search/MessageSearchSource.ts +1 -1
  36. package/src/search/UserSearchSource.ts +1 -1
  37. package/src/search/index.ts +1 -0
  38. package/src/search/types.ts +20 -0
  39. package/src/types.ts +8 -1
  40. package/src/utils/WithSubscriptions.ts +16 -2
  41. package/src/utils.ts +31 -2
package/dist/esm/index.js CHANGED
@@ -2598,6 +2598,23 @@ function formatMessage(message) {
2598
2598
  quoted_message: toLocalMessageBase(message.quoted_message)
2599
2599
  };
2600
2600
  }
2601
+ function unformatMessage(message) {
2602
+ const toMessageResponseBase = (msg) => {
2603
+ if (!msg) return null;
2604
+ const newDateString = (/* @__PURE__ */ new Date()).toISOString();
2605
+ return {
2606
+ ...msg,
2607
+ created_at: message.created_at ? message.created_at.toISOString() : newDateString,
2608
+ deleted_at: message.deleted_at ? message.deleted_at.toISOString() : void 0,
2609
+ pinned_at: message.pinned_at ? message.pinned_at.toISOString() : void 0,
2610
+ updated_at: message.updated_at ? message.updated_at.toISOString() : newDateString
2611
+ };
2612
+ };
2613
+ return {
2614
+ ...toMessageResponseBase(message),
2615
+ quoted_message: toMessageResponseBase(message.quoted_message)
2616
+ };
2617
+ }
2601
2618
  var localMessageToNewMessagePayload = (localMessage) => {
2602
2619
  const {
2603
2620
  // Remove all timestamp fields and client-specific fields.
@@ -7266,11 +7283,8 @@ var DEFAULT_SEARCH_SOURCE_OPTIONS = {
7266
7283
  debounceMs: 300,
7267
7284
  pageSize: 10
7268
7285
  };
7269
- var BaseSearchSource = class {
7286
+ var BaseSearchSourceBase = class {
7270
7287
  constructor(options) {
7271
- this.setDebounceOptions = ({ debounceMs }) => {
7272
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
7273
- };
7274
7288
  this.activate = () => {
7275
7289
  if (this.isActive) return;
7276
7290
  this.state.partialNext({ isActive: true });
@@ -7284,11 +7298,9 @@ var BaseSearchSource = class {
7284
7298
  const searchString = newSearchString ?? this.searchQuery;
7285
7299
  return !!(this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery) && searchString);
7286
7300
  };
7287
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
7288
- const { debounceMs, pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
7301
+ const { pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
7289
7302
  this.pageSize = pageSize;
7290
7303
  this.state = new StateStore(this.initialState);
7291
- this.setDebounceOptions({ debounceMs });
7292
7304
  }
7293
7305
  get lastQueryError() {
7294
7306
  return this.state.getLatestValue().lastQueryError;
@@ -7348,8 +7360,7 @@ var BaseSearchSource = class {
7348
7360
  items: isFirstPage ? stateUpdate.items : [...this.items ?? [], ...stateUpdate.items || []]
7349
7361
  };
7350
7362
  }
7351
- async executeQuery(newSearchString) {
7352
- if (!this.canExecuteQuery(newSearchString)) return;
7363
+ prepareStateForQuery(newSearchString) {
7353
7364
  const hasNewSearchQuery = typeof newSearchString !== "undefined";
7354
7365
  const searchString = newSearchString ?? this.searchQuery;
7355
7366
  if (hasNewSearchQuery) {
@@ -7357,18 +7368,47 @@ var BaseSearchSource = class {
7357
7368
  } else {
7358
7369
  this.state.partialNext({ isLoading: true });
7359
7370
  }
7371
+ return { searchString, hasNewSearchQuery };
7372
+ }
7373
+ updatePaginationStateFromQuery(result) {
7374
+ const { items, next } = result;
7360
7375
  const stateUpdate = {};
7376
+ if (next || next === null) {
7377
+ stateUpdate.next = next;
7378
+ stateUpdate.hasNext = !!next;
7379
+ } else {
7380
+ stateUpdate.offset = (this.offset ?? 0) + items.length;
7381
+ stateUpdate.hasNext = items.length === this.pageSize;
7382
+ }
7383
+ return stateUpdate;
7384
+ }
7385
+ resetState() {
7386
+ this.state.next(this.initialState);
7387
+ }
7388
+ resetStateAndActivate() {
7389
+ this.resetState();
7390
+ this.activate();
7391
+ }
7392
+ };
7393
+ var BaseSearchSource = class extends BaseSearchSourceBase {
7394
+ constructor(options) {
7395
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
7396
+ super(options);
7397
+ this.setDebounceOptions = ({ debounceMs }) => {
7398
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
7399
+ };
7400
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
7401
+ this.setDebounceOptions({ debounceMs });
7402
+ }
7403
+ async executeQuery(newSearchString) {
7404
+ if (!this.canExecuteQuery(newSearchString)) return;
7405
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
7406
+ let stateUpdate = {};
7361
7407
  try {
7362
7408
  const results = await this.query(searchString);
7363
7409
  if (!results) return;
7364
- const { items, next } = results;
7365
- if (next || next === null) {
7366
- stateUpdate.next = next;
7367
- stateUpdate.hasNext = !!next;
7368
- } else {
7369
- stateUpdate.offset = (this.offset ?? 0) + items.length;
7370
- stateUpdate.hasNext = items.length === this.pageSize;
7371
- }
7410
+ const { items } = results;
7411
+ stateUpdate = this.updatePaginationStateFromQuery(results);
7372
7412
  stateUpdate.items = await this.filterQueryResults(items);
7373
7413
  } catch (e) {
7374
7414
  stateUpdate.lastQueryError = e;
@@ -7379,12 +7419,35 @@ var BaseSearchSource = class {
7379
7419
  cancelScheduledQuery() {
7380
7420
  this.searchDebounced.cancel();
7381
7421
  }
7382
- resetState() {
7383
- this.state.next(this.initialState);
7422
+ };
7423
+ var BaseSearchSourceSync = class extends BaseSearchSourceBase {
7424
+ constructor(options) {
7425
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
7426
+ super(options);
7427
+ this.setDebounceOptions = ({ debounceMs }) => {
7428
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
7429
+ };
7430
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
7431
+ this.setDebounceOptions({ debounceMs });
7384
7432
  }
7385
- resetStateAndActivate() {
7386
- this.resetState();
7387
- this.activate();
7433
+ executeQuery(newSearchString) {
7434
+ if (!this.canExecuteQuery(newSearchString)) return;
7435
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
7436
+ let stateUpdate = {};
7437
+ try {
7438
+ const results = this.query(searchString);
7439
+ if (!results) return;
7440
+ const { items } = results;
7441
+ stateUpdate = this.updatePaginationStateFromQuery(results);
7442
+ stateUpdate.items = this.filterQueryResults(items);
7443
+ } catch (e) {
7444
+ stateUpdate.lastQueryError = e;
7445
+ } finally {
7446
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
7447
+ }
7448
+ }
7449
+ cancelScheduledQuery() {
7450
+ this.searchDebounced.cancel();
7388
7451
  }
7389
7452
  };
7390
7453
 
@@ -7667,7 +7730,7 @@ var getTokenizedSuggestionDisplayName = ({
7667
7730
  });
7668
7731
 
7669
7732
  // src/messageComposer/middleware/textComposer/commands.ts
7670
- var CommandSearchSource = class extends BaseSearchSource {
7733
+ var CommandSearchSource = class extends BaseSearchSourceSync {
7671
7734
  constructor(channel, options) {
7672
7735
  super(options);
7673
7736
  this.type = "commands";
@@ -7711,10 +7774,10 @@ var CommandSearchSource = class extends BaseSearchSource {
7711
7774
  }
7712
7775
  return 0;
7713
7776
  });
7714
- return Promise.resolve({
7777
+ return {
7715
7778
  items: selectedCommands.map((c) => ({ ...c, id: c.name })),
7716
7779
  next: null
7717
- });
7780
+ };
7718
7781
  }
7719
7782
  filterQueryResults(items) {
7720
7783
  return items;
@@ -8322,6 +8385,7 @@ var TextComposer = class {
8322
8385
  var _WithSubscriptions = class _WithSubscriptions {
8323
8386
  constructor() {
8324
8387
  this.unsubscribeFunctions = /* @__PURE__ */ new Set();
8388
+ this.refCount = 0;
8325
8389
  }
8326
8390
  /**
8327
8391
  * Returns a boolean, provides information of whether `registerSubscriptions`
@@ -8333,6 +8397,12 @@ var _WithSubscriptions = class _WithSubscriptions {
8333
8397
  addUnsubscribeFunction(unsubscribeFunction) {
8334
8398
  this.unsubscribeFunctions.add(unsubscribeFunction);
8335
8399
  }
8400
+ /**
8401
+ * Increments `refCount` by one and returns new value.
8402
+ */
8403
+ incrementRefCount() {
8404
+ return ++this.refCount;
8405
+ }
8336
8406
  /**
8337
8407
  * If you re-declare `unregisterSubscriptions` method within your class
8338
8408
  * make sure to run the original too.
@@ -8349,8 +8419,13 @@ var _WithSubscriptions = class _WithSubscriptions {
8349
8419
  * ```
8350
8420
  */
8351
8421
  unregisterSubscriptions() {
8422
+ if (this.refCount > 1) {
8423
+ this.refCount--;
8424
+ return _WithSubscriptions.symbol;
8425
+ }
8352
8426
  this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
8353
8427
  this.unsubscribeFunctions.clear();
8428
+ this.refCount = 0;
8354
8429
  return _WithSubscriptions.symbol;
8355
8430
  }
8356
8431
  };
@@ -8836,7 +8911,6 @@ var initState5 = (composition) => {
8836
8911
  showReplyInChannel: false
8837
8912
  };
8838
8913
  };
8839
- var noop4 = () => void 0;
8840
8914
  var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8841
8915
  // todo: mediaRecorder: MediaRecorderController;
8842
8916
  constructor({
@@ -8861,22 +8935,48 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8861
8935
  this.editedMessage = message;
8862
8936
  }
8863
8937
  };
8938
+ this.initStateFromChannelResponse = (channelApiResponse) => {
8939
+ if (this.channel.cid !== channelApiResponse.channel.cid) {
8940
+ return;
8941
+ }
8942
+ if (channelApiResponse.draft) {
8943
+ this.initState({ composition: channelApiResponse.draft });
8944
+ } else if (this.state.getLatestValue().draftId) {
8945
+ this.clear();
8946
+ this.client.offlineDb?.executeQuerySafely(
8947
+ (db) => db.deleteDraft({
8948
+ cid: this.channel.cid,
8949
+ parent_id: void 0
8950
+ // makes sure that we don't delete thread drafts while upserting channels
8951
+ }),
8952
+ { method: "deleteDraft" }
8953
+ );
8954
+ }
8955
+ };
8864
8956
  this.initEditingAuditState = (composition) => initEditingAuditState(composition);
8957
+ this.registerDraftEventSubscriptions = () => {
8958
+ const unsubscribeDraftUpdated = this.subscribeDraftUpdated();
8959
+ const unsubscribeDraftDeleted = this.subscribeDraftDeleted();
8960
+ return () => {
8961
+ unsubscribeDraftUpdated();
8962
+ unsubscribeDraftDeleted();
8963
+ };
8964
+ };
8865
8965
  this.registerSubscriptions = () => {
8866
- if (this.hasSubscriptions) {
8867
- return noop4;
8868
- }
8869
- this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
8870
- this.addUnsubscribeFunction(this.subscribeMessageUpdated());
8871
- this.addUnsubscribeFunction(this.subscribeMessageDeleted());
8872
- this.addUnsubscribeFunction(this.subscribeTextComposerStateChanged());
8873
- this.addUnsubscribeFunction(this.subscribeAttachmentManagerStateChanged());
8874
- this.addUnsubscribeFunction(this.subscribeLinkPreviewsManagerStateChanged());
8875
- this.addUnsubscribeFunction(this.subscribePollComposerStateChanged());
8876
- this.addUnsubscribeFunction(this.subscribeCustomDataManagerStateChanged());
8877
- this.addUnsubscribeFunction(this.subscribeMessageComposerStateChanged());
8878
- this.addUnsubscribeFunction(this.subscribeMessageComposerConfigStateChanged());
8879
- return this.unregisterSubscriptions.bind(this);
8966
+ if (!this.hasSubscriptions) {
8967
+ this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
8968
+ this.addUnsubscribeFunction(this.subscribeMessageUpdated());
8969
+ this.addUnsubscribeFunction(this.subscribeMessageDeleted());
8970
+ this.addUnsubscribeFunction(this.subscribeTextComposerStateChanged());
8971
+ this.addUnsubscribeFunction(this.subscribeAttachmentManagerStateChanged());
8972
+ this.addUnsubscribeFunction(this.subscribeLinkPreviewsManagerStateChanged());
8973
+ this.addUnsubscribeFunction(this.subscribePollComposerStateChanged());
8974
+ this.addUnsubscribeFunction(this.subscribeCustomDataManagerStateChanged());
8975
+ this.addUnsubscribeFunction(this.subscribeMessageComposerStateChanged());
8976
+ this.addUnsubscribeFunction(this.subscribeMessageComposerConfigStateChanged());
8977
+ }
8978
+ this.incrementRefCount();
8979
+ return () => this.unregisterSubscriptions();
8880
8980
  };
8881
8981
  this.subscribeMessageUpdated = () => {
8882
8982
  const eventTypes = [
@@ -8926,13 +9026,13 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8926
9026
  }).unsubscribe;
8927
9027
  this.subscribeDraftUpdated = () => this.client.on("draft.updated", (event) => {
8928
9028
  const draft = event.draft;
8929
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid)
9029
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid)
8930
9030
  return;
8931
9031
  this.initState({ composition: draft });
8932
9032
  }).unsubscribe;
8933
9033
  this.subscribeDraftDeleted = () => this.client.on("draft.deleted", (event) => {
8934
9034
  const draft = event.draft;
8935
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid) {
9035
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid) {
8936
9036
  return;
8937
9037
  }
8938
9038
  this.logDraftUpdateTimestamp();
@@ -8996,7 +9096,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8996
9096
  }
8997
9097
  });
8998
9098
  this.subscribeMessageComposerConfigStateChanged = () => {
8999
- let draftUnsubscribeFunctions;
9099
+ let draftUnsubscribeFunction;
9000
9100
  const unsubscribe = this.configState.subscribeWithSelector(
9001
9101
  (currentValue) => ({
9002
9102
  textDefaultValue: currentValue.text.defaultValue,
@@ -9009,19 +9109,16 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
9009
9109
  selection: { start: 0, end: 0 }
9010
9110
  });
9011
9111
  }
9012
- if (draftsEnabled && !draftUnsubscribeFunctions) {
9013
- draftUnsubscribeFunctions = [
9014
- this.subscribeDraftUpdated(),
9015
- this.subscribeDraftDeleted()
9016
- ];
9017
- } else if (!draftsEnabled && draftUnsubscribeFunctions) {
9018
- draftUnsubscribeFunctions.forEach((fn) => fn());
9019
- draftUnsubscribeFunctions = null;
9112
+ if (draftsEnabled && !draftUnsubscribeFunction) {
9113
+ draftUnsubscribeFunction = this.registerDraftEventSubscriptions();
9114
+ } else if (!draftsEnabled && draftUnsubscribeFunction) {
9115
+ draftUnsubscribeFunction();
9116
+ draftUnsubscribeFunction = null;
9020
9117
  }
9021
9118
  }
9022
9119
  );
9023
9120
  return () => {
9024
- draftUnsubscribeFunctions?.forEach((unsubscribe2) => unsubscribe2());
9121
+ draftUnsubscribeFunction?.();
9025
9122
  unsubscribe();
9026
9123
  };
9027
9124
  };
@@ -9091,14 +9188,78 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
9091
9188
  if (!composition) return;
9092
9189
  const { draft } = composition;
9093
9190
  this.state.partialNext({ draftId: draft.id });
9191
+ if (this.client.offlineDb) {
9192
+ try {
9193
+ const optimisticDraftResponse = {
9194
+ channel_cid: this.channel.cid,
9195
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
9196
+ message: draft,
9197
+ parent_id: draft.parent_id,
9198
+ quoted_message: this.quotedMessage ? unformatMessage(this.quotedMessage) : void 0
9199
+ };
9200
+ await this.client.offlineDb.upsertDraft({ draft: optimisticDraftResponse });
9201
+ } catch (error) {
9202
+ this.client.logger("error", `offlineDb:upsertDraft`, {
9203
+ tags: ["channel", "offlineDb"],
9204
+ error
9205
+ });
9206
+ }
9207
+ }
9094
9208
  this.logDraftUpdateTimestamp();
9095
9209
  await this.channel.createDraft(draft);
9096
9210
  };
9097
9211
  this.deleteDraft = async () => {
9098
9212
  if (this.editedMessage || !this.config.drafts.enabled || !this.draftId) return;
9099
9213
  this.state.partialNext({ draftId: null });
9214
+ const parentId = this.threadId ?? void 0;
9215
+ if (this.client.offlineDb) {
9216
+ try {
9217
+ await this.client.offlineDb.deleteDraft({
9218
+ cid: this.channel.cid,
9219
+ parent_id: parentId
9220
+ });
9221
+ } catch (error) {
9222
+ this.client.logger("error", `offlineDb:deleteDraft`, {
9223
+ tags: ["channel", "offlineDb"],
9224
+ error
9225
+ });
9226
+ }
9227
+ }
9100
9228
  this.logDraftUpdateTimestamp();
9101
- await this.channel.deleteDraft({ parent_id: this.threadId ?? void 0 });
9229
+ await this.channel.deleteDraft({ parent_id: parentId });
9230
+ };
9231
+ this.getDraft = async () => {
9232
+ if (this.editedMessage || !this.config.drafts.enabled || !this.client.userID) return;
9233
+ const draftFromOfflineDB = await this.client.offlineDb?.getDraft({
9234
+ cid: this.channel.cid,
9235
+ userId: this.client.userID,
9236
+ parent_id: this.threadId ?? void 0
9237
+ });
9238
+ if (draftFromOfflineDB) {
9239
+ this.initState({ composition: draftFromOfflineDB });
9240
+ }
9241
+ try {
9242
+ const response = await this.channel.getDraft({
9243
+ parent_id: this.threadId ?? void 0
9244
+ });
9245
+ const { draft } = response;
9246
+ if (!draft) return;
9247
+ this.client.offlineDb?.executeQuerySafely(
9248
+ (db) => db.upsertDraft({
9249
+ draft
9250
+ }),
9251
+ { method: "upsertDraft" }
9252
+ );
9253
+ this.initState({ composition: draft });
9254
+ } catch (error) {
9255
+ this.client.notifications.add({
9256
+ message: "Failed to get the draft",
9257
+ origin: {
9258
+ emitter: "MessageComposer",
9259
+ context: { composer: this }
9260
+ }
9261
+ });
9262
+ }
9102
9263
  };
9103
9264
  this.createPoll = async () => {
9104
9265
  const composition = await this.pollComposer.compose();
@@ -9227,6 +9388,9 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
9227
9388
  return this.state.getLatestValue().showReplyInChannel;
9228
9389
  }
9229
9390
  get hasSendableData() {
9391
+ if (this.client.offlineDb) {
9392
+ return !this.compositionIsEmpty;
9393
+ }
9230
9394
  return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId);
9231
9395
  }
9232
9396
  get compositionIsEmpty() {
@@ -9542,6 +9706,17 @@ var Channel = class {
9542
9706
  updates
9543
9707
  );
9544
9708
  }
9709
+ /**
9710
+ * sendReaction - Sends a reaction to a message. If offline support is enabled, it will make sure
9711
+ * that sending the reaction is queued up if it fails due to bad internet conditions and executed
9712
+ * later.
9713
+ *
9714
+ * @param {string} messageID the message id
9715
+ * @param {Reaction} reaction the reaction object for instance {type: 'love'}
9716
+ * @param {{ enforce_unique?: boolean, skip_push?: boolean }} [options] Option object, {enforce_unique: true, skip_push: true} to override any existing reaction or skip sending push notifications
9717
+ *
9718
+ * @return {Promise<ReactionAPIResponse>} The Server Response
9719
+ */
9545
9720
  async sendReaction(messageID, reaction, options) {
9546
9721
  if (!messageID) {
9547
9722
  throw Error(`Message id is missing`);
@@ -10394,9 +10569,7 @@ var Channel = class {
10394
10569
  };
10395
10570
  this.getClient().polls.hydratePollCache(state.messages, true);
10396
10571
  this.getClient().reminders.hydrateState(state.messages);
10397
- if (state.draft) {
10398
- this.messageComposer.initState({ composition: state.draft });
10399
- }
10572
+ this.messageComposer.initStateFromChannelResponse(state);
10400
10573
  const areCapabilitiesChanged = [...state.channel.own_capabilities || []].sort().join() !== [
10401
10574
  ...this.data && Array.isArray(this.data?.own_capabilities) ? this.data.own_capabilities : []
10402
10575
  ].sort().join();
@@ -10523,13 +10696,11 @@ var Channel = class {
10523
10696
  /**
10524
10697
  * createDraft - Creates or updates a draft message in a channel
10525
10698
  *
10526
- * @param {string} channelType The channel type
10527
- * @param {string} channelID The channel ID
10528
10699
  * @param {DraftMessagePayload} message The draft message to create or update
10529
10700
  *
10530
10701
  * @return {Promise<CreateDraftResponse>} Response containing the created draft
10531
10702
  */
10532
- async createDraft(message) {
10703
+ async _createDraft(message) {
10533
10704
  return await this.getClient().post(
10534
10705
  this._channelURL() + "/draft",
10535
10706
  {
@@ -10538,18 +10709,82 @@ var Channel = class {
10538
10709
  );
10539
10710
  }
10540
10711
  /**
10541
- * deleteDraft - Deletes a draft message from a channel
10712
+ * createDraft - Creates or updates a draft message in a channel. If offline support is
10713
+ * enabled, it will make sure that creating the draft is queued up if it fails due to
10714
+ * bad internet conditions and executed later.
10715
+ *
10716
+ * @param {DraftMessagePayload} message The draft message to create or update
10717
+ *
10718
+ * @return {Promise<CreateDraftResponse>} Response containing the created draft
10719
+ */
10720
+ async createDraft(message) {
10721
+ try {
10722
+ const offlineDb = this.getClient().offlineDb;
10723
+ if (offlineDb) {
10724
+ return await offlineDb.queueTask({
10725
+ task: {
10726
+ channelId: this.id,
10727
+ channelType: this.type,
10728
+ threadId: message.parent_id,
10729
+ payload: [message],
10730
+ type: "create-draft"
10731
+ }
10732
+ });
10733
+ }
10734
+ } catch (error) {
10735
+ this._client.logger("error", `offlineDb:create-draft`, {
10736
+ tags: ["channel", "offlineDb"],
10737
+ error
10738
+ });
10739
+ }
10740
+ return this._createDraft(message);
10741
+ }
10742
+ /**
10743
+ * deleteDraft - Deletes a draft message from a channel or a thread.
10542
10744
  *
10543
10745
  * @param {Object} options
10544
10746
  * @param {string} options.parent_id Optional parent message ID for drafts in threads
10545
10747
  *
10546
10748
  * @return {Promise<APIResponse>} API response
10547
10749
  */
10548
- async deleteDraft({ parent_id } = {}) {
10750
+ async _deleteDraft({ parent_id } = {}) {
10549
10751
  return await this.getClient().delete(this._channelURL() + "/draft", {
10550
10752
  parent_id
10551
10753
  });
10552
10754
  }
10755
+ /**
10756
+ * deleteDraft - Deletes a draft message from a channel or a thread. If offline support is
10757
+ * enabled, it will make sure that deleting the draft is queued up if it fails due to
10758
+ * bad internet conditions and executed later.
10759
+ *
10760
+ * @param {Object} options
10761
+ * @param {string} options.parent_id Optional parent message ID for drafts in threads
10762
+ *
10763
+ * @return {Promise<APIResponse>} API response
10764
+ */
10765
+ async deleteDraft(options = {}) {
10766
+ const { parent_id } = options;
10767
+ try {
10768
+ const offlineDb = this.getClient().offlineDb;
10769
+ if (offlineDb) {
10770
+ return await offlineDb.queueTask({
10771
+ task: {
10772
+ channelId: this.id,
10773
+ channelType: this.type,
10774
+ threadId: parent_id,
10775
+ payload: [options],
10776
+ type: "delete-draft"
10777
+ }
10778
+ });
10779
+ }
10780
+ } catch (error) {
10781
+ this._client.logger("error", `offlineDb:delete-draft`, {
10782
+ tags: ["channel", "offlineDb"],
10783
+ error
10784
+ });
10785
+ }
10786
+ return this._deleteDraft(options);
10787
+ }
10553
10788
  /**
10554
10789
  * getDraft - Retrieves a draft message from a channel
10555
10790
  *
@@ -15199,9 +15434,7 @@ var StreamChat = class _StreamChat {
15199
15434
  this.polls.hydratePollCache(channelState.messages, true);
15200
15435
  this.reminders.hydrateState(channelState.messages);
15201
15436
  }
15202
- if (channelState.draft) {
15203
- c.messageComposer.initState({ composition: channelState.draft });
15204
- }
15437
+ c.messageComposer.initStateFromChannelResponse(channelState);
15205
15438
  channels.push(c);
15206
15439
  }
15207
15440
  return channels;
@@ -16118,7 +16351,7 @@ var StreamChat = class _StreamChat {
16118
16351
  if (this.userAgent) {
16119
16352
  return this.userAgent;
16120
16353
  }
16121
- const version = "9.6.0";
16354
+ const version = "9.7.0";
16122
16355
  const clientBundle = "browser-esm";
16123
16356
  let userAgentString = "";
16124
16357
  if (this.sdkIdentifier) {
@@ -17207,6 +17440,52 @@ var StreamChat = class _StreamChat {
17207
17440
  ...rest
17208
17441
  });
17209
17442
  }
17443
+ /**
17444
+ * uploadFile - Uploads a file to the configured storage (defaults to Stream CDN)
17445
+ *
17446
+ * @param {string|NodeJS.ReadableStream|Buffer|File} uri The file to upload
17447
+ * @param {string} [name] The name of the file
17448
+ * @param {string} [contentType] The content type of the file
17449
+ * @param {UserResponse} [user] Optional user information
17450
+ *
17451
+ * @return {Promise<SendFileAPIResponse>} Response containing the file URL
17452
+ */
17453
+ uploadFile(uri, name, contentType, user) {
17454
+ return this.sendFile(`${this.baseURL}/uploads/file`, uri, name, contentType, user);
17455
+ }
17456
+ /**
17457
+ * uploadImage - Uploads an image to the configured storage (defaults to Stream CDN)
17458
+ *
17459
+ * @param {string|NodeJS.ReadableStream|File} uri The image to upload
17460
+ * @param {string} [name] The name of the image
17461
+ * @param {string} [contentType] The content type of the image
17462
+ * @param {UserResponse} [user] Optional user information
17463
+ *
17464
+ * @return {Promise<SendFileAPIResponse>} Response containing the image URL
17465
+ */
17466
+ uploadImage(uri, name, contentType, user) {
17467
+ return this.sendFile(`${this.baseURL}/uploads/image`, uri, name, contentType, user);
17468
+ }
17469
+ /**
17470
+ * deleteFile - Deletes a file from the configured storage
17471
+ *
17472
+ * @param {string} url The URL of the file to delete
17473
+ *
17474
+ * @return {Promise<APIResponse>} The server response
17475
+ */
17476
+ deleteFile(url) {
17477
+ return this.delete(`${this.baseURL}/uploads/file`, { url });
17478
+ }
17479
+ /**
17480
+ * deleteImage - Deletes an image from the configured storage
17481
+ *
17482
+ * @param {string} url The URL of the image to delete
17483
+ *
17484
+ * @return {Promise<APIResponse>} The server response
17485
+ */
17486
+ deleteImage(url) {
17487
+ return this.delete(`${this.baseURL}/uploads/image`, { url });
17488
+ }
17210
17489
  };
17211
17490
 
17212
17491
  // src/events.ts
@@ -17915,6 +18194,35 @@ var AbstractOfflineDB = class {
17915
18194
  (executeOverride) => reactionMethod({ message, reaction, execute: executeOverride })
17916
18195
  );
17917
18196
  };
18197
+ /**
18198
+ * A utility handler for all draft events:
18199
+ * - draft.updated -> updateDraft
18200
+ * - draft.deleted -> deleteDraft
18201
+ * @param event - the WS event we are trying to process
18202
+ * @param execute - whether to immediately execute the operation.
18203
+ */
18204
+ this.handleDraftEvent = async ({
18205
+ event,
18206
+ execute = true
18207
+ }) => {
18208
+ const { cid, draft, type } = event;
18209
+ if (!draft) return [];
18210
+ if (type === "draft.updated") {
18211
+ return await this.upsertDraft({
18212
+ draft,
18213
+ execute
18214
+ });
18215
+ }
18216
+ if (type === "draft.deleted") {
18217
+ if (!cid) return [];
18218
+ return await this.deleteDraft({
18219
+ cid,
18220
+ parent_id: draft.parent_id,
18221
+ execute
18222
+ });
18223
+ }
18224
+ return [];
18225
+ };
17918
18226
  /**
17919
18227
  * A generic event handler that decides which DB API to invoke based on
17920
18228
  * event.type for all events we are currently handling. It is used to both
@@ -17951,6 +18259,9 @@ var AbstractOfflineDB = class {
17951
18259
  if (type === "channel.hidden" || type === "channel.visible") {
17952
18260
  return await this.handleChannelVisibilityEvent({ event, execute });
17953
18261
  }
18262
+ if (type === "draft.updated" || type === "draft.deleted") {
18263
+ return await this.handleDraftEvent({ event, execute });
18264
+ }
17954
18265
  if ((type === "channel.updated" || type === "notification.message_new" || type === "notification.added_to_channel") && channel) {
17955
18266
  return await this.upsertChannelData({ channel, execute });
17956
18267
  }
@@ -18025,6 +18336,12 @@ var AbstractOfflineDB = class {
18025
18336
  if (task.type === "delete-reaction") {
18026
18337
  return await channel._deleteReaction(...task.payload);
18027
18338
  }
18339
+ if (task.type === "create-draft") {
18340
+ return await channel._createDraft(...task.payload);
18341
+ }
18342
+ if (task.type === "delete-draft") {
18343
+ return await channel._deleteDraft(...task.payload);
18344
+ }
18028
18345
  if (task.type === "send-message") {
18029
18346
  const newMessageResponse = await channel._sendMessage(...task.payload);
18030
18347
  const newMessage = newMessageResponse?.message;
@@ -18152,6 +18469,7 @@ export {
18152
18469
  AttachmentManager,
18153
18470
  BasePaginator,
18154
18471
  BaseSearchSource,
18472
+ BaseSearchSourceSync,
18155
18473
  BuiltinPermissions,
18156
18474
  BuiltinRoles,
18157
18475
  Campaign,