stream-chat 9.6.1 → 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 (39) hide show
  1. package/dist/cjs/index.browser.cjs +357 -50
  2. package/dist/cjs/index.browser.cjs.map +3 -3
  3. package/dist/cjs/index.node.cjs +358 -50
  4. package/dist/cjs/index.node.cjs.map +3 -3
  5. package/dist/esm/index.js +357 -50
  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.d.ts +11 -2
  23. package/package.json +1 -1
  24. package/src/channel.ts +85 -10
  25. package/src/client.ts +61 -3
  26. package/src/messageComposer/messageComposer.ts +120 -14
  27. package/src/messageComposer/middleware/textComposer/commands.ts +6 -7
  28. package/src/messageComposer/middleware/textComposer/mentions.ts +1 -2
  29. package/src/messageComposer/middleware/textComposer/types.ts +2 -2
  30. package/src/offline-support/offline_support_api.ts +79 -0
  31. package/src/offline-support/types.ts +41 -1
  32. package/src/search/BaseSearchSource.ts +123 -52
  33. package/src/search/ChannelSearchSource.ts +1 -1
  34. package/src/search/MessageSearchSource.ts +1 -1
  35. package/src/search/UserSearchSource.ts +1 -1
  36. package/src/search/index.ts +1 -0
  37. package/src/search/types.ts +20 -0
  38. package/src/types.ts +8 -1
  39. package/src/utils.ts +31 -2
@@ -161,6 +161,7 @@ __export(index_exports, {
161
161
  AttachmentManager: () => AttachmentManager,
162
162
  BasePaginator: () => BasePaginator,
163
163
  BaseSearchSource: () => BaseSearchSource,
164
+ BaseSearchSourceSync: () => BaseSearchSourceSync,
164
165
  BuiltinPermissions: () => BuiltinPermissions,
165
166
  BuiltinRoles: () => BuiltinRoles,
166
167
  Campaign: () => Campaign,
@@ -2757,6 +2758,23 @@ function formatMessage(message) {
2757
2758
  quoted_message: toLocalMessageBase(message.quoted_message)
2758
2759
  };
2759
2760
  }
2761
+ function unformatMessage(message) {
2762
+ const toMessageResponseBase = (msg) => {
2763
+ if (!msg) return null;
2764
+ const newDateString = (/* @__PURE__ */ new Date()).toISOString();
2765
+ return {
2766
+ ...msg,
2767
+ created_at: message.created_at ? message.created_at.toISOString() : newDateString,
2768
+ deleted_at: message.deleted_at ? message.deleted_at.toISOString() : void 0,
2769
+ pinned_at: message.pinned_at ? message.pinned_at.toISOString() : void 0,
2770
+ updated_at: message.updated_at ? message.updated_at.toISOString() : newDateString
2771
+ };
2772
+ };
2773
+ return {
2774
+ ...toMessageResponseBase(message),
2775
+ quoted_message: toMessageResponseBase(message.quoted_message)
2776
+ };
2777
+ }
2760
2778
  var localMessageToNewMessagePayload = (localMessage) => {
2761
2779
  const {
2762
2780
  // Remove all timestamp fields and client-specific fields.
@@ -6296,11 +6314,8 @@ var DEFAULT_SEARCH_SOURCE_OPTIONS = {
6296
6314
  debounceMs: 300,
6297
6315
  pageSize: 10
6298
6316
  };
6299
- var BaseSearchSource = class {
6317
+ var BaseSearchSourceBase = class {
6300
6318
  constructor(options) {
6301
- this.setDebounceOptions = ({ debounceMs }) => {
6302
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6303
- };
6304
6319
  this.activate = () => {
6305
6320
  if (this.isActive) return;
6306
6321
  this.state.partialNext({ isActive: true });
@@ -6314,11 +6329,9 @@ var BaseSearchSource = class {
6314
6329
  const searchString = newSearchString ?? this.searchQuery;
6315
6330
  return !!(this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery) && searchString);
6316
6331
  };
6317
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
6318
- const { debounceMs, pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6332
+ const { pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6319
6333
  this.pageSize = pageSize;
6320
6334
  this.state = new StateStore(this.initialState);
6321
- this.setDebounceOptions({ debounceMs });
6322
6335
  }
6323
6336
  get lastQueryError() {
6324
6337
  return this.state.getLatestValue().lastQueryError;
@@ -6378,8 +6391,7 @@ var BaseSearchSource = class {
6378
6391
  items: isFirstPage ? stateUpdate.items : [...this.items ?? [], ...stateUpdate.items || []]
6379
6392
  };
6380
6393
  }
6381
- async executeQuery(newSearchString) {
6382
- if (!this.canExecuteQuery(newSearchString)) return;
6394
+ prepareStateForQuery(newSearchString) {
6383
6395
  const hasNewSearchQuery = typeof newSearchString !== "undefined";
6384
6396
  const searchString = newSearchString ?? this.searchQuery;
6385
6397
  if (hasNewSearchQuery) {
@@ -6387,18 +6399,47 @@ var BaseSearchSource = class {
6387
6399
  } else {
6388
6400
  this.state.partialNext({ isLoading: true });
6389
6401
  }
6402
+ return { searchString, hasNewSearchQuery };
6403
+ }
6404
+ updatePaginationStateFromQuery(result) {
6405
+ const { items, next } = result;
6390
6406
  const stateUpdate = {};
6407
+ if (next || next === null) {
6408
+ stateUpdate.next = next;
6409
+ stateUpdate.hasNext = !!next;
6410
+ } else {
6411
+ stateUpdate.offset = (this.offset ?? 0) + items.length;
6412
+ stateUpdate.hasNext = items.length === this.pageSize;
6413
+ }
6414
+ return stateUpdate;
6415
+ }
6416
+ resetState() {
6417
+ this.state.next(this.initialState);
6418
+ }
6419
+ resetStateAndActivate() {
6420
+ this.resetState();
6421
+ this.activate();
6422
+ }
6423
+ };
6424
+ var BaseSearchSource = class extends BaseSearchSourceBase {
6425
+ constructor(options) {
6426
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6427
+ super(options);
6428
+ this.setDebounceOptions = ({ debounceMs }) => {
6429
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6430
+ };
6431
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
6432
+ this.setDebounceOptions({ debounceMs });
6433
+ }
6434
+ async executeQuery(newSearchString) {
6435
+ if (!this.canExecuteQuery(newSearchString)) return;
6436
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
6437
+ let stateUpdate = {};
6391
6438
  try {
6392
6439
  const results = await this.query(searchString);
6393
6440
  if (!results) return;
6394
- const { items, next } = results;
6395
- if (next || next === null) {
6396
- stateUpdate.next = next;
6397
- stateUpdate.hasNext = !!next;
6398
- } else {
6399
- stateUpdate.offset = (this.offset ?? 0) + items.length;
6400
- stateUpdate.hasNext = items.length === this.pageSize;
6401
- }
6441
+ const { items } = results;
6442
+ stateUpdate = this.updatePaginationStateFromQuery(results);
6402
6443
  stateUpdate.items = await this.filterQueryResults(items);
6403
6444
  } catch (e) {
6404
6445
  stateUpdate.lastQueryError = e;
@@ -6409,12 +6450,35 @@ var BaseSearchSource = class {
6409
6450
  cancelScheduledQuery() {
6410
6451
  this.searchDebounced.cancel();
6411
6452
  }
6412
- resetState() {
6413
- this.state.next(this.initialState);
6453
+ };
6454
+ var BaseSearchSourceSync = class extends BaseSearchSourceBase {
6455
+ constructor(options) {
6456
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6457
+ super(options);
6458
+ this.setDebounceOptions = ({ debounceMs }) => {
6459
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6460
+ };
6461
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
6462
+ this.setDebounceOptions({ debounceMs });
6414
6463
  }
6415
- resetStateAndActivate() {
6416
- this.resetState();
6417
- this.activate();
6464
+ executeQuery(newSearchString) {
6465
+ if (!this.canExecuteQuery(newSearchString)) return;
6466
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
6467
+ let stateUpdate = {};
6468
+ try {
6469
+ const results = this.query(searchString);
6470
+ if (!results) return;
6471
+ const { items } = results;
6472
+ stateUpdate = this.updatePaginationStateFromQuery(results);
6473
+ stateUpdate.items = this.filterQueryResults(items);
6474
+ } catch (e) {
6475
+ stateUpdate.lastQueryError = e;
6476
+ } finally {
6477
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
6478
+ }
6479
+ }
6480
+ cancelScheduledQuery() {
6481
+ this.searchDebounced.cancel();
6418
6482
  }
6419
6483
  };
6420
6484
 
@@ -6697,7 +6761,7 @@ var getTokenizedSuggestionDisplayName = ({
6697
6761
  });
6698
6762
 
6699
6763
  // src/messageComposer/middleware/textComposer/commands.ts
6700
- var CommandSearchSource = class extends BaseSearchSource {
6764
+ var CommandSearchSource = class extends BaseSearchSourceSync {
6701
6765
  constructor(channel, options) {
6702
6766
  super(options);
6703
6767
  this.type = "commands";
@@ -6741,10 +6805,10 @@ var CommandSearchSource = class extends BaseSearchSource {
6741
6805
  }
6742
6806
  return 0;
6743
6807
  });
6744
- return Promise.resolve({
6808
+ return {
6745
6809
  items: selectedCommands.map((c) => ({ ...c, id: c.name })),
6746
6810
  next: null
6747
- });
6811
+ };
6748
6812
  }
6749
6813
  filterQueryResults(items) {
6750
6814
  return items;
@@ -7902,7 +7966,33 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7902
7966
  this.editedMessage = message;
7903
7967
  }
7904
7968
  };
7969
+ this.initStateFromChannelResponse = (channelApiResponse) => {
7970
+ if (this.channel.cid !== channelApiResponse.channel.cid) {
7971
+ return;
7972
+ }
7973
+ if (channelApiResponse.draft) {
7974
+ this.initState({ composition: channelApiResponse.draft });
7975
+ } else if (this.state.getLatestValue().draftId) {
7976
+ this.clear();
7977
+ this.client.offlineDb?.executeQuerySafely(
7978
+ (db) => db.deleteDraft({
7979
+ cid: this.channel.cid,
7980
+ parent_id: void 0
7981
+ // makes sure that we don't delete thread drafts while upserting channels
7982
+ }),
7983
+ { method: "deleteDraft" }
7984
+ );
7985
+ }
7986
+ };
7905
7987
  this.initEditingAuditState = (composition) => initEditingAuditState(composition);
7988
+ this.registerDraftEventSubscriptions = () => {
7989
+ const unsubscribeDraftUpdated = this.subscribeDraftUpdated();
7990
+ const unsubscribeDraftDeleted = this.subscribeDraftDeleted();
7991
+ return () => {
7992
+ unsubscribeDraftUpdated();
7993
+ unsubscribeDraftDeleted();
7994
+ };
7995
+ };
7906
7996
  this.registerSubscriptions = () => {
7907
7997
  if (!this.hasSubscriptions) {
7908
7998
  this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
@@ -7967,13 +8057,13 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7967
8057
  }).unsubscribe;
7968
8058
  this.subscribeDraftUpdated = () => this.client.on("draft.updated", (event) => {
7969
8059
  const draft = event.draft;
7970
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid)
8060
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid)
7971
8061
  return;
7972
8062
  this.initState({ composition: draft });
7973
8063
  }).unsubscribe;
7974
8064
  this.subscribeDraftDeleted = () => this.client.on("draft.deleted", (event) => {
7975
8065
  const draft = event.draft;
7976
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid) {
8066
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid) {
7977
8067
  return;
7978
8068
  }
7979
8069
  this.logDraftUpdateTimestamp();
@@ -8037,7 +8127,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8037
8127
  }
8038
8128
  });
8039
8129
  this.subscribeMessageComposerConfigStateChanged = () => {
8040
- let draftUnsubscribeFunctions;
8130
+ let draftUnsubscribeFunction;
8041
8131
  const unsubscribe = this.configState.subscribeWithSelector(
8042
8132
  (currentValue) => ({
8043
8133
  textDefaultValue: currentValue.text.defaultValue,
@@ -8050,19 +8140,16 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8050
8140
  selection: { start: 0, end: 0 }
8051
8141
  });
8052
8142
  }
8053
- if (draftsEnabled && !draftUnsubscribeFunctions) {
8054
- draftUnsubscribeFunctions = [
8055
- this.subscribeDraftUpdated(),
8056
- this.subscribeDraftDeleted()
8057
- ];
8058
- } else if (!draftsEnabled && draftUnsubscribeFunctions) {
8059
- draftUnsubscribeFunctions.forEach((fn) => fn());
8060
- draftUnsubscribeFunctions = null;
8143
+ if (draftsEnabled && !draftUnsubscribeFunction) {
8144
+ draftUnsubscribeFunction = this.registerDraftEventSubscriptions();
8145
+ } else if (!draftsEnabled && draftUnsubscribeFunction) {
8146
+ draftUnsubscribeFunction();
8147
+ draftUnsubscribeFunction = null;
8061
8148
  }
8062
8149
  }
8063
8150
  );
8064
8151
  return () => {
8065
- draftUnsubscribeFunctions?.forEach((unsubscribe2) => unsubscribe2());
8152
+ draftUnsubscribeFunction?.();
8066
8153
  unsubscribe();
8067
8154
  };
8068
8155
  };
@@ -8132,14 +8219,78 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8132
8219
  if (!composition) return;
8133
8220
  const { draft } = composition;
8134
8221
  this.state.partialNext({ draftId: draft.id });
8222
+ if (this.client.offlineDb) {
8223
+ try {
8224
+ const optimisticDraftResponse = {
8225
+ channel_cid: this.channel.cid,
8226
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
8227
+ message: draft,
8228
+ parent_id: draft.parent_id,
8229
+ quoted_message: this.quotedMessage ? unformatMessage(this.quotedMessage) : void 0
8230
+ };
8231
+ await this.client.offlineDb.upsertDraft({ draft: optimisticDraftResponse });
8232
+ } catch (error) {
8233
+ this.client.logger("error", `offlineDb:upsertDraft`, {
8234
+ tags: ["channel", "offlineDb"],
8235
+ error
8236
+ });
8237
+ }
8238
+ }
8135
8239
  this.logDraftUpdateTimestamp();
8136
8240
  await this.channel.createDraft(draft);
8137
8241
  };
8138
8242
  this.deleteDraft = async () => {
8139
8243
  if (this.editedMessage || !this.config.drafts.enabled || !this.draftId) return;
8140
8244
  this.state.partialNext({ draftId: null });
8245
+ const parentId = this.threadId ?? void 0;
8246
+ if (this.client.offlineDb) {
8247
+ try {
8248
+ await this.client.offlineDb.deleteDraft({
8249
+ cid: this.channel.cid,
8250
+ parent_id: parentId
8251
+ });
8252
+ } catch (error) {
8253
+ this.client.logger("error", `offlineDb:deleteDraft`, {
8254
+ tags: ["channel", "offlineDb"],
8255
+ error
8256
+ });
8257
+ }
8258
+ }
8141
8259
  this.logDraftUpdateTimestamp();
8142
- await this.channel.deleteDraft({ parent_id: this.threadId ?? void 0 });
8260
+ await this.channel.deleteDraft({ parent_id: parentId });
8261
+ };
8262
+ this.getDraft = async () => {
8263
+ if (this.editedMessage || !this.config.drafts.enabled || !this.client.userID) return;
8264
+ const draftFromOfflineDB = await this.client.offlineDb?.getDraft({
8265
+ cid: this.channel.cid,
8266
+ userId: this.client.userID,
8267
+ parent_id: this.threadId ?? void 0
8268
+ });
8269
+ if (draftFromOfflineDB) {
8270
+ this.initState({ composition: draftFromOfflineDB });
8271
+ }
8272
+ try {
8273
+ const response = await this.channel.getDraft({
8274
+ parent_id: this.threadId ?? void 0
8275
+ });
8276
+ const { draft } = response;
8277
+ if (!draft) return;
8278
+ this.client.offlineDb?.executeQuerySafely(
8279
+ (db) => db.upsertDraft({
8280
+ draft
8281
+ }),
8282
+ { method: "upsertDraft" }
8283
+ );
8284
+ this.initState({ composition: draft });
8285
+ } catch (error) {
8286
+ this.client.notifications.add({
8287
+ message: "Failed to get the draft",
8288
+ origin: {
8289
+ emitter: "MessageComposer",
8290
+ context: { composer: this }
8291
+ }
8292
+ });
8293
+ }
8143
8294
  };
8144
8295
  this.createPoll = async () => {
8145
8296
  const composition = await this.pollComposer.compose();
@@ -8268,6 +8419,9 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8268
8419
  return this.state.getLatestValue().showReplyInChannel;
8269
8420
  }
8270
8421
  get hasSendableData() {
8422
+ if (this.client.offlineDb) {
8423
+ return !this.compositionIsEmpty;
8424
+ }
8271
8425
  return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId);
8272
8426
  }
8273
8427
  get compositionIsEmpty() {
@@ -8583,6 +8737,17 @@ var Channel = class {
8583
8737
  updates
8584
8738
  );
8585
8739
  }
8740
+ /**
8741
+ * sendReaction - Sends a reaction to a message. If offline support is enabled, it will make sure
8742
+ * that sending the reaction is queued up if it fails due to bad internet conditions and executed
8743
+ * later.
8744
+ *
8745
+ * @param {string} messageID the message id
8746
+ * @param {Reaction} reaction the reaction object for instance {type: 'love'}
8747
+ * @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
8748
+ *
8749
+ * @return {Promise<ReactionAPIResponse>} The Server Response
8750
+ */
8586
8751
  async sendReaction(messageID, reaction, options) {
8587
8752
  if (!messageID) {
8588
8753
  throw Error(`Message id is missing`);
@@ -9435,9 +9600,7 @@ var Channel = class {
9435
9600
  };
9436
9601
  this.getClient().polls.hydratePollCache(state.messages, true);
9437
9602
  this.getClient().reminders.hydrateState(state.messages);
9438
- if (state.draft) {
9439
- this.messageComposer.initState({ composition: state.draft });
9440
- }
9603
+ this.messageComposer.initStateFromChannelResponse(state);
9441
9604
  const areCapabilitiesChanged = [...state.channel.own_capabilities || []].sort().join() !== [
9442
9605
  ...this.data && Array.isArray(this.data?.own_capabilities) ? this.data.own_capabilities : []
9443
9606
  ].sort().join();
@@ -9564,13 +9727,11 @@ var Channel = class {
9564
9727
  /**
9565
9728
  * createDraft - Creates or updates a draft message in a channel
9566
9729
  *
9567
- * @param {string} channelType The channel type
9568
- * @param {string} channelID The channel ID
9569
9730
  * @param {DraftMessagePayload} message The draft message to create or update
9570
9731
  *
9571
9732
  * @return {Promise<CreateDraftResponse>} Response containing the created draft
9572
9733
  */
9573
- async createDraft(message) {
9734
+ async _createDraft(message) {
9574
9735
  return await this.getClient().post(
9575
9736
  this._channelURL() + "/draft",
9576
9737
  {
@@ -9579,18 +9740,82 @@ var Channel = class {
9579
9740
  );
9580
9741
  }
9581
9742
  /**
9582
- * deleteDraft - Deletes a draft message from a channel
9743
+ * createDraft - Creates or updates a draft message in a channel. If offline support is
9744
+ * enabled, it will make sure that creating the draft is queued up if it fails due to
9745
+ * bad internet conditions and executed later.
9746
+ *
9747
+ * @param {DraftMessagePayload} message The draft message to create or update
9748
+ *
9749
+ * @return {Promise<CreateDraftResponse>} Response containing the created draft
9750
+ */
9751
+ async createDraft(message) {
9752
+ try {
9753
+ const offlineDb = this.getClient().offlineDb;
9754
+ if (offlineDb) {
9755
+ return await offlineDb.queueTask({
9756
+ task: {
9757
+ channelId: this.id,
9758
+ channelType: this.type,
9759
+ threadId: message.parent_id,
9760
+ payload: [message],
9761
+ type: "create-draft"
9762
+ }
9763
+ });
9764
+ }
9765
+ } catch (error) {
9766
+ this._client.logger("error", `offlineDb:create-draft`, {
9767
+ tags: ["channel", "offlineDb"],
9768
+ error
9769
+ });
9770
+ }
9771
+ return this._createDraft(message);
9772
+ }
9773
+ /**
9774
+ * deleteDraft - Deletes a draft message from a channel or a thread.
9583
9775
  *
9584
9776
  * @param {Object} options
9585
9777
  * @param {string} options.parent_id Optional parent message ID for drafts in threads
9586
9778
  *
9587
9779
  * @return {Promise<APIResponse>} API response
9588
9780
  */
9589
- async deleteDraft({ parent_id } = {}) {
9781
+ async _deleteDraft({ parent_id } = {}) {
9590
9782
  return await this.getClient().delete(this._channelURL() + "/draft", {
9591
9783
  parent_id
9592
9784
  });
9593
9785
  }
9786
+ /**
9787
+ * deleteDraft - Deletes a draft message from a channel or a thread. If offline support is
9788
+ * enabled, it will make sure that deleting the draft is queued up if it fails due to
9789
+ * bad internet conditions and executed later.
9790
+ *
9791
+ * @param {Object} options
9792
+ * @param {string} options.parent_id Optional parent message ID for drafts in threads
9793
+ *
9794
+ * @return {Promise<APIResponse>} API response
9795
+ */
9796
+ async deleteDraft(options = {}) {
9797
+ const { parent_id } = options;
9798
+ try {
9799
+ const offlineDb = this.getClient().offlineDb;
9800
+ if (offlineDb) {
9801
+ return await offlineDb.queueTask({
9802
+ task: {
9803
+ channelId: this.id,
9804
+ channelType: this.type,
9805
+ threadId: parent_id,
9806
+ payload: [options],
9807
+ type: "delete-draft"
9808
+ }
9809
+ });
9810
+ }
9811
+ } catch (error) {
9812
+ this._client.logger("error", `offlineDb:delete-draft`, {
9813
+ tags: ["channel", "offlineDb"],
9814
+ error
9815
+ });
9816
+ }
9817
+ return this._deleteDraft(options);
9818
+ }
9594
9819
  /**
9595
9820
  * getDraft - Retrieves a draft message from a channel
9596
9821
  *
@@ -14240,9 +14465,7 @@ var StreamChat = class _StreamChat {
14240
14465
  this.polls.hydratePollCache(channelState.messages, true);
14241
14466
  this.reminders.hydrateState(channelState.messages);
14242
14467
  }
14243
- if (channelState.draft) {
14244
- c.messageComposer.initState({ composition: channelState.draft });
14245
- }
14468
+ c.messageComposer.initStateFromChannelResponse(channelState);
14246
14469
  channels.push(c);
14247
14470
  }
14248
14471
  return channels;
@@ -15159,7 +15382,7 @@ var StreamChat = class _StreamChat {
15159
15382
  if (this.userAgent) {
15160
15383
  return this.userAgent;
15161
15384
  }
15162
- const version = "9.6.1";
15385
+ const version = "9.7.0";
15163
15386
  const clientBundle = "browser-cjs";
15164
15387
  let userAgentString = "";
15165
15388
  if (this.sdkIdentifier) {
@@ -16248,6 +16471,52 @@ var StreamChat = class _StreamChat {
16248
16471
  ...rest
16249
16472
  });
16250
16473
  }
16474
+ /**
16475
+ * uploadFile - Uploads a file to the configured storage (defaults to Stream CDN)
16476
+ *
16477
+ * @param {string|NodeJS.ReadableStream|Buffer|File} uri The file to upload
16478
+ * @param {string} [name] The name of the file
16479
+ * @param {string} [contentType] The content type of the file
16480
+ * @param {UserResponse} [user] Optional user information
16481
+ *
16482
+ * @return {Promise<SendFileAPIResponse>} Response containing the file URL
16483
+ */
16484
+ uploadFile(uri, name, contentType, user) {
16485
+ return this.sendFile(`${this.baseURL}/uploads/file`, uri, name, contentType, user);
16486
+ }
16487
+ /**
16488
+ * uploadImage - Uploads an image to the configured storage (defaults to Stream CDN)
16489
+ *
16490
+ * @param {string|NodeJS.ReadableStream|File} uri The image to upload
16491
+ * @param {string} [name] The name of the image
16492
+ * @param {string} [contentType] The content type of the image
16493
+ * @param {UserResponse} [user] Optional user information
16494
+ *
16495
+ * @return {Promise<SendFileAPIResponse>} Response containing the image URL
16496
+ */
16497
+ uploadImage(uri, name, contentType, user) {
16498
+ return this.sendFile(`${this.baseURL}/uploads/image`, uri, name, contentType, user);
16499
+ }
16500
+ /**
16501
+ * deleteFile - Deletes a file from the configured storage
16502
+ *
16503
+ * @param {string} url The URL of the file to delete
16504
+ *
16505
+ * @return {Promise<APIResponse>} The server response
16506
+ */
16507
+ deleteFile(url) {
16508
+ return this.delete(`${this.baseURL}/uploads/file`, { url });
16509
+ }
16510
+ /**
16511
+ * deleteImage - Deletes an image from the configured storage
16512
+ *
16513
+ * @param {string} url The URL of the image to delete
16514
+ *
16515
+ * @return {Promise<APIResponse>} The server response
16516
+ */
16517
+ deleteImage(url) {
16518
+ return this.delete(`${this.baseURL}/uploads/image`, { url });
16519
+ }
16251
16520
  };
16252
16521
 
16253
16522
  // src/events.ts
@@ -16956,6 +17225,35 @@ var AbstractOfflineDB = class {
16956
17225
  (executeOverride) => reactionMethod({ message, reaction, execute: executeOverride })
16957
17226
  );
16958
17227
  };
17228
+ /**
17229
+ * A utility handler for all draft events:
17230
+ * - draft.updated -> updateDraft
17231
+ * - draft.deleted -> deleteDraft
17232
+ * @param event - the WS event we are trying to process
17233
+ * @param execute - whether to immediately execute the operation.
17234
+ */
17235
+ this.handleDraftEvent = async ({
17236
+ event,
17237
+ execute = true
17238
+ }) => {
17239
+ const { cid, draft, type } = event;
17240
+ if (!draft) return [];
17241
+ if (type === "draft.updated") {
17242
+ return await this.upsertDraft({
17243
+ draft,
17244
+ execute
17245
+ });
17246
+ }
17247
+ if (type === "draft.deleted") {
17248
+ if (!cid) return [];
17249
+ return await this.deleteDraft({
17250
+ cid,
17251
+ parent_id: draft.parent_id,
17252
+ execute
17253
+ });
17254
+ }
17255
+ return [];
17256
+ };
16959
17257
  /**
16960
17258
  * A generic event handler that decides which DB API to invoke based on
16961
17259
  * event.type for all events we are currently handling. It is used to both
@@ -16992,6 +17290,9 @@ var AbstractOfflineDB = class {
16992
17290
  if (type === "channel.hidden" || type === "channel.visible") {
16993
17291
  return await this.handleChannelVisibilityEvent({ event, execute });
16994
17292
  }
17293
+ if (type === "draft.updated" || type === "draft.deleted") {
17294
+ return await this.handleDraftEvent({ event, execute });
17295
+ }
16995
17296
  if ((type === "channel.updated" || type === "notification.message_new" || type === "notification.added_to_channel") && channel) {
16996
17297
  return await this.upsertChannelData({ channel, execute });
16997
17298
  }
@@ -17066,6 +17367,12 @@ var AbstractOfflineDB = class {
17066
17367
  if (task.type === "delete-reaction") {
17067
17368
  return await channel._deleteReaction(...task.payload);
17068
17369
  }
17370
+ if (task.type === "create-draft") {
17371
+ return await channel._createDraft(...task.payload);
17372
+ }
17373
+ if (task.type === "delete-draft") {
17374
+ return await channel._deleteDraft(...task.payload);
17375
+ }
17069
17376
  if (task.type === "send-message") {
17070
17377
  const newMessageResponse = await channel._sendMessage(...task.payload);
17071
17378
  const newMessage = newMessageResponse?.message;