stream-chat 9.6.1 → 9.8.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 (59) hide show
  1. package/dist/cjs/index.browser.cjs +532 -57
  2. package/dist/cjs/index.browser.cjs.map +4 -4
  3. package/dist/cjs/index.node.cjs +538 -57
  4. package/dist/cjs/index.node.cjs.map +4 -4
  5. package/dist/esm/index.js +532 -57
  6. package/dist/esm/index.js.map +4 -4
  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/messageComposer/commandInjection.d.ts +4 -0
  11. package/dist/types/messageComposer/middleware/messageComposer/index.d.ts +1 -0
  12. package/dist/types/messageComposer/middleware/textComposer/activeCommandGuard.d.ts +4 -0
  13. package/dist/types/messageComposer/middleware/textComposer/commandStringExtraction.d.ts +5 -0
  14. package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +5 -5
  15. package/dist/types/messageComposer/middleware/textComposer/index.d.ts +2 -0
  16. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +1 -2
  17. package/dist/types/messageComposer/middleware/textComposer/textMiddlewareUtils.d.ts +6 -0
  18. package/dist/types/messageComposer/middleware/textComposer/types.d.ts +3 -2
  19. package/dist/types/messageComposer/textComposer.d.ts +6 -3
  20. package/dist/types/offline-support/offline_support_api.d.ts +39 -0
  21. package/dist/types/offline-support/types.d.ts +36 -2
  22. package/dist/types/pagination/BasePaginator.d.ts +1 -1
  23. package/dist/types/pagination/ReminderPaginator.d.ts +6 -2
  24. package/dist/types/reminders/ReminderManager.d.ts +3 -3
  25. package/dist/types/search/BaseSearchSource.d.ts +37 -31
  26. package/dist/types/search/ChannelSearchSource.d.ts +1 -1
  27. package/dist/types/search/MessageSearchSource.d.ts +1 -1
  28. package/dist/types/search/UserSearchSource.d.ts +1 -1
  29. package/dist/types/search/index.d.ts +1 -0
  30. package/dist/types/search/types.d.ts +20 -0
  31. package/dist/types/types.d.ts +6 -2
  32. package/dist/types/utils.d.ts +11 -2
  33. package/package.json +1 -1
  34. package/src/channel.ts +85 -10
  35. package/src/client.ts +61 -3
  36. package/src/messageComposer/messageComposer.ts +120 -14
  37. package/src/messageComposer/middleware/messageComposer/commandInjection.ts +72 -0
  38. package/src/messageComposer/middleware/messageComposer/index.ts +1 -0
  39. package/src/messageComposer/middleware/textComposer/activeCommandGuard.ts +20 -0
  40. package/src/messageComposer/middleware/textComposer/commandStringExtraction.ts +56 -0
  41. package/src/messageComposer/middleware/textComposer/commands.ts +28 -11
  42. package/src/messageComposer/middleware/textComposer/index.ts +2 -0
  43. package/src/messageComposer/middleware/textComposer/mentions.ts +1 -2
  44. package/src/messageComposer/middleware/textComposer/textMiddlewareUtils.ts +14 -0
  45. package/src/messageComposer/middleware/textComposer/types.ts +3 -2
  46. package/src/messageComposer/textComposer.ts +23 -3
  47. package/src/offline-support/offline_support_api.ts +79 -0
  48. package/src/offline-support/types.ts +41 -1
  49. package/src/pagination/BasePaginator.ts +1 -1
  50. package/src/pagination/ReminderPaginator.ts +20 -2
  51. package/src/reminders/ReminderManager.ts +16 -2
  52. package/src/search/BaseSearchSource.ts +123 -52
  53. package/src/search/ChannelSearchSource.ts +1 -1
  54. package/src/search/MessageSearchSource.ts +1 -1
  55. package/src/search/UserSearchSource.ts +1 -1
  56. package/src/search/index.ts +1 -0
  57. package/src/search/types.ts +20 -0
  58. package/src/types.ts +9 -2
  59. 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,
@@ -238,12 +239,16 @@ __export(index_exports, {
238
239
  calculateLevenshtein: () => calculateLevenshtein,
239
240
  channelManagerEventToHandlerMapping: () => channelManagerEventToHandlerMapping,
240
241
  chatCodes: () => chatCodes,
242
+ createActiveCommandGuardMiddleware: () => createActiveCommandGuardMiddleware,
241
243
  createAttachmentsCompositionMiddleware: () => createAttachmentsCompositionMiddleware,
244
+ createCommandInjectionMiddleware: () => createCommandInjectionMiddleware,
245
+ createCommandStringExtractionMiddleware: () => createCommandStringExtractionMiddleware,
242
246
  createCommandsMiddleware: () => createCommandsMiddleware,
243
247
  createCompositionDataCleanupMiddleware: () => createCompositionDataCleanupMiddleware,
244
248
  createCompositionValidationMiddleware: () => createCompositionValidationMiddleware,
245
249
  createCustomDataCompositionMiddleware: () => createCustomDataCompositionMiddleware,
246
250
  createDraftAttachmentsCompositionMiddleware: () => createDraftAttachmentsCompositionMiddleware,
251
+ createDraftCommandInjectionMiddleware: () => createDraftCommandInjectionMiddleware,
247
252
  createDraftCompositionValidationMiddleware: () => createDraftCompositionValidationMiddleware,
248
253
  createDraftCustomDataCompositionMiddleware: () => createDraftCustomDataCompositionMiddleware,
249
254
  createDraftLinkPreviewsCompositionMiddleware: () => createDraftLinkPreviewsCompositionMiddleware,
@@ -267,6 +272,7 @@ __export(index_exports, {
267
272
  formatMessage: () => formatMessage,
268
273
  generateFileName: () => generateFileName,
269
274
  getAttachmentTypeFromMimeType: () => getAttachmentTypeFromMimeType,
275
+ getCompleteCommandInString: () => getCompleteCommandInString,
270
276
  getExtensionFromMimeType: () => getExtensionFromMimeType,
271
277
  getTokenizedSuggestionDisplayName: () => getTokenizedSuggestionDisplayName,
272
278
  getTriggerCharWithToken: () => getTriggerCharWithToken,
@@ -2757,6 +2763,23 @@ function formatMessage(message) {
2757
2763
  quoted_message: toLocalMessageBase(message.quoted_message)
2758
2764
  };
2759
2765
  }
2766
+ function unformatMessage(message) {
2767
+ const toMessageResponseBase = (msg) => {
2768
+ if (!msg) return null;
2769
+ const newDateString = (/* @__PURE__ */ new Date()).toISOString();
2770
+ return {
2771
+ ...msg,
2772
+ created_at: message.created_at ? message.created_at.toISOString() : newDateString,
2773
+ deleted_at: message.deleted_at ? message.deleted_at.toISOString() : void 0,
2774
+ pinned_at: message.pinned_at ? message.pinned_at.toISOString() : void 0,
2775
+ updated_at: message.updated_at ? message.updated_at.toISOString() : newDateString
2776
+ };
2777
+ };
2778
+ return {
2779
+ ...toMessageResponseBase(message),
2780
+ quoted_message: toMessageResponseBase(message.quoted_message)
2781
+ };
2782
+ }
2760
2783
  var localMessageToNewMessagePayload = (localMessage) => {
2761
2784
  const {
2762
2785
  // Remove all timestamp fields and client-specific fields.
@@ -6291,16 +6314,83 @@ var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
6291
6314
  }
6292
6315
  };
6293
6316
 
6317
+ // src/messageComposer/middleware/messageComposer/commandInjection.ts
6318
+ var createCommandInjectionMiddleware = (composer) => ({
6319
+ handlers: {
6320
+ compose: ({
6321
+ forward,
6322
+ next,
6323
+ state
6324
+ }) => {
6325
+ const command = composer.textComposer.command;
6326
+ if (!command) {
6327
+ return forward();
6328
+ }
6329
+ const { text } = state.localMessage;
6330
+ const injection = `/${command?.name}`;
6331
+ const enrichedText = `${injection} ${text}`;
6332
+ return next({
6333
+ ...state,
6334
+ localMessage: {
6335
+ ...state.localMessage,
6336
+ text: enrichedText
6337
+ },
6338
+ message: {
6339
+ ...state.message,
6340
+ text: enrichedText
6341
+ }
6342
+ });
6343
+ }
6344
+ },
6345
+ id: "stream-io/message-composer-middleware/command-string-injection"
6346
+ });
6347
+ var createDraftCommandInjectionMiddleware = (composer) => ({
6348
+ handlers: {
6349
+ compose: ({
6350
+ forward,
6351
+ next,
6352
+ state
6353
+ }) => {
6354
+ const command = composer.textComposer.command;
6355
+ if (!command) {
6356
+ return forward();
6357
+ }
6358
+ const { text } = state.draft;
6359
+ const injection = `/${command?.name}`;
6360
+ const enrichedText = `${injection} ${text}`;
6361
+ return next({
6362
+ ...state,
6363
+ draft: {
6364
+ ...state.draft,
6365
+ text: enrichedText
6366
+ }
6367
+ });
6368
+ }
6369
+ },
6370
+ id: "stream-io/message-composer-middleware/draft-command-string-injection"
6371
+ });
6372
+
6373
+ // src/messageComposer/middleware/textComposer/activeCommandGuard.ts
6374
+ var createActiveCommandGuardMiddleware = () => ({
6375
+ handlers: {
6376
+ onChange: ({ complete, forward, state }) => {
6377
+ if (state.command) {
6378
+ return complete(state);
6379
+ }
6380
+ return forward();
6381
+ },
6382
+ onSuggestionItemSelect: ({ forward }) => forward()
6383
+ },
6384
+ id: "stream-io/text-composer/active-command-guard"
6385
+ });
6386
+
6294
6387
  // src/search/BaseSearchSource.ts
6295
6388
  var DEFAULT_SEARCH_SOURCE_OPTIONS = {
6296
6389
  debounceMs: 300,
6297
6390
  pageSize: 10
6298
6391
  };
6299
- var BaseSearchSource = class {
6392
+ var BaseSearchSourceBase = class {
6300
6393
  constructor(options) {
6301
- this.setDebounceOptions = ({ debounceMs }) => {
6302
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6303
- };
6304
6394
  this.activate = () => {
6305
6395
  if (this.isActive) return;
6306
6396
  this.state.partialNext({ isActive: true });
@@ -6314,11 +6404,9 @@ var BaseSearchSource = class {
6314
6404
  const searchString = newSearchString ?? this.searchQuery;
6315
6405
  return !!(this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery) && searchString);
6316
6406
  };
6317
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
6318
- const { debounceMs, pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6407
+ const { pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6319
6408
  this.pageSize = pageSize;
6320
6409
  this.state = new StateStore(this.initialState);
6321
- this.setDebounceOptions({ debounceMs });
6322
6410
  }
6323
6411
  get lastQueryError() {
6324
6412
  return this.state.getLatestValue().lastQueryError;
@@ -6378,8 +6466,7 @@ var BaseSearchSource = class {
6378
6466
  items: isFirstPage ? stateUpdate.items : [...this.items ?? [], ...stateUpdate.items || []]
6379
6467
  };
6380
6468
  }
6381
- async executeQuery(newSearchString) {
6382
- if (!this.canExecuteQuery(newSearchString)) return;
6469
+ prepareStateForQuery(newSearchString) {
6383
6470
  const hasNewSearchQuery = typeof newSearchString !== "undefined";
6384
6471
  const searchString = newSearchString ?? this.searchQuery;
6385
6472
  if (hasNewSearchQuery) {
@@ -6387,18 +6474,47 @@ var BaseSearchSource = class {
6387
6474
  } else {
6388
6475
  this.state.partialNext({ isLoading: true });
6389
6476
  }
6477
+ return { searchString, hasNewSearchQuery };
6478
+ }
6479
+ updatePaginationStateFromQuery(result) {
6480
+ const { items, next } = result;
6390
6481
  const stateUpdate = {};
6482
+ if (next || next === null) {
6483
+ stateUpdate.next = next;
6484
+ stateUpdate.hasNext = !!next;
6485
+ } else {
6486
+ stateUpdate.offset = (this.offset ?? 0) + items.length;
6487
+ stateUpdate.hasNext = items.length === this.pageSize;
6488
+ }
6489
+ return stateUpdate;
6490
+ }
6491
+ resetState() {
6492
+ this.state.next(this.initialState);
6493
+ }
6494
+ resetStateAndActivate() {
6495
+ this.resetState();
6496
+ this.activate();
6497
+ }
6498
+ };
6499
+ var BaseSearchSource = class extends BaseSearchSourceBase {
6500
+ constructor(options) {
6501
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6502
+ super(options);
6503
+ this.setDebounceOptions = ({ debounceMs }) => {
6504
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6505
+ };
6506
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
6507
+ this.setDebounceOptions({ debounceMs });
6508
+ }
6509
+ async executeQuery(newSearchString) {
6510
+ if (!this.canExecuteQuery(newSearchString)) return;
6511
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
6512
+ let stateUpdate = {};
6391
6513
  try {
6392
6514
  const results = await this.query(searchString);
6393
6515
  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
- }
6516
+ const { items } = results;
6517
+ stateUpdate = this.updatePaginationStateFromQuery(results);
6402
6518
  stateUpdate.items = await this.filterQueryResults(items);
6403
6519
  } catch (e) {
6404
6520
  stateUpdate.lastQueryError = e;
@@ -6409,12 +6525,35 @@ var BaseSearchSource = class {
6409
6525
  cancelScheduledQuery() {
6410
6526
  this.searchDebounced.cancel();
6411
6527
  }
6412
- resetState() {
6413
- this.state.next(this.initialState);
6528
+ };
6529
+ var BaseSearchSourceSync = class extends BaseSearchSourceBase {
6530
+ constructor(options) {
6531
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6532
+ super(options);
6533
+ this.setDebounceOptions = ({ debounceMs }) => {
6534
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6535
+ };
6536
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
6537
+ this.setDebounceOptions({ debounceMs });
6414
6538
  }
6415
- resetStateAndActivate() {
6416
- this.resetState();
6417
- this.activate();
6539
+ executeQuery(newSearchString) {
6540
+ if (!this.canExecuteQuery(newSearchString)) return;
6541
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
6542
+ let stateUpdate = {};
6543
+ try {
6544
+ const results = this.query(searchString);
6545
+ if (!results) return;
6546
+ const { items } = results;
6547
+ stateUpdate = this.updatePaginationStateFromQuery(results);
6548
+ stateUpdate.items = this.filterQueryResults(items);
6549
+ } catch (e) {
6550
+ stateUpdate.lastQueryError = e;
6551
+ } finally {
6552
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
6553
+ }
6554
+ }
6555
+ cancelScheduledQuery() {
6556
+ this.searchDebounced.cancel();
6418
6557
  }
6419
6558
  };
6420
6559
 
@@ -6648,6 +6787,11 @@ var getTriggerCharWithToken = ({
6648
6787
  );
6649
6788
  return match && match[match.length - 1].trim();
6650
6789
  };
6790
+ var getCompleteCommandInString = (text) => {
6791
+ const match = text.match(/^\/(\S+)\s+.*/);
6792
+ const commandName = match && match[1];
6793
+ return commandName;
6794
+ };
6651
6795
  var insertItemWithTrigger = ({
6652
6796
  insertText,
6653
6797
  selection,
@@ -6697,7 +6841,7 @@ var getTokenizedSuggestionDisplayName = ({
6697
6841
  });
6698
6842
 
6699
6843
  // src/messageComposer/middleware/textComposer/commands.ts
6700
- var CommandSearchSource = class extends BaseSearchSource {
6844
+ var CommandSearchSource = class extends BaseSearchSourceSync {
6701
6845
  constructor(channel, options) {
6702
6846
  super(options);
6703
6847
  this.type = "commands";
@@ -6741,10 +6885,10 @@ var CommandSearchSource = class extends BaseSearchSource {
6741
6885
  }
6742
6886
  return 0;
6743
6887
  });
6744
- return Promise.resolve({
6888
+ return {
6745
6889
  items: selectedCommands.map((c) => ({ ...c, id: c.name })),
6746
6890
  next: null
6747
- });
6891
+ };
6748
6892
  }
6749
6893
  filterQueryResults(items) {
6750
6894
  return items;
@@ -6764,9 +6908,21 @@ var createCommandsMiddleware = (channel, options) => {
6764
6908
  handlers: {
6765
6909
  onChange: ({ state, next, complete, forward }) => {
6766
6910
  if (!state.selection) return forward();
6911
+ const finalText = state.text.slice(0, state.selection.end);
6912
+ const commandName = getCompleteCommandInString(finalText);
6913
+ if (commandName) {
6914
+ const command = searchSource?.query(commandName).items[0];
6915
+ if (command) {
6916
+ return next({
6917
+ ...state,
6918
+ command,
6919
+ suggestions: void 0
6920
+ });
6921
+ }
6922
+ }
6767
6923
  const triggerWithToken = getTriggerCharWithToken({
6768
6924
  trigger: finalOptions.trigger,
6769
- text: state.text.slice(0, state.selection.end),
6925
+ text: finalText,
6770
6926
  acceptTrailingSpaces: false,
6771
6927
  isCommand: true
6772
6928
  });
@@ -6785,6 +6941,7 @@ var createCommandsMiddleware = (channel, options) => {
6785
6941
  }
6786
6942
  return complete({
6787
6943
  ...state,
6944
+ command: null,
6788
6945
  suggestions: {
6789
6946
  query: triggerWithToken.slice(1),
6790
6947
  searchSource,
@@ -6792,12 +6949,12 @@ var createCommandsMiddleware = (channel, options) => {
6792
6949
  }
6793
6950
  });
6794
6951
  },
6795
- onSuggestionItemSelect: ({ state, complete, forward }) => {
6952
+ onSuggestionItemSelect: ({ state, next, forward }) => {
6796
6953
  const { selectedSuggestion } = state.change ?? {};
6797
6954
  if (!selectedSuggestion || state.suggestions?.trigger !== finalOptions.trigger)
6798
6955
  return forward();
6799
6956
  searchSource.resetStateAndActivate();
6800
- return complete({
6957
+ return next({
6801
6958
  ...state,
6802
6959
  ...insertItemWithTrigger({
6803
6960
  insertText: `/${selectedSuggestion.name} `,
@@ -6805,6 +6962,7 @@ var createCommandsMiddleware = (channel, options) => {
6805
6962
  text: state.text,
6806
6963
  trigger: finalOptions.trigger
6807
6964
  }),
6965
+ command: selectedSuggestion,
6808
6966
  suggestions: void 0
6809
6967
  });
6810
6968
  }
@@ -6812,6 +6970,45 @@ var createCommandsMiddleware = (channel, options) => {
6812
6970
  };
6813
6971
  };
6814
6972
 
6973
+ // src/messageComposer/middleware/textComposer/commandStringExtraction.ts
6974
+ var stripCommandFromText = (text, commandName) => text.replace(new RegExp(`^${escapeRegExp(`/${commandName}`)}\\s*`), "");
6975
+ var createCommandStringExtractionMiddleware = () => ({
6976
+ handlers: {
6977
+ onChange: ({ complete, forward, state }) => {
6978
+ const { command } = state;
6979
+ if (!command?.name) {
6980
+ return forward();
6981
+ }
6982
+ const newText = stripCommandFromText(state.text, command.name);
6983
+ return complete({
6984
+ ...state,
6985
+ selection: {
6986
+ end: newText.length,
6987
+ start: newText.length
6988
+ },
6989
+ text: newText
6990
+ });
6991
+ },
6992
+ onSuggestionItemSelect: ({ next, forward, state }) => {
6993
+ const { command } = state;
6994
+ if (!command) {
6995
+ return forward();
6996
+ }
6997
+ const triggerWithCommand = `/${command?.name} `;
6998
+ const newText = state.text.slice(triggerWithCommand.length);
6999
+ return next({
7000
+ ...state,
7001
+ selection: {
7002
+ end: newText.length,
7003
+ start: newText.length
7004
+ },
7005
+ text: newText
7006
+ });
7007
+ }
7008
+ },
7009
+ id: "stream-io/text-composer/command-string-extraction"
7010
+ });
7011
+
6815
7012
  // src/messageComposer/middleware/textComposer/mentions.ts
6816
7013
  var accentsMap = {
6817
7014
  a: "\xE1|\xE0|\xE3|\xE2|\xC0|\xC1|\xC3|\xC2",
@@ -7144,6 +7341,7 @@ var initState4 = ({
7144
7341
  if (!message) {
7145
7342
  const text2 = composer.config.text.defaultValue ?? "";
7146
7343
  return {
7344
+ command: null,
7147
7345
  mentionedUsers: [],
7148
7346
  text: text2,
7149
7347
  selection: { start: text2.length, end: text2.length }
@@ -7182,6 +7380,10 @@ var TextComposer = class {
7182
7380
  mentionedUsers.splice(existingUserIndex, 1);
7183
7381
  this.state.partialNext({ mentionedUsers });
7184
7382
  };
7383
+ this.setCommand = (command) => {
7384
+ if (command?.name === this.command?.name) return;
7385
+ this.state.partialNext({ command });
7386
+ };
7185
7387
  this.setText = (text) => {
7186
7388
  if (!this.enabled || text === this.text) return;
7187
7389
  this.state.partialNext({ text });
@@ -7191,7 +7393,10 @@ var TextComposer = class {
7191
7393
  if (!this.enabled || !selectionChanged) return;
7192
7394
  this.state.partialNext({ selection });
7193
7395
  };
7194
- this.insertText = ({ text, selection }) => {
7396
+ this.insertText = async ({
7397
+ text,
7398
+ selection
7399
+ }) => {
7195
7400
  if (!this.enabled) return;
7196
7401
  const finalSelection = selection ?? this.selection;
7197
7402
  const { maxLengthOnEdit } = this.composer.config.text ?? {};
@@ -7207,7 +7412,7 @@ var TextComposer = class {
7207
7412
  );
7208
7413
  const expectedCursorPosition = currentText.slice(0, finalSelection.start).length + text.length;
7209
7414
  const cursorPosition = expectedCursorPosition >= finalText.length ? finalText.length : currentText.slice(0, expectedCursorPosition).length;
7210
- this.state.partialNext({
7415
+ await this.handleChange({
7211
7416
  text: finalText,
7212
7417
  selection: {
7213
7418
  start: cursorPosition,
@@ -7327,6 +7532,9 @@ var TextComposer = class {
7327
7532
  this.composer.updateConfig({ text: { publishTypingEvents } });
7328
7533
  }
7329
7534
  // --- START STATE API ---
7535
+ get command() {
7536
+ return this.state.getLatestValue().command;
7537
+ }
7330
7538
  get mentionedUsers() {
7331
7539
  return this.state.getLatestValue().mentionedUsers;
7332
7540
  }
@@ -7345,6 +7553,9 @@ var TextComposer = class {
7345
7553
  setMentionedUsers(users) {
7346
7554
  this.state.partialNext({ mentionedUsers: users });
7347
7555
  }
7556
+ clearCommand() {
7557
+ this.state.partialNext({ command: null });
7558
+ }
7348
7559
  // --- END TEXT PROCESSING ----
7349
7560
  };
7350
7561
 
@@ -7902,7 +8113,33 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7902
8113
  this.editedMessage = message;
7903
8114
  }
7904
8115
  };
8116
+ this.initStateFromChannelResponse = (channelApiResponse) => {
8117
+ if (this.channel.cid !== channelApiResponse.channel.cid) {
8118
+ return;
8119
+ }
8120
+ if (channelApiResponse.draft) {
8121
+ this.initState({ composition: channelApiResponse.draft });
8122
+ } else if (this.state.getLatestValue().draftId) {
8123
+ this.clear();
8124
+ this.client.offlineDb?.executeQuerySafely(
8125
+ (db) => db.deleteDraft({
8126
+ cid: this.channel.cid,
8127
+ parent_id: void 0
8128
+ // makes sure that we don't delete thread drafts while upserting channels
8129
+ }),
8130
+ { method: "deleteDraft" }
8131
+ );
8132
+ }
8133
+ };
7905
8134
  this.initEditingAuditState = (composition) => initEditingAuditState(composition);
8135
+ this.registerDraftEventSubscriptions = () => {
8136
+ const unsubscribeDraftUpdated = this.subscribeDraftUpdated();
8137
+ const unsubscribeDraftDeleted = this.subscribeDraftDeleted();
8138
+ return () => {
8139
+ unsubscribeDraftUpdated();
8140
+ unsubscribeDraftDeleted();
8141
+ };
8142
+ };
7906
8143
  this.registerSubscriptions = () => {
7907
8144
  if (!this.hasSubscriptions) {
7908
8145
  this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
@@ -7967,13 +8204,13 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7967
8204
  }).unsubscribe;
7968
8205
  this.subscribeDraftUpdated = () => this.client.on("draft.updated", (event) => {
7969
8206
  const draft = event.draft;
7970
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid)
8207
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid)
7971
8208
  return;
7972
8209
  this.initState({ composition: draft });
7973
8210
  }).unsubscribe;
7974
8211
  this.subscribeDraftDeleted = () => this.client.on("draft.deleted", (event) => {
7975
8212
  const draft = event.draft;
7976
- if (!draft || !!draft.parent_id !== !!this.threadId || draft.channel_cid !== this.channel.cid) {
8213
+ if (!draft || (draft.parent_id ?? null) !== (this.threadId ?? null) || draft.channel_cid !== this.channel.cid) {
7977
8214
  return;
7978
8215
  }
7979
8216
  this.logDraftUpdateTimestamp();
@@ -8037,7 +8274,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8037
8274
  }
8038
8275
  });
8039
8276
  this.subscribeMessageComposerConfigStateChanged = () => {
8040
- let draftUnsubscribeFunctions;
8277
+ let draftUnsubscribeFunction;
8041
8278
  const unsubscribe = this.configState.subscribeWithSelector(
8042
8279
  (currentValue) => ({
8043
8280
  textDefaultValue: currentValue.text.defaultValue,
@@ -8050,19 +8287,16 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8050
8287
  selection: { start: 0, end: 0 }
8051
8288
  });
8052
8289
  }
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;
8290
+ if (draftsEnabled && !draftUnsubscribeFunction) {
8291
+ draftUnsubscribeFunction = this.registerDraftEventSubscriptions();
8292
+ } else if (!draftsEnabled && draftUnsubscribeFunction) {
8293
+ draftUnsubscribeFunction();
8294
+ draftUnsubscribeFunction = null;
8061
8295
  }
8062
8296
  }
8063
8297
  );
8064
8298
  return () => {
8065
- draftUnsubscribeFunctions?.forEach((unsubscribe2) => unsubscribe2());
8299
+ draftUnsubscribeFunction?.();
8066
8300
  unsubscribe();
8067
8301
  };
8068
8302
  };
@@ -8132,14 +8366,78 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8132
8366
  if (!composition) return;
8133
8367
  const { draft } = composition;
8134
8368
  this.state.partialNext({ draftId: draft.id });
8369
+ if (this.client.offlineDb) {
8370
+ try {
8371
+ const optimisticDraftResponse = {
8372
+ channel_cid: this.channel.cid,
8373
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
8374
+ message: draft,
8375
+ parent_id: draft.parent_id,
8376
+ quoted_message: this.quotedMessage ? unformatMessage(this.quotedMessage) : void 0
8377
+ };
8378
+ await this.client.offlineDb.upsertDraft({ draft: optimisticDraftResponse });
8379
+ } catch (error) {
8380
+ this.client.logger("error", `offlineDb:upsertDraft`, {
8381
+ tags: ["channel", "offlineDb"],
8382
+ error
8383
+ });
8384
+ }
8385
+ }
8135
8386
  this.logDraftUpdateTimestamp();
8136
8387
  await this.channel.createDraft(draft);
8137
8388
  };
8138
8389
  this.deleteDraft = async () => {
8139
8390
  if (this.editedMessage || !this.config.drafts.enabled || !this.draftId) return;
8140
8391
  this.state.partialNext({ draftId: null });
8392
+ const parentId = this.threadId ?? void 0;
8393
+ if (this.client.offlineDb) {
8394
+ try {
8395
+ await this.client.offlineDb.deleteDraft({
8396
+ cid: this.channel.cid,
8397
+ parent_id: parentId
8398
+ });
8399
+ } catch (error) {
8400
+ this.client.logger("error", `offlineDb:deleteDraft`, {
8401
+ tags: ["channel", "offlineDb"],
8402
+ error
8403
+ });
8404
+ }
8405
+ }
8141
8406
  this.logDraftUpdateTimestamp();
8142
- await this.channel.deleteDraft({ parent_id: this.threadId ?? void 0 });
8407
+ await this.channel.deleteDraft({ parent_id: parentId });
8408
+ };
8409
+ this.getDraft = async () => {
8410
+ if (this.editedMessage || !this.config.drafts.enabled || !this.client.userID) return;
8411
+ const draftFromOfflineDB = await this.client.offlineDb?.getDraft({
8412
+ cid: this.channel.cid,
8413
+ userId: this.client.userID,
8414
+ parent_id: this.threadId ?? void 0
8415
+ });
8416
+ if (draftFromOfflineDB) {
8417
+ this.initState({ composition: draftFromOfflineDB });
8418
+ }
8419
+ try {
8420
+ const response = await this.channel.getDraft({
8421
+ parent_id: this.threadId ?? void 0
8422
+ });
8423
+ const { draft } = response;
8424
+ if (!draft) return;
8425
+ this.client.offlineDb?.executeQuerySafely(
8426
+ (db) => db.upsertDraft({
8427
+ draft
8428
+ }),
8429
+ { method: "upsertDraft" }
8430
+ );
8431
+ this.initState({ composition: draft });
8432
+ } catch (error) {
8433
+ this.client.notifications.add({
8434
+ message: "Failed to get the draft",
8435
+ origin: {
8436
+ emitter: "MessageComposer",
8437
+ context: { composer: this }
8438
+ }
8439
+ });
8440
+ }
8143
8441
  };
8144
8442
  this.createPoll = async () => {
8145
8443
  const composition = await this.pollComposer.compose();
@@ -8268,6 +8566,9 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8268
8566
  return this.state.getLatestValue().showReplyInChannel;
8269
8567
  }
8270
8568
  get hasSendableData() {
8569
+ if (this.client.offlineDb) {
8570
+ return !this.compositionIsEmpty;
8571
+ }
8271
8572
  return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId);
8272
8573
  }
8273
8574
  get compositionIsEmpty() {
@@ -8583,6 +8884,17 @@ var Channel = class {
8583
8884
  updates
8584
8885
  );
8585
8886
  }
8887
+ /**
8888
+ * sendReaction - Sends a reaction to a message. If offline support is enabled, it will make sure
8889
+ * that sending the reaction is queued up if it fails due to bad internet conditions and executed
8890
+ * later.
8891
+ *
8892
+ * @param {string} messageID the message id
8893
+ * @param {Reaction} reaction the reaction object for instance {type: 'love'}
8894
+ * @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
8895
+ *
8896
+ * @return {Promise<ReactionAPIResponse>} The Server Response
8897
+ */
8586
8898
  async sendReaction(messageID, reaction, options) {
8587
8899
  if (!messageID) {
8588
8900
  throw Error(`Message id is missing`);
@@ -9435,9 +9747,7 @@ var Channel = class {
9435
9747
  };
9436
9748
  this.getClient().polls.hydratePollCache(state.messages, true);
9437
9749
  this.getClient().reminders.hydrateState(state.messages);
9438
- if (state.draft) {
9439
- this.messageComposer.initState({ composition: state.draft });
9440
- }
9750
+ this.messageComposer.initStateFromChannelResponse(state);
9441
9751
  const areCapabilitiesChanged = [...state.channel.own_capabilities || []].sort().join() !== [
9442
9752
  ...this.data && Array.isArray(this.data?.own_capabilities) ? this.data.own_capabilities : []
9443
9753
  ].sort().join();
@@ -9564,13 +9874,11 @@ var Channel = class {
9564
9874
  /**
9565
9875
  * createDraft - Creates or updates a draft message in a channel
9566
9876
  *
9567
- * @param {string} channelType The channel type
9568
- * @param {string} channelID The channel ID
9569
9877
  * @param {DraftMessagePayload} message The draft message to create or update
9570
9878
  *
9571
9879
  * @return {Promise<CreateDraftResponse>} Response containing the created draft
9572
9880
  */
9573
- async createDraft(message) {
9881
+ async _createDraft(message) {
9574
9882
  return await this.getClient().post(
9575
9883
  this._channelURL() + "/draft",
9576
9884
  {
@@ -9579,18 +9887,82 @@ var Channel = class {
9579
9887
  );
9580
9888
  }
9581
9889
  /**
9582
- * deleteDraft - Deletes a draft message from a channel
9890
+ * createDraft - Creates or updates a draft message in a channel. If offline support is
9891
+ * enabled, it will make sure that creating the draft is queued up if it fails due to
9892
+ * bad internet conditions and executed later.
9893
+ *
9894
+ * @param {DraftMessagePayload} message The draft message to create or update
9895
+ *
9896
+ * @return {Promise<CreateDraftResponse>} Response containing the created draft
9897
+ */
9898
+ async createDraft(message) {
9899
+ try {
9900
+ const offlineDb = this.getClient().offlineDb;
9901
+ if (offlineDb) {
9902
+ return await offlineDb.queueTask({
9903
+ task: {
9904
+ channelId: this.id,
9905
+ channelType: this.type,
9906
+ threadId: message.parent_id,
9907
+ payload: [message],
9908
+ type: "create-draft"
9909
+ }
9910
+ });
9911
+ }
9912
+ } catch (error) {
9913
+ this._client.logger("error", `offlineDb:create-draft`, {
9914
+ tags: ["channel", "offlineDb"],
9915
+ error
9916
+ });
9917
+ }
9918
+ return this._createDraft(message);
9919
+ }
9920
+ /**
9921
+ * deleteDraft - Deletes a draft message from a channel or a thread.
9583
9922
  *
9584
9923
  * @param {Object} options
9585
9924
  * @param {string} options.parent_id Optional parent message ID for drafts in threads
9586
9925
  *
9587
9926
  * @return {Promise<APIResponse>} API response
9588
9927
  */
9589
- async deleteDraft({ parent_id } = {}) {
9928
+ async _deleteDraft({ parent_id } = {}) {
9590
9929
  return await this.getClient().delete(this._channelURL() + "/draft", {
9591
9930
  parent_id
9592
9931
  });
9593
9932
  }
9933
+ /**
9934
+ * deleteDraft - Deletes a draft message from a channel or a thread. If offline support is
9935
+ * enabled, it will make sure that deleting the draft is queued up if it fails due to
9936
+ * bad internet conditions and executed later.
9937
+ *
9938
+ * @param {Object} options
9939
+ * @param {string} options.parent_id Optional parent message ID for drafts in threads
9940
+ *
9941
+ * @return {Promise<APIResponse>} API response
9942
+ */
9943
+ async deleteDraft(options = {}) {
9944
+ const { parent_id } = options;
9945
+ try {
9946
+ const offlineDb = this.getClient().offlineDb;
9947
+ if (offlineDb) {
9948
+ return await offlineDb.queueTask({
9949
+ task: {
9950
+ channelId: this.id,
9951
+ channelType: this.type,
9952
+ threadId: parent_id,
9953
+ payload: [options],
9954
+ type: "delete-draft"
9955
+ }
9956
+ });
9957
+ }
9958
+ } catch (error) {
9959
+ this._client.logger("error", `offlineDb:delete-draft`, {
9960
+ tags: ["channel", "offlineDb"],
9961
+ error
9962
+ });
9963
+ }
9964
+ return this._deleteDraft(options);
9965
+ }
9594
9966
  /**
9595
9967
  * getDraft - Retrieves a draft message from a channel
9596
9968
  *
@@ -12744,6 +13116,20 @@ var ReminderPaginator = class extends BasePaginator {
12744
13116
  this.filterQueryResults = (items) => items;
12745
13117
  this.client = client;
12746
13118
  }
13119
+ get filters() {
13120
+ return this._filters;
13121
+ }
13122
+ get sort() {
13123
+ return this._sort;
13124
+ }
13125
+ set filters(filters) {
13126
+ this._filters = filters;
13127
+ this.resetState();
13128
+ }
13129
+ set sort(sort) {
13130
+ this._sort = sort;
13131
+ this.resetState();
13132
+ }
12747
13133
  };
12748
13134
 
12749
13135
  // src/reminders/ReminderManager.ts
@@ -12758,7 +13144,8 @@ var DEFAULT_REMINDER_MANAGER_CONFIG = {
12758
13144
  2 * oneHour2,
12759
13145
  8 * oneHour2,
12760
13146
  oneDay2
12761
- ]
13147
+ ],
13148
+ stopTimerRefreshBoundaryMs: DEFAULT_STOP_REFRESH_BOUNDARY_MS
12762
13149
  };
12763
13150
  var isReminderExistsError = (error) => error.message.match("already has reminder created for this message_id");
12764
13151
  var isReminderDoesNotExistError = (error) => error.message.match("reminder does not exist");
@@ -12909,13 +13296,19 @@ var _ReminderManager = class _ReminderManager extends WithSubscriptions {
12909
13296
  };
12910
13297
  this.client = client;
12911
13298
  this.configState = new StateStore({
12912
- scheduledOffsetsMs: config?.scheduledOffsetsMs ?? DEFAULT_REMINDER_MANAGER_CONFIG.scheduledOffsetsMs
13299
+ scheduledOffsetsMs: config?.scheduledOffsetsMs ?? DEFAULT_REMINDER_MANAGER_CONFIG.scheduledOffsetsMs,
13300
+ stopTimerRefreshBoundaryMs: config?.stopTimerRefreshBoundaryMs ?? DEFAULT_REMINDER_MANAGER_CONFIG.stopTimerRefreshBoundaryMs
12913
13301
  });
12914
13302
  this.state = new StateStore({ reminders: /* @__PURE__ */ new Map() });
12915
13303
  this.paginator = new ReminderPaginator(client);
12916
13304
  }
12917
13305
  // Config API START //
12918
13306
  updateConfig(config) {
13307
+ if (typeof config.stopTimerRefreshBoundaryMs === "number" && config.stopTimerRefreshBoundaryMs !== this.stopTimerRefreshBoundaryMs) {
13308
+ this.reminders.forEach((reminder) => {
13309
+ reminder.timer.stopRefreshBoundaryMs = config?.stopTimerRefreshBoundaryMs;
13310
+ });
13311
+ }
12919
13312
  this.configState.partialNext(config);
12920
13313
  }
12921
13314
  get stopTimerRefreshBoundaryMs() {
@@ -14240,9 +14633,7 @@ var StreamChat = class _StreamChat {
14240
14633
  this.polls.hydratePollCache(channelState.messages, true);
14241
14634
  this.reminders.hydrateState(channelState.messages);
14242
14635
  }
14243
- if (channelState.draft) {
14244
- c.messageComposer.initState({ composition: channelState.draft });
14245
- }
14636
+ c.messageComposer.initStateFromChannelResponse(channelState);
14246
14637
  channels.push(c);
14247
14638
  }
14248
14639
  return channels;
@@ -15159,7 +15550,7 @@ var StreamChat = class _StreamChat {
15159
15550
  if (this.userAgent) {
15160
15551
  return this.userAgent;
15161
15552
  }
15162
- const version = "9.6.1";
15553
+ const version = "9.8.0";
15163
15554
  const clientBundle = "browser-cjs";
15164
15555
  let userAgentString = "";
15165
15556
  if (this.sdkIdentifier) {
@@ -16248,6 +16639,52 @@ var StreamChat = class _StreamChat {
16248
16639
  ...rest
16249
16640
  });
16250
16641
  }
16642
+ /**
16643
+ * uploadFile - Uploads a file to the configured storage (defaults to Stream CDN)
16644
+ *
16645
+ * @param {string|NodeJS.ReadableStream|Buffer|File} uri The file to upload
16646
+ * @param {string} [name] The name of the file
16647
+ * @param {string} [contentType] The content type of the file
16648
+ * @param {UserResponse} [user] Optional user information
16649
+ *
16650
+ * @return {Promise<SendFileAPIResponse>} Response containing the file URL
16651
+ */
16652
+ uploadFile(uri, name, contentType, user) {
16653
+ return this.sendFile(`${this.baseURL}/uploads/file`, uri, name, contentType, user);
16654
+ }
16655
+ /**
16656
+ * uploadImage - Uploads an image to the configured storage (defaults to Stream CDN)
16657
+ *
16658
+ * @param {string|NodeJS.ReadableStream|File} uri The image to upload
16659
+ * @param {string} [name] The name of the image
16660
+ * @param {string} [contentType] The content type of the image
16661
+ * @param {UserResponse} [user] Optional user information
16662
+ *
16663
+ * @return {Promise<SendFileAPIResponse>} Response containing the image URL
16664
+ */
16665
+ uploadImage(uri, name, contentType, user) {
16666
+ return this.sendFile(`${this.baseURL}/uploads/image`, uri, name, contentType, user);
16667
+ }
16668
+ /**
16669
+ * deleteFile - Deletes a file from the configured storage
16670
+ *
16671
+ * @param {string} url The URL of the file to delete
16672
+ *
16673
+ * @return {Promise<APIResponse>} The server response
16674
+ */
16675
+ deleteFile(url) {
16676
+ return this.delete(`${this.baseURL}/uploads/file`, { url });
16677
+ }
16678
+ /**
16679
+ * deleteImage - Deletes an image from the configured storage
16680
+ *
16681
+ * @param {string} url The URL of the image to delete
16682
+ *
16683
+ * @return {Promise<APIResponse>} The server response
16684
+ */
16685
+ deleteImage(url) {
16686
+ return this.delete(`${this.baseURL}/uploads/image`, { url });
16687
+ }
16251
16688
  };
16252
16689
 
16253
16690
  // src/events.ts
@@ -16956,6 +17393,35 @@ var AbstractOfflineDB = class {
16956
17393
  (executeOverride) => reactionMethod({ message, reaction, execute: executeOverride })
16957
17394
  );
16958
17395
  };
17396
+ /**
17397
+ * A utility handler for all draft events:
17398
+ * - draft.updated -> updateDraft
17399
+ * - draft.deleted -> deleteDraft
17400
+ * @param event - the WS event we are trying to process
17401
+ * @param execute - whether to immediately execute the operation.
17402
+ */
17403
+ this.handleDraftEvent = async ({
17404
+ event,
17405
+ execute = true
17406
+ }) => {
17407
+ const { cid, draft, type } = event;
17408
+ if (!draft) return [];
17409
+ if (type === "draft.updated") {
17410
+ return await this.upsertDraft({
17411
+ draft,
17412
+ execute
17413
+ });
17414
+ }
17415
+ if (type === "draft.deleted") {
17416
+ if (!cid) return [];
17417
+ return await this.deleteDraft({
17418
+ cid,
17419
+ parent_id: draft.parent_id,
17420
+ execute
17421
+ });
17422
+ }
17423
+ return [];
17424
+ };
16959
17425
  /**
16960
17426
  * A generic event handler that decides which DB API to invoke based on
16961
17427
  * event.type for all events we are currently handling. It is used to both
@@ -16992,6 +17458,9 @@ var AbstractOfflineDB = class {
16992
17458
  if (type === "channel.hidden" || type === "channel.visible") {
16993
17459
  return await this.handleChannelVisibilityEvent({ event, execute });
16994
17460
  }
17461
+ if (type === "draft.updated" || type === "draft.deleted") {
17462
+ return await this.handleDraftEvent({ event, execute });
17463
+ }
16995
17464
  if ((type === "channel.updated" || type === "notification.message_new" || type === "notification.added_to_channel") && channel) {
16996
17465
  return await this.upsertChannelData({ channel, execute });
16997
17466
  }
@@ -17066,6 +17535,12 @@ var AbstractOfflineDB = class {
17066
17535
  if (task.type === "delete-reaction") {
17067
17536
  return await channel._deleteReaction(...task.payload);
17068
17537
  }
17538
+ if (task.type === "create-draft") {
17539
+ return await channel._createDraft(...task.payload);
17540
+ }
17541
+ if (task.type === "delete-draft") {
17542
+ return await channel._deleteDraft(...task.payload);
17543
+ }
17069
17544
  if (task.type === "send-message") {
17070
17545
  const newMessageResponse = await channel._sendMessage(...task.payload);
17071
17546
  const newMessage = newMessageResponse?.message;