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
@@ -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;
@@ -7352,6 +7416,7 @@ var TextComposer = class {
7352
7416
  var _WithSubscriptions = class _WithSubscriptions {
7353
7417
  constructor() {
7354
7418
  this.unsubscribeFunctions = /* @__PURE__ */ new Set();
7419
+ this.refCount = 0;
7355
7420
  }
7356
7421
  /**
7357
7422
  * Returns a boolean, provides information of whether `registerSubscriptions`
@@ -7363,6 +7428,12 @@ var _WithSubscriptions = class _WithSubscriptions {
7363
7428
  addUnsubscribeFunction(unsubscribeFunction) {
7364
7429
  this.unsubscribeFunctions.add(unsubscribeFunction);
7365
7430
  }
7431
+ /**
7432
+ * Increments `refCount` by one and returns new value.
7433
+ */
7434
+ incrementRefCount() {
7435
+ return ++this.refCount;
7436
+ }
7366
7437
  /**
7367
7438
  * If you re-declare `unregisterSubscriptions` method within your class
7368
7439
  * make sure to run the original too.
@@ -7379,8 +7450,13 @@ var _WithSubscriptions = class _WithSubscriptions {
7379
7450
  * ```
7380
7451
  */
7381
7452
  unregisterSubscriptions() {
7453
+ if (this.refCount > 1) {
7454
+ this.refCount--;
7455
+ return _WithSubscriptions.symbol;
7456
+ }
7382
7457
  this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
7383
7458
  this.unsubscribeFunctions.clear();
7459
+ this.refCount = 0;
7384
7460
  return _WithSubscriptions.symbol;
7385
7461
  }
7386
7462
  };
@@ -7866,7 +7942,6 @@ var initState5 = (composition) => {
7866
7942
  showReplyInChannel: false
7867
7943
  };
7868
7944
  };
7869
- var noop3 = () => void 0;
7870
7945
  var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7871
7946
  // todo: mediaRecorder: MediaRecorderController;
7872
7947
  constructor({
@@ -7891,22 +7966,48 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7891
7966
  this.editedMessage = message;
7892
7967
  }
7893
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
+ };
7894
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
+ };
7895
7996
  this.registerSubscriptions = () => {
7896
- if (this.hasSubscriptions) {
7897
- return noop3;
7898
- }
7899
- this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
7900
- this.addUnsubscribeFunction(this.subscribeMessageUpdated());
7901
- this.addUnsubscribeFunction(this.subscribeMessageDeleted());
7902
- this.addUnsubscribeFunction(this.subscribeTextComposerStateChanged());
7903
- this.addUnsubscribeFunction(this.subscribeAttachmentManagerStateChanged());
7904
- this.addUnsubscribeFunction(this.subscribeLinkPreviewsManagerStateChanged());
7905
- this.addUnsubscribeFunction(this.subscribePollComposerStateChanged());
7906
- this.addUnsubscribeFunction(this.subscribeCustomDataManagerStateChanged());
7907
- this.addUnsubscribeFunction(this.subscribeMessageComposerStateChanged());
7908
- this.addUnsubscribeFunction(this.subscribeMessageComposerConfigStateChanged());
7909
- return this.unregisterSubscriptions.bind(this);
7997
+ if (!this.hasSubscriptions) {
7998
+ this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
7999
+ this.addUnsubscribeFunction(this.subscribeMessageUpdated());
8000
+ this.addUnsubscribeFunction(this.subscribeMessageDeleted());
8001
+ this.addUnsubscribeFunction(this.subscribeTextComposerStateChanged());
8002
+ this.addUnsubscribeFunction(this.subscribeAttachmentManagerStateChanged());
8003
+ this.addUnsubscribeFunction(this.subscribeLinkPreviewsManagerStateChanged());
8004
+ this.addUnsubscribeFunction(this.subscribePollComposerStateChanged());
8005
+ this.addUnsubscribeFunction(this.subscribeCustomDataManagerStateChanged());
8006
+ this.addUnsubscribeFunction(this.subscribeMessageComposerStateChanged());
8007
+ this.addUnsubscribeFunction(this.subscribeMessageComposerConfigStateChanged());
8008
+ }
8009
+ this.incrementRefCount();
8010
+ return () => this.unregisterSubscriptions();
7910
8011
  };
7911
8012
  this.subscribeMessageUpdated = () => {
7912
8013
  const eventTypes = [
@@ -7956,13 +8057,13 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7956
8057
  }).unsubscribe;
7957
8058
  this.subscribeDraftUpdated = () => this.client.on("draft.updated", (event) => {
7958
8059
  const draft = event.draft;
7959
- 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)
7960
8061
  return;
7961
8062
  this.initState({ composition: draft });
7962
8063
  }).unsubscribe;
7963
8064
  this.subscribeDraftDeleted = () => this.client.on("draft.deleted", (event) => {
7964
8065
  const draft = event.draft;
7965
- 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) {
7966
8067
  return;
7967
8068
  }
7968
8069
  this.logDraftUpdateTimestamp();
@@ -8026,7 +8127,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8026
8127
  }
8027
8128
  });
8028
8129
  this.subscribeMessageComposerConfigStateChanged = () => {
8029
- let draftUnsubscribeFunctions;
8130
+ let draftUnsubscribeFunction;
8030
8131
  const unsubscribe = this.configState.subscribeWithSelector(
8031
8132
  (currentValue) => ({
8032
8133
  textDefaultValue: currentValue.text.defaultValue,
@@ -8039,19 +8140,16 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8039
8140
  selection: { start: 0, end: 0 }
8040
8141
  });
8041
8142
  }
8042
- if (draftsEnabled && !draftUnsubscribeFunctions) {
8043
- draftUnsubscribeFunctions = [
8044
- this.subscribeDraftUpdated(),
8045
- this.subscribeDraftDeleted()
8046
- ];
8047
- } else if (!draftsEnabled && draftUnsubscribeFunctions) {
8048
- draftUnsubscribeFunctions.forEach((fn) => fn());
8049
- draftUnsubscribeFunctions = null;
8143
+ if (draftsEnabled && !draftUnsubscribeFunction) {
8144
+ draftUnsubscribeFunction = this.registerDraftEventSubscriptions();
8145
+ } else if (!draftsEnabled && draftUnsubscribeFunction) {
8146
+ draftUnsubscribeFunction();
8147
+ draftUnsubscribeFunction = null;
8050
8148
  }
8051
8149
  }
8052
8150
  );
8053
8151
  return () => {
8054
- draftUnsubscribeFunctions?.forEach((unsubscribe2) => unsubscribe2());
8152
+ draftUnsubscribeFunction?.();
8055
8153
  unsubscribe();
8056
8154
  };
8057
8155
  };
@@ -8121,14 +8219,78 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8121
8219
  if (!composition) return;
8122
8220
  const { draft } = composition;
8123
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
+ }
8124
8239
  this.logDraftUpdateTimestamp();
8125
8240
  await this.channel.createDraft(draft);
8126
8241
  };
8127
8242
  this.deleteDraft = async () => {
8128
8243
  if (this.editedMessage || !this.config.drafts.enabled || !this.draftId) return;
8129
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
+ }
8130
8259
  this.logDraftUpdateTimestamp();
8131
- 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
+ }
8132
8294
  };
8133
8295
  this.createPoll = async () => {
8134
8296
  const composition = await this.pollComposer.compose();
@@ -8257,6 +8419,9 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8257
8419
  return this.state.getLatestValue().showReplyInChannel;
8258
8420
  }
8259
8421
  get hasSendableData() {
8422
+ if (this.client.offlineDb) {
8423
+ return !this.compositionIsEmpty;
8424
+ }
8260
8425
  return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId);
8261
8426
  }
8262
8427
  get compositionIsEmpty() {
@@ -8572,6 +8737,17 @@ var Channel = class {
8572
8737
  updates
8573
8738
  );
8574
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
+ */
8575
8751
  async sendReaction(messageID, reaction, options) {
8576
8752
  if (!messageID) {
8577
8753
  throw Error(`Message id is missing`);
@@ -9424,9 +9600,7 @@ var Channel = class {
9424
9600
  };
9425
9601
  this.getClient().polls.hydratePollCache(state.messages, true);
9426
9602
  this.getClient().reminders.hydrateState(state.messages);
9427
- if (state.draft) {
9428
- this.messageComposer.initState({ composition: state.draft });
9429
- }
9603
+ this.messageComposer.initStateFromChannelResponse(state);
9430
9604
  const areCapabilitiesChanged = [...state.channel.own_capabilities || []].sort().join() !== [
9431
9605
  ...this.data && Array.isArray(this.data?.own_capabilities) ? this.data.own_capabilities : []
9432
9606
  ].sort().join();
@@ -9553,13 +9727,11 @@ var Channel = class {
9553
9727
  /**
9554
9728
  * createDraft - Creates or updates a draft message in a channel
9555
9729
  *
9556
- * @param {string} channelType The channel type
9557
- * @param {string} channelID The channel ID
9558
9730
  * @param {DraftMessagePayload} message The draft message to create or update
9559
9731
  *
9560
9732
  * @return {Promise<CreateDraftResponse>} Response containing the created draft
9561
9733
  */
9562
- async createDraft(message) {
9734
+ async _createDraft(message) {
9563
9735
  return await this.getClient().post(
9564
9736
  this._channelURL() + "/draft",
9565
9737
  {
@@ -9568,18 +9740,82 @@ var Channel = class {
9568
9740
  );
9569
9741
  }
9570
9742
  /**
9571
- * 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.
9572
9775
  *
9573
9776
  * @param {Object} options
9574
9777
  * @param {string} options.parent_id Optional parent message ID for drafts in threads
9575
9778
  *
9576
9779
  * @return {Promise<APIResponse>} API response
9577
9780
  */
9578
- async deleteDraft({ parent_id } = {}) {
9781
+ async _deleteDraft({ parent_id } = {}) {
9579
9782
  return await this.getClient().delete(this._channelURL() + "/draft", {
9580
9783
  parent_id
9581
9784
  });
9582
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
+ }
9583
9819
  /**
9584
9820
  * getDraft - Retrieves a draft message from a channel
9585
9821
  *
@@ -14229,9 +14465,7 @@ var StreamChat = class _StreamChat {
14229
14465
  this.polls.hydratePollCache(channelState.messages, true);
14230
14466
  this.reminders.hydrateState(channelState.messages);
14231
14467
  }
14232
- if (channelState.draft) {
14233
- c.messageComposer.initState({ composition: channelState.draft });
14234
- }
14468
+ c.messageComposer.initStateFromChannelResponse(channelState);
14235
14469
  channels.push(c);
14236
14470
  }
14237
14471
  return channels;
@@ -15148,7 +15382,7 @@ var StreamChat = class _StreamChat {
15148
15382
  if (this.userAgent) {
15149
15383
  return this.userAgent;
15150
15384
  }
15151
- const version = "9.6.0";
15385
+ const version = "9.7.0";
15152
15386
  const clientBundle = "browser-cjs";
15153
15387
  let userAgentString = "";
15154
15388
  if (this.sdkIdentifier) {
@@ -16237,6 +16471,52 @@ var StreamChat = class _StreamChat {
16237
16471
  ...rest
16238
16472
  });
16239
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
+ }
16240
16520
  };
16241
16521
 
16242
16522
  // src/events.ts
@@ -16945,6 +17225,35 @@ var AbstractOfflineDB = class {
16945
17225
  (executeOverride) => reactionMethod({ message, reaction, execute: executeOverride })
16946
17226
  );
16947
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
+ };
16948
17257
  /**
16949
17258
  * A generic event handler that decides which DB API to invoke based on
16950
17259
  * event.type for all events we are currently handling. It is used to both
@@ -16981,6 +17290,9 @@ var AbstractOfflineDB = class {
16981
17290
  if (type === "channel.hidden" || type === "channel.visible") {
16982
17291
  return await this.handleChannelVisibilityEvent({ event, execute });
16983
17292
  }
17293
+ if (type === "draft.updated" || type === "draft.deleted") {
17294
+ return await this.handleDraftEvent({ event, execute });
17295
+ }
16984
17296
  if ((type === "channel.updated" || type === "notification.message_new" || type === "notification.added_to_channel") && channel) {
16985
17297
  return await this.upsertChannelData({ channel, execute });
16986
17298
  }
@@ -17055,6 +17367,12 @@ var AbstractOfflineDB = class {
17055
17367
  if (task.type === "delete-reaction") {
17056
17368
  return await channel._deleteReaction(...task.payload);
17057
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
+ }
17058
17376
  if (task.type === "send-message") {
17059
17377
  const newMessageResponse = await channel._sendMessage(...task.payload);
17060
17378
  const newMessage = newMessageResponse?.message;