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
@@ -10233,7 +10233,7 @@ var require_follow_redirects = __commonJS({
10233
10233
  "ERR_STREAM_WRITE_AFTER_END",
10234
10234
  "write after end"
10235
10235
  );
10236
- var destroy = Writable.prototype.destroy || noop4;
10236
+ var destroy = Writable.prototype.destroy || noop3;
10237
10237
  function RedirectableRequest(options, responseCallback) {
10238
10238
  Writable.call(this);
10239
10239
  this._sanitizeOptions(options);
@@ -10585,7 +10585,7 @@ var require_follow_redirects = __commonJS({
10585
10585
  });
10586
10586
  return exports3;
10587
10587
  }
10588
- function noop4() {
10588
+ function noop3() {
10589
10589
  }
10590
10590
  function urlToOptions(urlObject) {
10591
10591
  var options = {
@@ -10631,7 +10631,7 @@ var require_follow_redirects = __commonJS({
10631
10631
  for (var event of events) {
10632
10632
  request.removeListener(event, eventHandlers[event]);
10633
10633
  }
10634
- request.on("error", noop4);
10634
+ request.on("error", noop3);
10635
10635
  request.destroy(error);
10636
10636
  }
10637
10637
  function isSubdomain(subdomain, domain) {
@@ -10672,6 +10672,7 @@ __export(index_exports, {
10672
10672
  AttachmentManager: () => AttachmentManager,
10673
10673
  BasePaginator: () => BasePaginator,
10674
10674
  BaseSearchSource: () => BaseSearchSource,
10675
+ BaseSearchSourceSync: () => BaseSearchSourceSync,
10675
10676
  BuiltinPermissions: () => BuiltinPermissions,
10676
10677
  BuiltinRoles: () => BuiltinRoles,
10677
10678
  Campaign: () => Campaign,
@@ -14100,6 +14101,23 @@ function formatMessage(message) {
14100
14101
  quoted_message: toLocalMessageBase(message.quoted_message)
14101
14102
  };
14102
14103
  }
14104
+ function unformatMessage(message) {
14105
+ const toMessageResponseBase = (msg) => {
14106
+ if (!msg) return null;
14107
+ const newDateString = (/* @__PURE__ */ new Date()).toISOString();
14108
+ return {
14109
+ ...msg,
14110
+ created_at: message.created_at ? message.created_at.toISOString() : newDateString,
14111
+ deleted_at: message.deleted_at ? message.deleted_at.toISOString() : void 0,
14112
+ pinned_at: message.pinned_at ? message.pinned_at.toISOString() : void 0,
14113
+ updated_at: message.updated_at ? message.updated_at.toISOString() : newDateString
14114
+ };
14115
+ };
14116
+ return {
14117
+ ...toMessageResponseBase(message),
14118
+ quoted_message: toMessageResponseBase(message.quoted_message)
14119
+ };
14120
+ }
14103
14121
  var localMessageToNewMessagePayload = (localMessage) => {
14104
14122
  const {
14105
14123
  // Remove all timestamp fields and client-specific fields.
@@ -17639,11 +17657,8 @@ var DEFAULT_SEARCH_SOURCE_OPTIONS = {
17639
17657
  debounceMs: 300,
17640
17658
  pageSize: 10
17641
17659
  };
17642
- var BaseSearchSource = class {
17660
+ var BaseSearchSourceBase = class {
17643
17661
  constructor(options) {
17644
- this.setDebounceOptions = ({ debounceMs }) => {
17645
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
17646
- };
17647
17662
  this.activate = () => {
17648
17663
  if (this.isActive) return;
17649
17664
  this.state.partialNext({ isActive: true });
@@ -17657,11 +17672,9 @@ var BaseSearchSource = class {
17657
17672
  const searchString = newSearchString ?? this.searchQuery;
17658
17673
  return !!(this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery) && searchString);
17659
17674
  };
17660
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
17661
- const { debounceMs, pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
17675
+ const { pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
17662
17676
  this.pageSize = pageSize;
17663
17677
  this.state = new StateStore(this.initialState);
17664
- this.setDebounceOptions({ debounceMs });
17665
17678
  }
17666
17679
  get lastQueryError() {
17667
17680
  return this.state.getLatestValue().lastQueryError;
@@ -17721,8 +17734,7 @@ var BaseSearchSource = class {
17721
17734
  items: isFirstPage ? stateUpdate.items : [...this.items ?? [], ...stateUpdate.items || []]
17722
17735
  };
17723
17736
  }
17724
- async executeQuery(newSearchString) {
17725
- if (!this.canExecuteQuery(newSearchString)) return;
17737
+ prepareStateForQuery(newSearchString) {
17726
17738
  const hasNewSearchQuery = typeof newSearchString !== "undefined";
17727
17739
  const searchString = newSearchString ?? this.searchQuery;
17728
17740
  if (hasNewSearchQuery) {
@@ -17730,18 +17742,47 @@ var BaseSearchSource = class {
17730
17742
  } else {
17731
17743
  this.state.partialNext({ isLoading: true });
17732
17744
  }
17745
+ return { searchString, hasNewSearchQuery };
17746
+ }
17747
+ updatePaginationStateFromQuery(result) {
17748
+ const { items, next } = result;
17733
17749
  const stateUpdate = {};
17750
+ if (next || next === null) {
17751
+ stateUpdate.next = next;
17752
+ stateUpdate.hasNext = !!next;
17753
+ } else {
17754
+ stateUpdate.offset = (this.offset ?? 0) + items.length;
17755
+ stateUpdate.hasNext = items.length === this.pageSize;
17756
+ }
17757
+ return stateUpdate;
17758
+ }
17759
+ resetState() {
17760
+ this.state.next(this.initialState);
17761
+ }
17762
+ resetStateAndActivate() {
17763
+ this.resetState();
17764
+ this.activate();
17765
+ }
17766
+ };
17767
+ var BaseSearchSource = class extends BaseSearchSourceBase {
17768
+ constructor(options) {
17769
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
17770
+ super(options);
17771
+ this.setDebounceOptions = ({ debounceMs }) => {
17772
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
17773
+ };
17774
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
17775
+ this.setDebounceOptions({ debounceMs });
17776
+ }
17777
+ async executeQuery(newSearchString) {
17778
+ if (!this.canExecuteQuery(newSearchString)) return;
17779
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
17780
+ let stateUpdate = {};
17734
17781
  try {
17735
17782
  const results = await this.query(searchString);
17736
17783
  if (!results) return;
17737
- const { items, next } = results;
17738
- if (next || next === null) {
17739
- stateUpdate.next = next;
17740
- stateUpdate.hasNext = !!next;
17741
- } else {
17742
- stateUpdate.offset = (this.offset ?? 0) + items.length;
17743
- stateUpdate.hasNext = items.length === this.pageSize;
17744
- }
17784
+ const { items } = results;
17785
+ stateUpdate = this.updatePaginationStateFromQuery(results);
17745
17786
  stateUpdate.items = await this.filterQueryResults(items);
17746
17787
  } catch (e) {
17747
17788
  stateUpdate.lastQueryError = e;
@@ -17752,12 +17793,35 @@ var BaseSearchSource = class {
17752
17793
  cancelScheduledQuery() {
17753
17794
  this.searchDebounced.cancel();
17754
17795
  }
17755
- resetState() {
17756
- this.state.next(this.initialState);
17796
+ };
17797
+ var BaseSearchSourceSync = class extends BaseSearchSourceBase {
17798
+ constructor(options) {
17799
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
17800
+ super(options);
17801
+ this.setDebounceOptions = ({ debounceMs }) => {
17802
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
17803
+ };
17804
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
17805
+ this.setDebounceOptions({ debounceMs });
17757
17806
  }
17758
- resetStateAndActivate() {
17759
- this.resetState();
17760
- this.activate();
17807
+ executeQuery(newSearchString) {
17808
+ if (!this.canExecuteQuery(newSearchString)) return;
17809
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
17810
+ let stateUpdate = {};
17811
+ try {
17812
+ const results = this.query(searchString);
17813
+ if (!results) return;
17814
+ const { items } = results;
17815
+ stateUpdate = this.updatePaginationStateFromQuery(results);
17816
+ stateUpdate.items = this.filterQueryResults(items);
17817
+ } catch (e) {
17818
+ stateUpdate.lastQueryError = e;
17819
+ } finally {
17820
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
17821
+ }
17822
+ }
17823
+ cancelScheduledQuery() {
17824
+ this.searchDebounced.cancel();
17761
17825
  }
17762
17826
  };
17763
17827
 
@@ -18040,7 +18104,7 @@ var getTokenizedSuggestionDisplayName = ({
18040
18104
  });
18041
18105
 
18042
18106
  // src/messageComposer/middleware/textComposer/commands.ts
18043
- var CommandSearchSource = class extends BaseSearchSource {
18107
+ var CommandSearchSource = class extends BaseSearchSourceSync {
18044
18108
  constructor(channel, options) {
18045
18109
  super(options);
18046
18110
  this.type = "commands";
@@ -18084,10 +18148,10 @@ var CommandSearchSource = class extends BaseSearchSource {
18084
18148
  }
18085
18149
  return 0;
18086
18150
  });
18087
- return Promise.resolve({
18151
+ return {
18088
18152
  items: selectedCommands.map((c) => ({ ...c, id: c.name })),
18089
18153
  next: null
18090
- });
18154
+ };
18091
18155
  }
18092
18156
  filterQueryResults(items) {
18093
18157
  return items;
@@ -18695,6 +18759,7 @@ var TextComposer = class {
18695
18759
  var _WithSubscriptions = class _WithSubscriptions {
18696
18760
  constructor() {
18697
18761
  this.unsubscribeFunctions = /* @__PURE__ */ new Set();
18762
+ this.refCount = 0;
18698
18763
  }
18699
18764
  /**
18700
18765
  * Returns a boolean, provides information of whether `registerSubscriptions`
@@ -18706,6 +18771,12 @@ var _WithSubscriptions = class _WithSubscriptions {
18706
18771
  addUnsubscribeFunction(unsubscribeFunction) {
18707
18772
  this.unsubscribeFunctions.add(unsubscribeFunction);
18708
18773
  }
18774
+ /**
18775
+ * Increments `refCount` by one and returns new value.
18776
+ */
18777
+ incrementRefCount() {
18778
+ return ++this.refCount;
18779
+ }
18709
18780
  /**
18710
18781
  * If you re-declare `unregisterSubscriptions` method within your class
18711
18782
  * make sure to run the original too.
@@ -18722,8 +18793,13 @@ var _WithSubscriptions = class _WithSubscriptions {
18722
18793
  * ```
18723
18794
  */
18724
18795
  unregisterSubscriptions() {
18796
+ if (this.refCount > 1) {
18797
+ this.refCount--;
18798
+ return _WithSubscriptions.symbol;
18799
+ }
18725
18800
  this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
18726
18801
  this.unsubscribeFunctions.clear();
18802
+ this.refCount = 0;
18727
18803
  return _WithSubscriptions.symbol;
18728
18804
  }
18729
18805
  };
@@ -19209,7 +19285,6 @@ var initState5 = (composition) => {
19209
19285
  showReplyInChannel: false
19210
19286
  };
19211
19287
  };
19212
- var noop3 = () => void 0;
19213
19288
  var _MessageComposer = class _MessageComposer extends WithSubscriptions {
19214
19289
  // todo: mediaRecorder: MediaRecorderController;
19215
19290
  constructor({
@@ -19234,22 +19309,48 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
19234
19309
  this.editedMessage = message;
19235
19310
  }
19236
19311
  };
19312
+ this.initStateFromChannelResponse = (channelApiResponse) => {
19313
+ if (this.channel.cid !== channelApiResponse.channel.cid) {
19314
+ return;
19315
+ }
19316
+ if (channelApiResponse.draft) {
19317
+ this.initState({ composition: channelApiResponse.draft });
19318
+ } else if (this.state.getLatestValue().draftId) {
19319
+ this.clear();
19320
+ this.client.offlineDb?.executeQuerySafely(
19321
+ (db) => db.deleteDraft({
19322
+ cid: this.channel.cid,
19323
+ parent_id: void 0
19324
+ // makes sure that we don't delete thread drafts while upserting channels
19325
+ }),
19326
+ { method: "deleteDraft" }
19327
+ );
19328
+ }
19329
+ };
19237
19330
  this.initEditingAuditState = (composition) => initEditingAuditState(composition);
19331
+ this.registerDraftEventSubscriptions = () => {
19332
+ const unsubscribeDraftUpdated = this.subscribeDraftUpdated();
19333
+ const unsubscribeDraftDeleted = this.subscribeDraftDeleted();
19334
+ return () => {
19335
+ unsubscribeDraftUpdated();
19336
+ unsubscribeDraftDeleted();
19337
+ };
19338
+ };
19238
19339
  this.registerSubscriptions = () => {
19239
- if (this.hasSubscriptions) {
19240
- return noop3;
19241
- }
19242
- this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
19243
- this.addUnsubscribeFunction(this.subscribeMessageUpdated());
19244
- this.addUnsubscribeFunction(this.subscribeMessageDeleted());
19245
- this.addUnsubscribeFunction(this.subscribeTextComposerStateChanged());
19246
- this.addUnsubscribeFunction(this.subscribeAttachmentManagerStateChanged());
19247
- this.addUnsubscribeFunction(this.subscribeLinkPreviewsManagerStateChanged());
19248
- this.addUnsubscribeFunction(this.subscribePollComposerStateChanged());
19249
- this.addUnsubscribeFunction(this.subscribeCustomDataManagerStateChanged());
19250
- this.addUnsubscribeFunction(this.subscribeMessageComposerStateChanged());
19251
- this.addUnsubscribeFunction(this.subscribeMessageComposerConfigStateChanged());
19252
- return this.unregisterSubscriptions.bind(this);
19340
+ if (!this.hasSubscriptions) {
19341
+ this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
19342
+ this.addUnsubscribeFunction(this.subscribeMessageUpdated());
19343
+ this.addUnsubscribeFunction(this.subscribeMessageDeleted());
19344
+ this.addUnsubscribeFunction(this.subscribeTextComposerStateChanged());
19345
+ this.addUnsubscribeFunction(this.subscribeAttachmentManagerStateChanged());
19346
+ this.addUnsubscribeFunction(this.subscribeLinkPreviewsManagerStateChanged());
19347
+ this.addUnsubscribeFunction(this.subscribePollComposerStateChanged());
19348
+ this.addUnsubscribeFunction(this.subscribeCustomDataManagerStateChanged());
19349
+ this.addUnsubscribeFunction(this.subscribeMessageComposerStateChanged());
19350
+ this.addUnsubscribeFunction(this.subscribeMessageComposerConfigStateChanged());
19351
+ }
19352
+ this.incrementRefCount();
19353
+ return () => this.unregisterSubscriptions();
19253
19354
  };
19254
19355
  this.subscribeMessageUpdated = () => {
19255
19356
  const eventTypes = [
@@ -19299,13 +19400,13 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
19299
19400
  }).unsubscribe;
19300
19401
  this.subscribeDraftUpdated = () => this.client.on("draft.updated", (event) => {
19301
19402
  const draft = event.draft;
19302
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid)
19403
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid)
19303
19404
  return;
19304
19405
  this.initState({ composition: draft });
19305
19406
  }).unsubscribe;
19306
19407
  this.subscribeDraftDeleted = () => this.client.on("draft.deleted", (event) => {
19307
19408
  const draft = event.draft;
19308
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid) {
19409
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid) {
19309
19410
  return;
19310
19411
  }
19311
19412
  this.logDraftUpdateTimestamp();
@@ -19369,7 +19470,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
19369
19470
  }
19370
19471
  });
19371
19472
  this.subscribeMessageComposerConfigStateChanged = () => {
19372
- let draftUnsubscribeFunctions;
19473
+ let draftUnsubscribeFunction;
19373
19474
  const unsubscribe = this.configState.subscribeWithSelector(
19374
19475
  (currentValue) => ({
19375
19476
  textDefaultValue: currentValue.text.defaultValue,
@@ -19382,19 +19483,16 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
19382
19483
  selection: { start: 0, end: 0 }
19383
19484
  });
19384
19485
  }
19385
- if (draftsEnabled && !draftUnsubscribeFunctions) {
19386
- draftUnsubscribeFunctions = [
19387
- this.subscribeDraftUpdated(),
19388
- this.subscribeDraftDeleted()
19389
- ];
19390
- } else if (!draftsEnabled && draftUnsubscribeFunctions) {
19391
- draftUnsubscribeFunctions.forEach((fn) => fn());
19392
- draftUnsubscribeFunctions = null;
19486
+ if (draftsEnabled && !draftUnsubscribeFunction) {
19487
+ draftUnsubscribeFunction = this.registerDraftEventSubscriptions();
19488
+ } else if (!draftsEnabled && draftUnsubscribeFunction) {
19489
+ draftUnsubscribeFunction();
19490
+ draftUnsubscribeFunction = null;
19393
19491
  }
19394
19492
  }
19395
19493
  );
19396
19494
  return () => {
19397
- draftUnsubscribeFunctions?.forEach((unsubscribe2) => unsubscribe2());
19495
+ draftUnsubscribeFunction?.();
19398
19496
  unsubscribe();
19399
19497
  };
19400
19498
  };
@@ -19464,14 +19562,78 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
19464
19562
  if (!composition) return;
19465
19563
  const { draft } = composition;
19466
19564
  this.state.partialNext({ draftId: draft.id });
19565
+ if (this.client.offlineDb) {
19566
+ try {
19567
+ const optimisticDraftResponse = {
19568
+ channel_cid: this.channel.cid,
19569
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
19570
+ message: draft,
19571
+ parent_id: draft.parent_id,
19572
+ quoted_message: this.quotedMessage ? unformatMessage(this.quotedMessage) : void 0
19573
+ };
19574
+ await this.client.offlineDb.upsertDraft({ draft: optimisticDraftResponse });
19575
+ } catch (error) {
19576
+ this.client.logger("error", `offlineDb:upsertDraft`, {
19577
+ tags: ["channel", "offlineDb"],
19578
+ error
19579
+ });
19580
+ }
19581
+ }
19467
19582
  this.logDraftUpdateTimestamp();
19468
19583
  await this.channel.createDraft(draft);
19469
19584
  };
19470
19585
  this.deleteDraft = async () => {
19471
19586
  if (this.editedMessage || !this.config.drafts.enabled || !this.draftId) return;
19472
19587
  this.state.partialNext({ draftId: null });
19588
+ const parentId = this.threadId ?? void 0;
19589
+ if (this.client.offlineDb) {
19590
+ try {
19591
+ await this.client.offlineDb.deleteDraft({
19592
+ cid: this.channel.cid,
19593
+ parent_id: parentId
19594
+ });
19595
+ } catch (error) {
19596
+ this.client.logger("error", `offlineDb:deleteDraft`, {
19597
+ tags: ["channel", "offlineDb"],
19598
+ error
19599
+ });
19600
+ }
19601
+ }
19473
19602
  this.logDraftUpdateTimestamp();
19474
- await this.channel.deleteDraft({ parent_id: this.threadId ?? void 0 });
19603
+ await this.channel.deleteDraft({ parent_id: parentId });
19604
+ };
19605
+ this.getDraft = async () => {
19606
+ if (this.editedMessage || !this.config.drafts.enabled || !this.client.userID) return;
19607
+ const draftFromOfflineDB = await this.client.offlineDb?.getDraft({
19608
+ cid: this.channel.cid,
19609
+ userId: this.client.userID,
19610
+ parent_id: this.threadId ?? void 0
19611
+ });
19612
+ if (draftFromOfflineDB) {
19613
+ this.initState({ composition: draftFromOfflineDB });
19614
+ }
19615
+ try {
19616
+ const response = await this.channel.getDraft({
19617
+ parent_id: this.threadId ?? void 0
19618
+ });
19619
+ const { draft } = response;
19620
+ if (!draft) return;
19621
+ this.client.offlineDb?.executeQuerySafely(
19622
+ (db) => db.upsertDraft({
19623
+ draft
19624
+ }),
19625
+ { method: "upsertDraft" }
19626
+ );
19627
+ this.initState({ composition: draft });
19628
+ } catch (error) {
19629
+ this.client.notifications.add({
19630
+ message: "Failed to get the draft",
19631
+ origin: {
19632
+ emitter: "MessageComposer",
19633
+ context: { composer: this }
19634
+ }
19635
+ });
19636
+ }
19475
19637
  };
19476
19638
  this.createPoll = async () => {
19477
19639
  const composition = await this.pollComposer.compose();
@@ -19600,6 +19762,9 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
19600
19762
  return this.state.getLatestValue().showReplyInChannel;
19601
19763
  }
19602
19764
  get hasSendableData() {
19765
+ if (this.client.offlineDb) {
19766
+ return !this.compositionIsEmpty;
19767
+ }
19603
19768
  return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId);
19604
19769
  }
19605
19770
  get compositionIsEmpty() {
@@ -19915,6 +20080,17 @@ var Channel = class {
19915
20080
  updates
19916
20081
  );
19917
20082
  }
20083
+ /**
20084
+ * sendReaction - Sends a reaction to a message. If offline support is enabled, it will make sure
20085
+ * that sending the reaction is queued up if it fails due to bad internet conditions and executed
20086
+ * later.
20087
+ *
20088
+ * @param {string} messageID the message id
20089
+ * @param {Reaction} reaction the reaction object for instance {type: 'love'}
20090
+ * @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
20091
+ *
20092
+ * @return {Promise<ReactionAPIResponse>} The Server Response
20093
+ */
19918
20094
  async sendReaction(messageID, reaction, options) {
19919
20095
  if (!messageID) {
19920
20096
  throw Error(`Message id is missing`);
@@ -20767,9 +20943,7 @@ var Channel = class {
20767
20943
  };
20768
20944
  this.getClient().polls.hydratePollCache(state.messages, true);
20769
20945
  this.getClient().reminders.hydrateState(state.messages);
20770
- if (state.draft) {
20771
- this.messageComposer.initState({ composition: state.draft });
20772
- }
20946
+ this.messageComposer.initStateFromChannelResponse(state);
20773
20947
  const areCapabilitiesChanged = [...state.channel.own_capabilities || []].sort().join() !== [
20774
20948
  ...this.data && Array.isArray(this.data?.own_capabilities) ? this.data.own_capabilities : []
20775
20949
  ].sort().join();
@@ -20896,13 +21070,11 @@ var Channel = class {
20896
21070
  /**
20897
21071
  * createDraft - Creates or updates a draft message in a channel
20898
21072
  *
20899
- * @param {string} channelType The channel type
20900
- * @param {string} channelID The channel ID
20901
21073
  * @param {DraftMessagePayload} message The draft message to create or update
20902
21074
  *
20903
21075
  * @return {Promise<CreateDraftResponse>} Response containing the created draft
20904
21076
  */
20905
- async createDraft(message) {
21077
+ async _createDraft(message) {
20906
21078
  return await this.getClient().post(
20907
21079
  this._channelURL() + "/draft",
20908
21080
  {
@@ -20911,18 +21083,82 @@ var Channel = class {
20911
21083
  );
20912
21084
  }
20913
21085
  /**
20914
- * deleteDraft - Deletes a draft message from a channel
21086
+ * createDraft - Creates or updates a draft message in a channel. If offline support is
21087
+ * enabled, it will make sure that creating the draft is queued up if it fails due to
21088
+ * bad internet conditions and executed later.
21089
+ *
21090
+ * @param {DraftMessagePayload} message The draft message to create or update
21091
+ *
21092
+ * @return {Promise<CreateDraftResponse>} Response containing the created draft
21093
+ */
21094
+ async createDraft(message) {
21095
+ try {
21096
+ const offlineDb = this.getClient().offlineDb;
21097
+ if (offlineDb) {
21098
+ return await offlineDb.queueTask({
21099
+ task: {
21100
+ channelId: this.id,
21101
+ channelType: this.type,
21102
+ threadId: message.parent_id,
21103
+ payload: [message],
21104
+ type: "create-draft"
21105
+ }
21106
+ });
21107
+ }
21108
+ } catch (error) {
21109
+ this._client.logger("error", `offlineDb:create-draft`, {
21110
+ tags: ["channel", "offlineDb"],
21111
+ error
21112
+ });
21113
+ }
21114
+ return this._createDraft(message);
21115
+ }
21116
+ /**
21117
+ * deleteDraft - Deletes a draft message from a channel or a thread.
20915
21118
  *
20916
21119
  * @param {Object} options
20917
21120
  * @param {string} options.parent_id Optional parent message ID for drafts in threads
20918
21121
  *
20919
21122
  * @return {Promise<APIResponse>} API response
20920
21123
  */
20921
- async deleteDraft({ parent_id } = {}) {
21124
+ async _deleteDraft({ parent_id } = {}) {
20922
21125
  return await this.getClient().delete(this._channelURL() + "/draft", {
20923
21126
  parent_id
20924
21127
  });
20925
21128
  }
21129
+ /**
21130
+ * deleteDraft - Deletes a draft message from a channel or a thread. If offline support is
21131
+ * enabled, it will make sure that deleting the draft is queued up if it fails due to
21132
+ * bad internet conditions and executed later.
21133
+ *
21134
+ * @param {Object} options
21135
+ * @param {string} options.parent_id Optional parent message ID for drafts in threads
21136
+ *
21137
+ * @return {Promise<APIResponse>} API response
21138
+ */
21139
+ async deleteDraft(options = {}) {
21140
+ const { parent_id } = options;
21141
+ try {
21142
+ const offlineDb = this.getClient().offlineDb;
21143
+ if (offlineDb) {
21144
+ return await offlineDb.queueTask({
21145
+ task: {
21146
+ channelId: this.id,
21147
+ channelType: this.type,
21148
+ threadId: parent_id,
21149
+ payload: [options],
21150
+ type: "delete-draft"
21151
+ }
21152
+ });
21153
+ }
21154
+ } catch (error) {
21155
+ this._client.logger("error", `offlineDb:delete-draft`, {
21156
+ tags: ["channel", "offlineDb"],
21157
+ error
21158
+ });
21159
+ }
21160
+ return this._deleteDraft(options);
21161
+ }
20926
21162
  /**
20927
21163
  * getDraft - Retrieves a draft message from a channel
20928
21164
  *
@@ -25560,9 +25796,7 @@ var StreamChat = class _StreamChat {
25560
25796
  this.polls.hydratePollCache(channelState.messages, true);
25561
25797
  this.reminders.hydrateState(channelState.messages);
25562
25798
  }
25563
- if (channelState.draft) {
25564
- c.messageComposer.initState({ composition: channelState.draft });
25565
- }
25799
+ c.messageComposer.initStateFromChannelResponse(channelState);
25566
25800
  channels.push(c);
25567
25801
  }
25568
25802
  return channels;
@@ -26479,7 +26713,7 @@ var StreamChat = class _StreamChat {
26479
26713
  if (this.userAgent) {
26480
26714
  return this.userAgent;
26481
26715
  }
26482
- const version = "9.6.0";
26716
+ const version = "9.7.0";
26483
26717
  const clientBundle = "node-cjs";
26484
26718
  let userAgentString = "";
26485
26719
  if (this.sdkIdentifier) {
@@ -27568,6 +27802,52 @@ var StreamChat = class _StreamChat {
27568
27802
  ...rest
27569
27803
  });
27570
27804
  }
27805
+ /**
27806
+ * uploadFile - Uploads a file to the configured storage (defaults to Stream CDN)
27807
+ *
27808
+ * @param {string|NodeJS.ReadableStream|Buffer|File} uri The file to upload
27809
+ * @param {string} [name] The name of the file
27810
+ * @param {string} [contentType] The content type of the file
27811
+ * @param {UserResponse} [user] Optional user information
27812
+ *
27813
+ * @return {Promise<SendFileAPIResponse>} Response containing the file URL
27814
+ */
27815
+ uploadFile(uri, name, contentType, user) {
27816
+ return this.sendFile(`${this.baseURL}/uploads/file`, uri, name, contentType, user);
27817
+ }
27818
+ /**
27819
+ * uploadImage - Uploads an image to the configured storage (defaults to Stream CDN)
27820
+ *
27821
+ * @param {string|NodeJS.ReadableStream|File} uri The image to upload
27822
+ * @param {string} [name] The name of the image
27823
+ * @param {string} [contentType] The content type of the image
27824
+ * @param {UserResponse} [user] Optional user information
27825
+ *
27826
+ * @return {Promise<SendFileAPIResponse>} Response containing the image URL
27827
+ */
27828
+ uploadImage(uri, name, contentType, user) {
27829
+ return this.sendFile(`${this.baseURL}/uploads/image`, uri, name, contentType, user);
27830
+ }
27831
+ /**
27832
+ * deleteFile - Deletes a file from the configured storage
27833
+ *
27834
+ * @param {string} url The URL of the file to delete
27835
+ *
27836
+ * @return {Promise<APIResponse>} The server response
27837
+ */
27838
+ deleteFile(url2) {
27839
+ return this.delete(`${this.baseURL}/uploads/file`, { url: url2 });
27840
+ }
27841
+ /**
27842
+ * deleteImage - Deletes an image from the configured storage
27843
+ *
27844
+ * @param {string} url The URL of the image to delete
27845
+ *
27846
+ * @return {Promise<APIResponse>} The server response
27847
+ */
27848
+ deleteImage(url2) {
27849
+ return this.delete(`${this.baseURL}/uploads/image`, { url: url2 });
27850
+ }
27571
27851
  };
27572
27852
 
27573
27853
  // src/events.ts
@@ -28276,6 +28556,35 @@ var AbstractOfflineDB = class {
28276
28556
  (executeOverride) => reactionMethod({ message, reaction, execute: executeOverride })
28277
28557
  );
28278
28558
  };
28559
+ /**
28560
+ * A utility handler for all draft events:
28561
+ * - draft.updated -> updateDraft
28562
+ * - draft.deleted -> deleteDraft
28563
+ * @param event - the WS event we are trying to process
28564
+ * @param execute - whether to immediately execute the operation.
28565
+ */
28566
+ this.handleDraftEvent = async ({
28567
+ event,
28568
+ execute = true
28569
+ }) => {
28570
+ const { cid, draft, type } = event;
28571
+ if (!draft) return [];
28572
+ if (type === "draft.updated") {
28573
+ return await this.upsertDraft({
28574
+ draft,
28575
+ execute
28576
+ });
28577
+ }
28578
+ if (type === "draft.deleted") {
28579
+ if (!cid) return [];
28580
+ return await this.deleteDraft({
28581
+ cid,
28582
+ parent_id: draft.parent_id,
28583
+ execute
28584
+ });
28585
+ }
28586
+ return [];
28587
+ };
28279
28588
  /**
28280
28589
  * A generic event handler that decides which DB API to invoke based on
28281
28590
  * event.type for all events we are currently handling. It is used to both
@@ -28312,6 +28621,9 @@ var AbstractOfflineDB = class {
28312
28621
  if (type === "channel.hidden" || type === "channel.visible") {
28313
28622
  return await this.handleChannelVisibilityEvent({ event, execute });
28314
28623
  }
28624
+ if (type === "draft.updated" || type === "draft.deleted") {
28625
+ return await this.handleDraftEvent({ event, execute });
28626
+ }
28315
28627
  if ((type === "channel.updated" || type === "notification.message_new" || type === "notification.added_to_channel") && channel) {
28316
28628
  return await this.upsertChannelData({ channel, execute });
28317
28629
  }
@@ -28386,6 +28698,12 @@ var AbstractOfflineDB = class {
28386
28698
  if (task.type === "delete-reaction") {
28387
28699
  return await channel._deleteReaction(...task.payload);
28388
28700
  }
28701
+ if (task.type === "create-draft") {
28702
+ return await channel._createDraft(...task.payload);
28703
+ }
28704
+ if (task.type === "delete-draft") {
28705
+ return await channel._deleteDraft(...task.payload);
28706
+ }
28389
28707
  if (task.type === "send-message") {
28390
28708
  const newMessageResponse = await channel._sendMessage(...task.payload);
28391
28709
  const newMessage = newMessageResponse?.message;
@@ -28514,6 +28832,7 @@ var FixedSizeQueueCache = class {
28514
28832
  AttachmentManager,
28515
28833
  BasePaginator,
28516
28834
  BaseSearchSource,
28835
+ BaseSearchSourceSync,
28517
28836
  BuiltinPermissions,
28518
28837
  BuiltinRoles,
28519
28838
  Campaign,