stream-chat 9.50.3 → 9.52.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 (57) hide show
  1. package/dist/cjs/index.browser.js +601 -275
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +610 -276
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +599 -273
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/channel.d.ts +4 -3
  8. package/dist/types/client.d.ts +23 -9
  9. package/dist/types/index.d.ts +7 -0
  10. package/dist/types/messageComposer/attachmentIdentity.d.ts +4 -0
  11. package/dist/types/messageComposer/attachmentManager.d.ts +8 -0
  12. package/dist/types/messageComposer/messageComposer.d.ts +13 -0
  13. package/dist/types/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.d.ts +20 -1
  14. package/dist/types/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.d.ts +6 -1
  15. package/dist/types/messageComposer/middleware/messageComposer/attachments.d.ts +24 -0
  16. package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +22 -1
  17. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +15 -9
  18. package/dist/types/messageComposer/types.d.ts +5 -0
  19. package/dist/types/middleware.d.ts +8 -0
  20. package/dist/types/offline-support/offline_support_api.d.ts +7 -1
  21. package/dist/types/offline-support/offline_sync_manager.d.ts +12 -1
  22. package/dist/types/search/BaseSearchSource.d.ts +42 -24
  23. package/dist/types/search/ChannelMemberSearchSource.d.ts +9 -4
  24. package/dist/types/search/ChannelSearchSource.d.ts +9 -3
  25. package/dist/types/search/MessageSearchSource.d.ts +14 -3
  26. package/dist/types/search/UserSearchSource.d.ts +9 -3
  27. package/dist/types/search/types.d.ts +14 -1
  28. package/dist/types/types.d.ts +12 -1
  29. package/dist/types/uploadManager.d.ts +30 -0
  30. package/dist/types/utils.d.ts +21 -2
  31. package/package.json +7 -7
  32. package/src/channel.ts +20 -8
  33. package/src/client.ts +71 -13
  34. package/src/index.ts +12 -0
  35. package/src/messageComposer/attachmentIdentity.ts +27 -0
  36. package/src/messageComposer/attachmentManager.ts +61 -12
  37. package/src/messageComposer/messageComposer.ts +43 -8
  38. package/src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts +1 -1
  39. package/src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts +58 -33
  40. package/src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts +4 -0
  41. package/src/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.ts +9 -0
  42. package/src/messageComposer/middleware/messageComposer/attachments.ts +109 -0
  43. package/src/messageComposer/middleware/messageComposer/types.ts +22 -1
  44. package/src/messageComposer/middleware/textComposer/mentions.ts +75 -23
  45. package/src/messageComposer/types.ts +5 -0
  46. package/src/middleware.ts +11 -0
  47. package/src/offline-support/offline_support_api.ts +21 -2
  48. package/src/offline-support/offline_sync_manager.ts +36 -8
  49. package/src/search/BaseSearchSource.ts +123 -39
  50. package/src/search/ChannelMemberSearchSource.ts +22 -11
  51. package/src/search/ChannelSearchSource.ts +16 -5
  52. package/src/search/MessageSearchSource.ts +36 -4
  53. package/src/search/UserSearchSource.ts +16 -5
  54. package/src/search/types.ts +14 -1
  55. package/src/types.ts +13 -1
  56. package/src/uploadManager.ts +45 -0
  57. package/src/utils.ts +78 -3
@@ -158,6 +158,7 @@ __export(index_exports, {
158
158
  createMessageComposerStateCompositionMiddleware: () => createMessageComposerStateCompositionMiddleware,
159
159
  createPollComposerStateMiddleware: () => createPollComposerStateMiddleware,
160
160
  createPostUploadAttachmentEnrichmentMiddleware: () => createPostUploadAttachmentEnrichmentMiddleware,
161
+ createSendWithPendingUploadsAttachmentsMiddleware: () => createSendWithPendingUploadsAttachmentsMiddleware,
161
162
  createSharedLocationCompositionMiddleware: () => createSharedLocationCompositionMiddleware,
162
163
  createTextComposerCompositionMiddleware: () => createTextComposerCompositionMiddleware,
163
164
  createTextComposerPreValidationMiddleware: () => createTextComposerPreValidationMiddleware,
@@ -186,6 +187,7 @@ __export(index_exports, {
186
187
  getTokenizedSuggestionDisplayName: () => getTokenizedSuggestionDisplayName,
187
188
  getTriggerCharWithToken: () => getTriggerCharWithToken,
188
189
  gunzipPayload: () => gunzipPayload,
190
+ hasPending: () => hasPending,
189
191
  insertItemWithTrigger: () => insertItemWithTrigger,
190
192
  isAudioAttachment: () => isAudioAttachment,
191
193
  isBlobButNotFile: () => isBlobButNotFile,
@@ -193,6 +195,7 @@ __export(index_exports, {
193
195
  isFileAttachment: () => isFileAttachment,
194
196
  isFileList: () => isFileList,
195
197
  isFileReference: () => isFileReference,
198
+ isFinishedUpload: () => isFinishedUpload,
196
199
  isGiphyAttachment: () => isGiphyAttachment,
197
200
  isImageAttachment: () => isImageAttachment,
198
201
  isImageFile: () => isImageFile,
@@ -205,9 +208,11 @@ __export(index_exports, {
205
208
  isLocalVoiceRecordingAttachment: () => isLocalVoiceRecordingAttachment,
206
209
  isOwnUser: () => isOwnUser,
207
210
  isPatch: () => isPatch,
211
+ isPendingUpload: () => isPendingUpload,
208
212
  isScrapedContent: () => isScrapedContent,
209
213
  isSharedLocationResponse: () => isSharedLocationResponse,
210
214
  isTargetedOptionTextUpdate: () => isTargetedOptionTextUpdate,
215
+ isUploadCancellation: () => isUploadCancellation,
211
216
  isUploadedAttachment: () => isUploadedAttachment,
212
217
  isVideoAttachment: () => isVideoAttachment,
213
218
  isVoiceRecordingAttachment: () => isVoiceRecordingAttachment,
@@ -227,12 +232,15 @@ __export(index_exports, {
227
232
  readFileAsArrayBuffer: () => readFileAsArrayBuffer,
228
233
  removeDiacritics: () => removeDiacritics,
229
234
  replaceWordWithEntity: () => replaceWordWithEntity,
235
+ settled: () => settled,
230
236
  stripCommandFromText: () => stripCommandFromText,
231
237
  stripMentionTokens: () => stripMentionTokens,
232
238
  textIsEmpty: () => textIsEmpty,
233
239
  timeLeftMs: () => timeLeftMs,
234
240
  verifyAndParseWebhook: () => verifyAndParseWebhook,
235
- verifySignature: () => verifySignature
241
+ verifySignature: () => verifySignature,
242
+ withCancellation: () => withCancellation,
243
+ withoutConcurrency: () => withoutConcurrency
236
244
  });
237
245
  module.exports = __toCommonJS(index_exports);
238
246
 
@@ -499,7 +507,7 @@ var ChannelBatchUpdater = class {
499
507
  };
500
508
 
501
509
  // src/client.ts
502
- var import_axios3 = __toESM(require("axios"));
510
+ var import_axios4 = __toESM(require("axios"));
503
511
  var import_https = __toESM(require("https"));
504
512
 
505
513
  // src/utils.ts
@@ -759,6 +767,38 @@ function unformatMessage(message) {
759
767
  quoted_message: toMessageResponseBase(message.quoted_message)
760
768
  };
761
769
  }
770
+ var isRemoteUrl = (url) => !!url && /^https?:\/\//i.test(url.trim());
771
+ var sanitizeOutgoingAttachments = ({
772
+ client,
773
+ message
774
+ }) => {
775
+ const attachments = message.attachments;
776
+ if (!attachments?.length) return message;
777
+ const unresolved = [];
778
+ let changed = false;
779
+ const sanitized = attachments.reduce((acc, attachment) => {
780
+ const { localMetadata, ...rest } = attachment;
781
+ if (!localMetadata) {
782
+ acc.push(attachment);
783
+ return acc;
784
+ }
785
+ changed = true;
786
+ const hasRemoteSource = isRemoteUrl(rest.asset_url) || isRemoteUrl(rest.image_url) || isRemoteUrl(rest.og_scrape_url) || isRemoteUrl(rest.title_link);
787
+ if (hasRemoteSource) {
788
+ acc.push(rest);
789
+ } else {
790
+ unresolved.push(rest);
791
+ }
792
+ return acc;
793
+ }, []);
794
+ if (unresolved.length) {
795
+ const text = `Dropped ${unresolved.length} attachment(s) whose upload never completed. Composing with pending uploads requires awaiting them before sending.`;
796
+ const extraData = { attachments: unresolved, message, tags: ["attachments"] };
797
+ client.logger("warn", text, extraData);
798
+ console.warn(text, extraData);
799
+ }
800
+ return changed ? { ...message, attachments: sanitized } : message;
801
+ };
762
802
  var localMessageToNewMessagePayload = (localMessage) => {
763
803
  const {
764
804
  // Remove all timestamp fields and client-specific fields.
@@ -967,7 +1007,8 @@ var debounce = (fn, timeout = 0, { leading = false, trailing = true } = {}) => {
967
1007
  }
968
1008
  runningTimeout = null;
969
1009
  };
970
- runningTimeout = setTimeout(timeoutHandler, timeout);
1010
+ const delay = typeof timeout === "function" ? timeout(...args) : timeout;
1011
+ runningTimeout = setTimeout(timeoutHandler, delay);
971
1012
  return lastResult;
972
1013
  };
973
1014
  debouncedFn.cancel = () => {
@@ -2530,6 +2571,12 @@ var CooldownTimer = class extends WithSubscriptions {
2530
2571
  var isScrapedContent = (attachment) => !!attachment?.og_scrape_url || !!attachment?.title_link;
2531
2572
  var isLocalAttachment = (attachment) => !!attachment?.localMetadata?.id;
2532
2573
  var isLocalUploadAttachment = (attachment) => !!attachment?.localMetadata?.uploadState;
2574
+ var PENDING_UPLOAD_STATES = [
2575
+ "pending",
2576
+ "uploading"
2577
+ ];
2578
+ var isPendingUpload = (attachment) => isLocalUploadAttachment(attachment) && PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
2579
+ var isFinishedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "finished";
2533
2580
  var isFileAttachment = (attachment, supportedVideoFormat = []) => attachment.type === "file" || !!(attachment.mime_type && supportedVideoFormat.indexOf(attachment.mime_type) === -1 && attachment.type !== "video");
2534
2581
  var isLocalFileAttachment = (attachment) => isFileAttachment(attachment) && isLocalAttachment(attachment);
2535
2582
  var isImageAttachment = (attachment) => attachment.type === "image" && !isScrapedContent(attachment);
@@ -2605,7 +2652,7 @@ var ensureIsLocalAttachment = (attachment) => {
2605
2652
  };
2606
2653
 
2607
2654
  // src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts
2608
- var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2655
+ var createPostUploadAttachmentEnrichmentMiddleware = (composer) => ({
2609
2656
  id: "stream-io/attachment-manager-middleware/post-upload-enrichment",
2610
2657
  handlers: {
2611
2658
  postProcess: ({
@@ -2618,9 +2665,10 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2618
2665
  if (error) return forward();
2619
2666
  if (!attachment || !response) return discard();
2620
2667
  const enrichedAttachment = { ...attachment };
2621
- const previewUri = attachment.localMetadata.previewUri;
2622
- if (previewUri) {
2623
- if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
2668
+ const { id, previewUri } = attachment.localMetadata;
2669
+ const attachmentIsStillInComposer = !composer || !!composer.attachmentManager.attachmentsById[id];
2670
+ if (previewUri && attachmentIsStillInComposer) {
2671
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
2624
2672
  delete enrichedAttachment.localMetadata.previewUri;
2625
2673
  }
2626
2674
  if (isLocalImageAttachment(attachment)) {
@@ -2643,6 +2691,12 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2643
2691
  var withoutConcurrency = createRunner(wrapWithContinuationTracking);
2644
2692
  var withCancellation = createRunner(wrapWithCancellation);
2645
2693
  var pendingPromises = /* @__PURE__ */ new Map();
2694
+ function hasPending(tag) {
2695
+ return pendingPromises.has(tag);
2696
+ }
2697
+ async function settled(tag) {
2698
+ await pendingPromises.get(tag)?.promise;
2699
+ }
2646
2700
  function createRunner(wrapper) {
2647
2701
  return function run(tag, cb) {
2648
2702
  const { cb: wrapped, onContinued } = wrapper(tag, cb);
@@ -2685,6 +2739,16 @@ var MiddlewareExecutor = class {
2685
2739
  this.middleware = [];
2686
2740
  this.id = generateUUIDv4();
2687
2741
  }
2742
+ /**
2743
+ * The middleware currently installed, in execution order.
2744
+ *
2745
+ * Read-only: use `use` / `insert` / `replace` / `setOrder` / `remove` to change the chain. It
2746
+ * exists so components can ask what the chain is capable of - a middleware may declare
2747
+ * behaviour its host has to account for, and the host has no other way to find out.
2748
+ */
2749
+ get installedMiddleware() {
2750
+ return this.middleware;
2751
+ }
2688
2752
  use(middleware) {
2689
2753
  this.middleware = this.middleware.concat(middleware);
2690
2754
  return this;
@@ -2788,6 +2852,142 @@ var MiddlewareExecutor = class {
2788
2852
  }
2789
2853
  };
2790
2854
 
2855
+ // src/uploadManager.ts
2856
+ var import_axios = __toESM(require("axios"));
2857
+ var isUploadCancellation = (error) => import_axios.default.isCancel(error) || error instanceof Error && error.name === "AbortError";
2858
+ var initState = () => ({ uploads: {} });
2859
+ var upsertById = (uploads, record) => ({
2860
+ ...uploads,
2861
+ [record.id]: { ...uploads[record.id], ...record }
2862
+ });
2863
+ var updateById = (uploads, record) => {
2864
+ if (!(record.id in uploads)) return null;
2865
+ const current = uploads[record.id];
2866
+ return { ...uploads, [record.id]: { ...current, ...record } };
2867
+ };
2868
+ var UploadManager = class {
2869
+ constructor(client) {
2870
+ this.client = client;
2871
+ this.inFlightUploads = /* @__PURE__ */ new Map();
2872
+ this.getUpload = (id) => this.uploads[id];
2873
+ /**
2874
+ * Clears all upload records.
2875
+ * Invoked when the user disconnects so a later session does not inherit stale upload state.
2876
+ * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
2877
+ */
2878
+ this.reset = () => {
2879
+ for (const { abortController } of this.inFlightUploads.values()) {
2880
+ abortController.abort();
2881
+ }
2882
+ this.inFlightUploads.clear();
2883
+ this.state.next(initState());
2884
+ };
2885
+ /**
2886
+ * Removes the upload record for `id` if present.
2887
+ * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
2888
+ */
2889
+ this.deleteUploadRecord = (id) => {
2890
+ const flight = this.inFlightUploads.get(id);
2891
+ if (flight) {
2892
+ this.inFlightUploads.delete(id);
2893
+ flight.abortController.abort();
2894
+ }
2895
+ this.state.next((current) => {
2896
+ if (!(id in current.uploads)) return current;
2897
+ const uploads = { ...current.uploads };
2898
+ delete uploads[id];
2899
+ return { ...current, uploads };
2900
+ });
2901
+ };
2902
+ /**
2903
+ * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
2904
+ * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
2905
+ * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
2906
+ */
2907
+ this.upload = ({
2908
+ id,
2909
+ channelCid,
2910
+ file
2911
+ }) => {
2912
+ const existing = this.inFlightUploads.get(id);
2913
+ if (existing) return existing.promise;
2914
+ let resolvePromise;
2915
+ let rejectPromise;
2916
+ const promise = new Promise((resolve, reject) => {
2917
+ resolvePromise = resolve;
2918
+ rejectPromise = reject;
2919
+ });
2920
+ const abortController = new AbortController();
2921
+ this.inFlightUploads.set(id, { promise, abortController });
2922
+ void (async () => {
2923
+ const attachmentManager = this.resolveAttachmentManager(channelCid);
2924
+ const trackProgress = attachmentManager.config.trackUploadProgress;
2925
+ try {
2926
+ this.upsertUpload({
2927
+ uploadConfirmationPending: false,
2928
+ id,
2929
+ uploadProgress: trackProgress ? 0 : void 0
2930
+ });
2931
+ const onProgress = trackProgress ? (progress) => {
2932
+ this.updateUpload({
2933
+ id,
2934
+ uploadProgress: progress,
2935
+ // Only a number says anything about the flush, so a report without one leaves
2936
+ // the flag alone: `undefined` means the transport cannot measure this upload,
2937
+ // not that bytes were un-sent. Lowering it there would drop a UI that had
2938
+ // already gone indeterminate back to a determinate one with nothing to show.
2939
+ //
2940
+ // The record is removed in the `finally` below, so a record that still exists
2941
+ // while reporting 100% can only mean "flushed, awaiting the response".
2942
+ ...typeof progress === "number" ? { uploadConfirmationPending: progress >= 100 } : {}
2943
+ });
2944
+ } : void 0;
2945
+ const uploadRequestOptions = {
2946
+ abortSignal: abortController.signal,
2947
+ ...onProgress ? { onProgress } : {}
2948
+ };
2949
+ const response = await attachmentManager.doUploadRequest(
2950
+ file,
2951
+ uploadRequestOptions
2952
+ );
2953
+ resolvePromise(response);
2954
+ } catch (error) {
2955
+ rejectPromise(error);
2956
+ } finally {
2957
+ this.inFlightUploads.delete(id);
2958
+ this.deleteUploadRecord(id);
2959
+ }
2960
+ })();
2961
+ return promise;
2962
+ };
2963
+ this.upsertUpload = (record) => {
2964
+ this.state.partialNext({
2965
+ uploads: upsertById(this.uploads, record)
2966
+ });
2967
+ };
2968
+ this.updateUpload = (record) => {
2969
+ this.state.next((current) => {
2970
+ const nextUploads = updateById(current.uploads, record);
2971
+ if (!nextUploads) return current;
2972
+ return { ...current, uploads: nextUploads };
2973
+ });
2974
+ };
2975
+ this.state = new StateStore(initState());
2976
+ }
2977
+ resolveAttachmentManager(channelCid) {
2978
+ const colon = channelCid.indexOf(":");
2979
+ if (colon <= 0 || colon === channelCid.length - 1) {
2980
+ throw new Error(`Invalid channelCid: ${channelCid}`);
2981
+ }
2982
+ const channelType = channelCid.slice(0, colon);
2983
+ const channelId = channelCid.slice(colon + 1);
2984
+ return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
2985
+ }
2986
+ get uploads() {
2987
+ return this.state.getLatestValue().uploads;
2988
+ }
2989
+ };
2990
+
2791
2991
  // src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts
2792
2992
  var createUploadErrorHandlerMiddleware = (composer) => ({
2793
2993
  id: "stream-io/attachment-manager-middleware/upload-error",
@@ -2800,6 +3000,7 @@ var createUploadErrorHandlerMiddleware = (composer) => ({
2800
3000
  const { attachment, error } = state;
2801
3001
  if (!error) return forward();
2802
3002
  if (!attachment) return discard();
3003
+ if (isUploadCancellation(error)) return forward();
2803
3004
  const reason = error instanceof Error ? error.message : "unknown error";
2804
3005
  composer.client.notifications.addError({
2805
3006
  message: "Error uploading attachment",
@@ -2824,7 +3025,7 @@ var AttachmentPostUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2824
3025
  super();
2825
3026
  this.use([
2826
3027
  createUploadErrorHandlerMiddleware(composer),
2827
- createPostUploadAttachmentEnrichmentMiddleware()
3028
+ createPostUploadAttachmentEnrichmentMiddleware(composer)
2828
3029
  ]);
2829
3030
  }
2830
3031
  };
@@ -3272,7 +3473,7 @@ function mergeWithDiff(target, source, customizer) {
3272
3473
  }
3273
3474
 
3274
3475
  // src/messageComposer/attachmentManager.ts
3275
- var initState = ({
3476
+ var initState2 = ({
3276
3477
  message
3277
3478
  }) => ({
3278
3479
  attachments: (message?.attachments ?? [])?.filter(({ og_scrape_url }) => !og_scrape_url).map((att) => {
@@ -3300,13 +3501,14 @@ var _AttachmentManager = class _AttachmentManager {
3300
3501
  ...attachment,
3301
3502
  localMetadata: {
3302
3503
  ...attachment.localMetadata,
3504
+ uploadConfirmationPending: false,
3303
3505
  uploadProgress: void 0,
3304
3506
  uploadState: "failed"
3305
3507
  }
3306
3508
  };
3307
3509
  };
3308
3510
  this.initState = ({ message } = {}) => {
3309
- this.state.next(initState({ message }));
3511
+ this.state.next(initState2({ message }));
3310
3512
  };
3311
3513
  this.getSnapshot = () => {
3312
3514
  const state = this.state.getLatestValue();
@@ -3383,8 +3585,22 @@ var _AttachmentManager = class _AttachmentManager {
3383
3585
  this.state.partialNext({ attachments });
3384
3586
  }
3385
3587
  };
3588
+ /**
3589
+ * Releases a local preview URL the SDK minted, if it minted one.
3590
+ *
3591
+ * Only `blob:` URLs are ours to revoke: `toLocalUploadAttachment` puts a
3592
+ * `URL.createObjectURL` result in `previewUri` for a `File`, but for a React Native
3593
+ * `FileReference` it stores the caller's own native file URI, which must be left alone.
3594
+ */
3595
+ this.revokePreviewUri = (attachment) => {
3596
+ const previewUri = attachment.localMetadata?.previewUri;
3597
+ if (previewUri?.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
3598
+ };
3386
3599
  this.removeAttachments = (localAttachmentIds) => {
3387
3600
  if (!localAttachmentIds.length) return;
3601
+ const removedAttachments = this.attachments.filter(
3602
+ (attachment) => localAttachmentIds.includes(attachment.localMetadata?.id)
3603
+ );
3388
3604
  this.state.partialNext({
3389
3605
  attachments: this.attachments.filter(
3390
3606
  (attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id)
@@ -3393,6 +3609,7 @@ var _AttachmentManager = class _AttachmentManager {
3393
3609
  for (const id of localAttachmentIds) {
3394
3610
  this.client.uploadManager.deleteUploadRecord(id);
3395
3611
  }
3612
+ removedAttachments.forEach(this.revokePreviewUri);
3396
3613
  };
3397
3614
  this.getUploadConfigCheck = async (fileLike) => {
3398
3615
  const client = this.channel.getClient();
@@ -3544,21 +3761,27 @@ var _AttachmentManager = class _AttachmentManager {
3544
3761
  localMetadata: {
3545
3762
  ...attachment.localMetadata,
3546
3763
  uploadState: "failed",
3547
- uploadProgress: void 0
3764
+ uploadProgress: void 0,
3765
+ // `false`, not `undefined`: these updates go through `updateAttachment`, which merges
3766
+ // via `mergeWith` — and that skips `undefined` source values, so `undefined` would
3767
+ // leave a stale `true` in place.
3768
+ uploadConfirmationPending: false
3548
3769
  }
3549
3770
  };
3550
- this.client.notifications.addError({
3551
- message: "Error uploading attachment",
3552
- origin: {
3553
- emitter: "AttachmentManager",
3554
- context: { attachment, failedAttachment }
3555
- },
3556
- options: {
3557
- type: "api:attachment:upload:failed",
3558
- metadata: { reason },
3559
- originalError: error instanceof Error ? error : void 0
3560
- }
3561
- });
3771
+ if (!isUploadCancellation(error)) {
3772
+ this.client.notifications.addError({
3773
+ message: "Error uploading attachment",
3774
+ origin: {
3775
+ emitter: "AttachmentManager",
3776
+ context: { attachment, failedAttachment }
3777
+ },
3778
+ options: {
3779
+ type: "api:attachment:upload:failed",
3780
+ metadata: { reason },
3781
+ originalError: error instanceof Error ? error : void 0
3782
+ }
3783
+ });
3784
+ }
3562
3785
  this.updateAttachment(failedAttachment);
3563
3786
  return failedAttachment;
3564
3787
  }
@@ -3571,7 +3794,9 @@ var _AttachmentManager = class _AttachmentManager {
3571
3794
  localMetadata: {
3572
3795
  ...attachment.localMetadata,
3573
3796
  uploadState: "finished",
3574
- uploadProgress: void 0
3797
+ uploadProgress: void 0,
3798
+ // See the note in the failure branch: `undefined` cannot clear through `mergeWith`.
3799
+ uploadConfirmationPending: false
3575
3800
  }
3576
3801
  };
3577
3802
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3620,7 +3845,9 @@ var _AttachmentManager = class _AttachmentManager {
3620
3845
  localMetadata: {
3621
3846
  ...attachment.localMetadata,
3622
3847
  uploadState: error ? "failed" : "finished",
3623
- uploadProgress: void 0
3848
+ uploadProgress: void 0,
3849
+ // See the note in `uploadAttachment`: `undefined` cannot clear through `mergeWith`.
3850
+ uploadConfirmationPending: false
3624
3851
  }
3625
3852
  },
3626
3853
  error,
@@ -3645,7 +3872,7 @@ var _AttachmentManager = class _AttachmentManager {
3645
3872
  );
3646
3873
  };
3647
3874
  this.composer = composer;
3648
- this.state = new StateStore(initState({ message }));
3875
+ this.state = new StateStore(initState2({ message }));
3649
3876
  this.attachmentsByIdGetterCache = { attachmentsById: {}, attachments: [] };
3650
3877
  this.preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor({
3651
3878
  composer
@@ -3750,7 +3977,8 @@ var _AttachmentManager = class _AttachmentManager {
3750
3977
  localMetadata: {
3751
3978
  ...attachment.localMetadata,
3752
3979
  uploadState: "uploading",
3753
- uploadProgress: this.config.trackUploadProgress ? 0 : void 0
3980
+ uploadProgress: this.config.trackUploadProgress ? 0 : void 0,
3981
+ uploadConfirmationPending: false
3754
3982
  }
3755
3983
  }
3756
3984
  ]);
@@ -3763,7 +3991,8 @@ var _AttachmentManager = class _AttachmentManager {
3763
3991
  localMetadata: {
3764
3992
  ...attachment.localMetadata,
3765
3993
  uploadState: "uploading",
3766
- uploadProgress: nextUpload.uploadProgress
3994
+ uploadProgress: nextUpload.uploadProgress,
3995
+ uploadConfirmationPending: nextUpload.uploadConfirmationPending
3767
3996
  }
3768
3997
  });
3769
3998
  }
@@ -3817,6 +4046,50 @@ var localAttachmentToAttachment = (localAttachment) => {
3817
4046
  const { localMetadata, ...attachment } = localAttachment;
3818
4047
  return attachment;
3819
4048
  };
4049
+ var composeWithPendingUploads = ({
4050
+ composer,
4051
+ state
4052
+ }) => {
4053
+ const composerIsKeptAsDraft = !!composer.pollId;
4054
+ const relevantAttachments = composer.attachmentManager.attachments.filter(
4055
+ (attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && isPendingUpload(attachment)
4056
+ );
4057
+ const localAttachments = (state.localMessage.attachments ?? []).concat(
4058
+ relevantAttachments.map(
4059
+ (attachment) => isPendingUpload(attachment) ? attachment : localAttachmentToAttachment(attachment)
4060
+ )
4061
+ );
4062
+ const messageAttachments = (state.message.attachments ?? []).concat(
4063
+ relevantAttachments.filter(isFinishedUpload).map(localAttachmentToAttachment)
4064
+ );
4065
+ if (!localAttachments.length && !messageAttachments.length) return state;
4066
+ return {
4067
+ ...state,
4068
+ localMessage: localAttachments.length ? {
4069
+ ...state.localMessage,
4070
+ attachments: localAttachments
4071
+ } : state.localMessage,
4072
+ message: messageAttachments.length ? {
4073
+ ...state.message,
4074
+ attachments: messageAttachments
4075
+ } : state.message
4076
+ };
4077
+ };
4078
+ var createSendWithPendingUploadsAttachmentsMiddleware = (composer) => ({
4079
+ allowsPendingUploads: true,
4080
+ id: "stream-io/message-composer-middleware/attachments",
4081
+ handlers: {
4082
+ compose: ({
4083
+ state,
4084
+ next,
4085
+ forward
4086
+ }) => {
4087
+ const { attachmentManager } = composer;
4088
+ if (!attachmentManager) return forward();
4089
+ return next(composeWithPendingUploads({ composer, state }));
4090
+ }
4091
+ }
4092
+ });
3820
4093
  var createAttachmentsCompositionMiddleware = (composer) => ({
3821
4094
  id: "stream-io/message-composer-middleware/attachments",
3822
4095
  handlers: {
@@ -4033,7 +4306,7 @@ var getInitialMentions = (message) => {
4033
4306
  };
4034
4307
  var isSameMentionEntity = (first, second) => first.id === second.id && first.mentionType === second.mentionType;
4035
4308
  var getMentionsFromState = (state) => state.mentions ?? state.mentionedUsers.map(userResponseToMentionEntity);
4036
- var initState2 = ({
4309
+ var initState3 = ({
4037
4310
  composer,
4038
4311
  message
4039
4312
  }) => {
@@ -4062,7 +4335,7 @@ var initState2 = ({
4062
4335
  var TextComposer = class {
4063
4336
  constructor({ composer, message }) {
4064
4337
  this.initState = ({ message } = {}) => {
4065
- this.state.next(initState2({ composer: this.composer, message }));
4338
+ this.state.next(initState3({ composer: this.composer, message }));
4066
4339
  };
4067
4340
  this.getSnapshot = (state = this.state.getLatestValue()) => state;
4068
4341
  this.restoreSnapshot = (snapshot) => {
@@ -4255,7 +4528,7 @@ var TextComposer = class {
4255
4528
  this.commitState(output.state);
4256
4529
  };
4257
4530
  this.composer = composer;
4258
- this.state = new StateStore(initState2({ composer, message }));
4531
+ this.state = new StateStore(initState3({ composer, message }));
4259
4532
  this.middlewareExecutor = new TextComposerMiddlewareExecutor({ composer });
4260
4533
  }
4261
4534
  get channel() {
@@ -4405,13 +4678,41 @@ function isErrorResponse(res) {
4405
4678
 
4406
4679
  // src/search/BaseSearchSource.ts
4407
4680
  var DEFAULT_SEARCH_SOURCE_OPTIONS = {
4408
- debounceMs: 300,
4409
4681
  pageSize: 10,
4410
4682
  allowEmptySearchString: false,
4411
4683
  resetOnNewSearchQuery: true
4412
4684
  };
4685
+ var resolveDebounceOptions = ({
4686
+ debounceMs,
4687
+ shortQueryDebounceMs,
4688
+ longQueryDebounceMs,
4689
+ shortQueryMaxLength
4690
+ }) => ({
4691
+ shortQueryDebounceMs: shortQueryDebounceMs ?? debounceMs ?? 500,
4692
+ longQueryDebounceMs: longQueryDebounceMs ?? debounceMs ?? 300,
4693
+ shortQueryMaxLength: shortQueryMaxLength ?? 2
4694
+ });
4413
4695
  var BaseSearchSourceBase = class {
4414
4696
  constructor(options) {
4697
+ this.setDebounceOptions = (options = {}) => {
4698
+ const resolved = resolveDebounceOptions(options);
4699
+ this.shortQueryDebounceMs = resolved.shortQueryDebounceMs;
4700
+ this.longQueryDebounceMs = resolved.longQueryDebounceMs;
4701
+ this.shortQueryMaxLength = resolved.shortQueryMaxLength;
4702
+ this.searchDebounced = debounce(
4703
+ this.executeQuery.bind(this),
4704
+ (searchString) => this.getDebounceMs(searchString)
4705
+ );
4706
+ };
4707
+ /**
4708
+ * Short queries match too much and are slow server-side, so they get a longer debounce.
4709
+ * Resolved per call, which is what lets the interval switch as the user keeps typing.
4710
+ */
4711
+ this.getDebounceMs = (searchString) => {
4712
+ const { length } = searchString ?? this.searchQuery;
4713
+ return length <= this.shortQueryMaxLength ? this.shortQueryDebounceMs : this.longQueryDebounceMs;
4714
+ };
4715
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
4415
4716
  this.activate = () => {
4416
4717
  if (this.isActive) return;
4417
4718
  this.state.partialNext({ isActive: true });
@@ -4423,7 +4724,7 @@ var BaseSearchSourceBase = class {
4423
4724
  this.canExecuteQuery = (newSearchString) => {
4424
4725
  const hasNewSearchQuery = typeof newSearchString !== "undefined";
4425
4726
  const searchString = newSearchString ?? this.searchQuery;
4426
- return !!(this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery) && (this.allowEmptySearchString || searchString));
4727
+ return !!(this.isActive && this.canDispatchQuery(hasNewSearchQuery) && (this.allowEmptySearchString || searchString));
4427
4728
  };
4428
4729
  const { pageSize, allowEmptySearchString, resetOnNewSearchQuery } = {
4429
4730
  ...DEFAULT_SEARCH_SOURCE_OPTIONS,
@@ -4433,6 +4734,14 @@ var BaseSearchSourceBase = class {
4433
4734
  this.allowEmptySearchString = allowEmptySearchString;
4434
4735
  this.resetOnNewSearchQuery = resetOnNewSearchQuery;
4435
4736
  this.state = new StateStore(this.initialState);
4737
+ this.setDebounceOptions(options ?? {});
4738
+ }
4739
+ /**
4740
+ * A new search query preempts an in-flight one; pagination waits for it to finish
4741
+ * and needs a next page to fetch.
4742
+ */
4743
+ canDispatchQuery(hasNewSearchQuery) {
4744
+ return hasNewSearchQuery || !this.isLoading && this.hasNext;
4436
4745
  }
4437
4746
  get lastQueryError() {
4438
4747
  return this.state.getLatestValue().lastQueryError;
@@ -4527,21 +4836,30 @@ var BaseSearchSourceBase = class {
4527
4836
  }
4528
4837
  };
4529
4838
  var BaseSearchSource = class extends BaseSearchSourceBase {
4530
- constructor(options) {
4531
- const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
4532
- super(options);
4533
- this.setDebounceOptions = ({ debounceMs }) => {
4534
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
4535
- };
4536
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
4537
- this.setDebounceOptions({ debounceMs });
4839
+ constructor() {
4840
+ super(...arguments);
4841
+ /** Aborts the in-flight request, if any, once a newer query is dispatched. */
4842
+ this.queryAbortController = null;
4843
+ }
4844
+ /** Aborts the in-flight request, if any. Returns true when one was aborted. */
4845
+ abortInFlightQuery() {
4846
+ if (!this.queryAbortController) return false;
4847
+ this.queryAbortController.abort();
4848
+ this.queryAbortController = null;
4849
+ return true;
4850
+ }
4851
+ resetState() {
4852
+ this.abortInFlightQuery();
4853
+ super.resetState();
4538
4854
  }
4539
4855
  async executeQuery(newSearchString) {
4540
4856
  if (!this.canExecuteQuery(newSearchString)) return;
4857
+ this.abortInFlightQuery();
4858
+ const { signal } = this.queryAbortController = new AbortController();
4541
4859
  const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
4542
4860
  let stateUpdate = {};
4543
4861
  try {
4544
- const results = await this.query(searchString);
4862
+ const results = await this.query(searchString, { signal });
4545
4863
  if (!results) return;
4546
4864
  const { items } = results;
4547
4865
  stateUpdate = this.updatePaginationStateFromQuery(results);
@@ -4552,23 +4870,19 @@ var BaseSearchSource = class extends BaseSearchSourceBase {
4552
4870
  stateUpdate.hasNext = false;
4553
4871
  }
4554
4872
  } finally {
4555
- this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
4873
+ if (!signal.aborted) {
4874
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
4875
+ }
4556
4876
  }
4557
4877
  }
4558
4878
  cancelScheduledQuery() {
4559
4879
  this.searchDebounced.cancel();
4880
+ if (this.abortInFlightQuery() && this.isLoading) {
4881
+ this.state.partialNext({ isLoading: false });
4882
+ }
4560
4883
  }
4561
4884
  };
4562
4885
  var BaseSearchSourceSync = class extends BaseSearchSourceBase {
4563
- constructor(options) {
4564
- const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
4565
- super(options);
4566
- this.setDebounceOptions = ({ debounceMs }) => {
4567
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
4568
- };
4569
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
4570
- this.setDebounceOptions({ debounceMs });
4571
- }
4572
4886
  executeQuery(newSearchString) {
4573
4887
  if (!this.canExecuteQuery(newSearchString)) return;
4574
4888
  const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
@@ -4864,13 +5178,13 @@ var UserGroupPaginator = class extends BasePaginator {
4864
5178
  // src/search/ChannelMemberSearchSource.ts
4865
5179
  var ChannelMemberSearchSource = class extends BaseSearchSource {
4866
5180
  constructor(channel, options, filterBuilderOptions = {}) {
4867
- super(options);
5181
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
5182
+ super({ ...restOptions, allowEmptySearchString: true });
4868
5183
  this.type = "members";
4869
- this.canExecuteQuery = (newSearchString) => {
4870
- const hasNewSearchQuery = typeof newSearchString !== "undefined";
4871
- return this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery);
4872
- };
4873
5184
  this.channel = channel;
5185
+ this.filters = filters;
5186
+ this.sort = sort;
5187
+ this.searchOptions = searchOptions;
4874
5188
  this.filterBuilder = new FilterBuilder({
4875
5189
  initialFilterConfig: {
4876
5190
  default: {
@@ -4886,7 +5200,7 @@ var ChannelMemberSearchSource = class extends BaseSearchSource {
4886
5200
  ...filterBuilderOptions
4887
5201
  });
4888
5202
  }
4889
- async query(searchQuery) {
5203
+ async query(searchQuery, queryOptions = {}) {
4890
5204
  const filters = this.filterBuilder.buildFilters({
4891
5205
  baseFilters: this.filters,
4892
5206
  context: {
@@ -4895,7 +5209,12 @@ var ChannelMemberSearchSource = class extends BaseSearchSource {
4895
5209
  });
4896
5210
  const sort = this.sort ?? [];
4897
5211
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
4898
- const { members } = await this.channel.queryMembers(filters ?? {}, sort, options);
5212
+ const { members } = await this.channel.queryMembers(
5213
+ filters ?? {},
5214
+ sort,
5215
+ options,
5216
+ queryOptions
5217
+ );
4899
5218
  return { items: members };
4900
5219
  }
4901
5220
  filterQueryResults(items) {
@@ -5010,9 +5329,13 @@ var SearchController = class {
5010
5329
  // src/search/UserSearchSource.ts
5011
5330
  var UserSearchSource = class extends BaseSearchSource {
5012
5331
  constructor(client, options, filterBuilderOptions = {}) {
5013
- super(options);
5332
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
5333
+ super(restOptions);
5014
5334
  this.type = "users";
5015
5335
  this.client = client;
5336
+ this.filters = filters;
5337
+ this.sort = sort;
5338
+ this.searchOptions = searchOptions;
5016
5339
  this.filterBuilder = new FilterBuilder({
5017
5340
  initialFilterConfig: {
5018
5341
  $or: {
@@ -5028,7 +5351,7 @@ var UserSearchSource = class extends BaseSearchSource {
5028
5351
  ...filterBuilderOptions
5029
5352
  });
5030
5353
  }
5031
- async query(searchQuery) {
5354
+ async query(searchQuery, queryOptions = {}) {
5032
5355
  const filters = this.filterBuilder.buildFilters({
5033
5356
  baseFilters: this.filters,
5034
5357
  context: { searchQuery }
@@ -5041,7 +5364,7 @@ var UserSearchSource = class extends BaseSearchSource {
5041
5364
  sort = { id: 1, ...this.sort };
5042
5365
  }
5043
5366
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
5044
- const { users } = await this.client.queryUsers(filters, sort, options);
5367
+ const { users } = await this.client.queryUsers(filters, sort, options, queryOptions);
5045
5368
  return { items: users };
5046
5369
  }
5047
5370
  filterQueryResults(items) {
@@ -5052,9 +5375,13 @@ var UserSearchSource = class extends BaseSearchSource {
5052
5375
  // src/search/ChannelSearchSource.ts
5053
5376
  var ChannelSearchSource = class extends BaseSearchSource {
5054
5377
  constructor(client, options, filterBuilderOptions = {}) {
5055
- super(options);
5378
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
5379
+ super(restOptions);
5056
5380
  this.type = "channels";
5057
5381
  this.client = client;
5382
+ this.filters = filters;
5383
+ this.sort = sort;
5384
+ this.searchOptions = searchOptions;
5058
5385
  this.filterBuilder = new FilterBuilder({
5059
5386
  ...filterBuilderOptions,
5060
5387
  initialFilterConfig: {
@@ -5066,7 +5393,7 @@ var ChannelSearchSource = class extends BaseSearchSource {
5066
5393
  }
5067
5394
  });
5068
5395
  }
5069
- async query(searchQuery) {
5396
+ async query(searchQuery, queryOptions = {}) {
5070
5397
  const filters = this.filterBuilder.buildFilters({
5071
5398
  baseFilters: {
5072
5399
  ...this.client.userID ? { members: { $in: [this.client.userID] } } : {},
@@ -5076,7 +5403,7 @@ var ChannelSearchSource = class extends BaseSearchSource {
5076
5403
  });
5077
5404
  const sort = this.sort ?? {};
5078
5405
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
5079
- const items = await this.client.queryChannels(filters, sort, options);
5406
+ const items = await this.client.queryChannels(filters, sort, options, queryOptions);
5080
5407
  return { items };
5081
5408
  }
5082
5409
  filterQueryResults(items) {
@@ -5087,9 +5414,24 @@ var ChannelSearchSource = class extends BaseSearchSource {
5087
5414
  // src/search/MessageSearchSource.ts
5088
5415
  var MessageSearchSource = class extends BaseSearchSource {
5089
5416
  constructor(client, options, filterBuilderOptions) {
5090
- super(options);
5417
+ const {
5418
+ messageSearchChannelFilters,
5419
+ messageSearchFilters,
5420
+ messageSearchSort,
5421
+ channelQueryFilters,
5422
+ channelQuerySort,
5423
+ channelQueryOptions,
5424
+ ...restOptions
5425
+ } = options || {};
5426
+ super(restOptions);
5091
5427
  this.type = "messages";
5092
5428
  this.client = client;
5429
+ this.messageSearchChannelFilters = messageSearchChannelFilters;
5430
+ this.messageSearchFilters = messageSearchFilters;
5431
+ this.messageSearchSort = messageSearchSort;
5432
+ this.channelQueryFilters = channelQueryFilters;
5433
+ this.channelQuerySort = channelQuerySort;
5434
+ this.channelQueryOptions = channelQueryOptions;
5093
5435
  this.messageSearchChannelFilterBuilder = new FilterBuilder(filterBuilderOptions?.messageSearchChannel);
5094
5436
  this.messageSearchFilterBuilder = new FilterBuilder({
5095
5437
  ...filterBuilderOptions?.messageSearch,
@@ -5112,7 +5454,7 @@ var MessageSearchSource = class extends BaseSearchSource {
5112
5454
  }
5113
5455
  });
5114
5456
  }
5115
- async query(searchQuery) {
5457
+ async query(searchQuery, queryOptions = {}) {
5116
5458
  if (!this.client.userID || this.next === null) return { items: [] };
5117
5459
  const channelFilters = this.messageSearchChannelFilterBuilder.buildFilters({
5118
5460
  baseFilters: {
@@ -5140,9 +5482,11 @@ var MessageSearchSource = class extends BaseSearchSource {
5140
5482
  const { next, results } = await this.client.search(
5141
5483
  channelFilters,
5142
5484
  messageFilters,
5143
- options
5485
+ options,
5486
+ queryOptions
5144
5487
  );
5145
5488
  const items = results.map(({ message }) => message);
5489
+ if (queryOptions.signal?.aborted) return { items, next };
5146
5490
  const cids = Array.from(
5147
5491
  items.reduce((acc, message) => {
5148
5492
  if (message.cid && !this.client.activeChannels[message.cid]) acc.add(message.cid);
@@ -5160,7 +5504,8 @@ var MessageSearchSource = class extends BaseSearchSource {
5160
5504
  last_message_at: -1,
5161
5505
  ...this.channelQuerySort
5162
5506
  },
5163
- this.channelQueryOptions
5507
+ this.channelQueryOptions,
5508
+ queryOptions
5164
5509
  );
5165
5510
  }
5166
5511
  return { items, next };
@@ -5885,6 +6230,13 @@ var MessageComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5885
6230
  createCompositionDataCleanupMiddleware(composer)
5886
6231
  ]);
5887
6232
  }
6233
+ /**
6234
+ * Narrows the base accessor to the composition middleware type, so callers can read the
6235
+ * declarations it carries (`allowsPendingUploads`) without casting.
6236
+ */
6237
+ get installedMiddleware() {
6238
+ return super.installedMiddleware;
6239
+ }
5888
6240
  };
5889
6241
  var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5890
6242
  constructor({ composer }) {
@@ -6454,13 +6806,18 @@ var MentionsSearchSource = class extends BaseSearchSource {
6454
6806
  constructor(channel, options) {
6455
6807
  const {
6456
6808
  mentionAllAppUsers,
6809
+ memberFilters,
6810
+ memberSort,
6811
+ searchOptions,
6457
6812
  suggestionFactoryMappers,
6458
6813
  textComposerText,
6459
6814
  transliterate,
6460
6815
  trigger,
6816
+ userFilters,
6817
+ userSort,
6461
6818
  ...restOptions
6462
6819
  } = options || {};
6463
- super(restOptions);
6820
+ super({ ...restOptions, allowEmptySearchString: true });
6464
6821
  this.type = "mentions";
6465
6822
  this.normalizeSearchValue = (value) => this.transliterate(removeDiacritics(value)).toLowerCase();
6466
6823
  this.matchesSearchQuery = (value, searchQuery) => {
@@ -6498,10 +6855,6 @@ var MentionsSearchSource = class extends BaseSearchSource {
6498
6855
  this.toHereMentionSuggestion = (searchToken = this.searchQuery) => this.mapMentionSuggestion("here", "here", searchToken);
6499
6856
  this.toRoleMentionSuggestion = (role, searchToken = this.searchQuery) => this.mapMentionSuggestion("role", role, searchToken);
6500
6857
  this.toUserGroupMentionSuggestion = (userGroup, searchToken = this.searchQuery) => this.mapMentionSuggestion("user_group", userGroup, searchToken);
6501
- this.canExecuteQuery = (newSearchString) => {
6502
- const hasNewSearchQuery = typeof newSearchString !== "undefined";
6503
- return this.isActive && !this.isLoading && (hasNewSearchQuery || this.hasNext);
6504
- };
6505
6858
  this.transliterate = (text) => text;
6506
6859
  this.getMembersAndWatchers = () => {
6507
6860
  const memberUsers = Object.values(this.channel.state.members ?? {}).map(
@@ -6521,10 +6874,10 @@ var MentionsSearchSource = class extends BaseSearchSource {
6521
6874
  ...this.isMentionTypeAllowed("channel") ? [this.toChannelMentionSuggestion(searchQuery)] : [],
6522
6875
  ...this.isMentionTypeAllowed("here") ? [this.toHereMentionSuggestion(searchQuery)] : []
6523
6876
  ].filter(({ name }) => this.matchesPrefixSearchQuery(name, searchQuery));
6524
- this.getRoleMentionSuggestions = async (query) => {
6877
+ this.getRoleMentionSuggestions = async (query, queryOptions = {}) => {
6525
6878
  if (!this.isMentionTypeAllowed("role")) return [];
6526
6879
  if (!query) return [];
6527
- const { roles } = await this.client.searchRoles({ query });
6880
+ const { roles } = await this.client.searchRoles({ query }, queryOptions);
6528
6881
  return [...roles?.map((role) => role.name) ?? []].sort((left, right) => left.localeCompare(right)).map((role) => this.toRoleMentionSuggestion(role, query));
6529
6882
  };
6530
6883
  this.searchMembersLocally = (searchQuery) => {
@@ -6586,20 +6939,25 @@ var MentionsSearchSource = class extends BaseSearchSource {
6586
6939
  options: { ...this.searchOptions, limit: this.pageSize, offset }
6587
6940
  };
6588
6941
  };
6589
- this.queryUsers = async (searchQuery, offset = 0) => {
6942
+ this.queryUsers = async (searchQuery, offset = 0, queryOptions = {}) => {
6590
6943
  const { filters, sort, options } = this.prepareQueryUsersParams(searchQuery, offset);
6591
- const { users } = await this.client.queryUsers(filters, sort, options);
6944
+ const { users } = await this.client.queryUsers(filters, sort, options, queryOptions);
6592
6945
  return users;
6593
6946
  };
6594
- this.queryMembers = async (searchQuery, offset = 0) => {
6947
+ this.queryMembers = async (searchQuery, offset = 0, queryOptions = {}) => {
6595
6948
  const { filters, sort, options } = this.prepareQueryMembersParams(
6596
6949
  searchQuery,
6597
6950
  offset
6598
6951
  );
6599
- const response = await this.channel.queryMembers(filters, sort, options);
6952
+ const response = await this.channel.queryMembers(
6953
+ filters,
6954
+ sort,
6955
+ options,
6956
+ queryOptions
6957
+ );
6600
6958
  return response.members.map((member) => member.user);
6601
6959
  };
6602
- this.getUserSuggestionsPage = async (searchQuery, userOffset = 0) => {
6960
+ this.getUserSuggestionsPage = async (searchQuery, userOffset = 0, queryOptions = {}) => {
6603
6961
  if (!this.isMentionTypeAllowed("user")) {
6604
6962
  return {
6605
6963
  items: [],
@@ -6609,7 +6967,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6609
6967
  let users;
6610
6968
  const shouldSearchLocally = this.allMembersLoadedWithInitialChannelQuery || !searchQuery;
6611
6969
  if (this.config.mentionAllAppUsers) {
6612
- users = await this.queryUsers(searchQuery, userOffset);
6970
+ users = await this.queryUsers(searchQuery, userOffset, queryOptions);
6613
6971
  } else if (shouldSearchLocally) {
6614
6972
  const localUsers = this.searchMembersLocally(searchQuery);
6615
6973
  const items2 = localUsers.slice(userOffset, userOffset + this.pageSize).map((user) => this.toUserSuggestion(user, searchQuery));
@@ -6618,7 +6976,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6618
6976
  nextOffset: localUsers.length > userOffset + this.pageSize ? userOffset + items2.length : void 0
6619
6977
  };
6620
6978
  } else {
6621
- users = await this.queryMembers(searchQuery, userOffset);
6979
+ users = await this.queryMembers(searchQuery, userOffset, queryOptions);
6622
6980
  }
6623
6981
  const items = users.map((user) => this.toUserSuggestion(user, searchQuery));
6624
6982
  return {
@@ -6635,7 +6993,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6635
6993
  name_gt: lastItem.name
6636
6994
  });
6637
6995
  };
6638
- this.getUserGroupSuggestionsPage = async (searchQuery, cursor) => {
6996
+ this.getUserGroupSuggestionsPage = async (searchQuery, cursor, queryOptions = {}) => {
6639
6997
  if (!this.isMentionTypeAllowed("user_group")) {
6640
6998
  return {
6641
6999
  items: [],
@@ -6657,7 +7015,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6657
7015
  ...userGroupCursor?.id_gt ? { id_gt: userGroupCursor.id_gt } : {},
6658
7016
  ...userGroupCursor?.name_gt ? { name_gt: userGroupCursor.name_gt } : {}
6659
7017
  };
6660
- const { user_groups } = await this.client.searchUserGroups(options);
7018
+ const { user_groups } = await this.client.searchUserGroups(options, queryOptions);
6661
7019
  return {
6662
7020
  items: user_groups.map(
6663
7021
  (userGroup) => this.toUserGroupMentionSuggestion(userGroup, searchQuery)
@@ -6667,6 +7025,11 @@ var MentionsSearchSource = class extends BaseSearchSource {
6667
7025
  };
6668
7026
  this.client = channel.getClient();
6669
7027
  this.channel = channel;
7028
+ this.userFilters = userFilters;
7029
+ this.userSort = userSort;
7030
+ this.memberFilters = memberFilters;
7031
+ this.memberSort = memberSort;
7032
+ this.searchOptions = searchOptions;
6670
7033
  this.config = {
6671
7034
  mentionAllAppUsers,
6672
7035
  suggestionFactoryMappers,
@@ -6699,16 +7062,21 @@ var MentionsSearchSource = class extends BaseSearchSource {
6699
7062
  offset: (this.offset ?? 0) + userPaginationState.itemCount
6700
7063
  };
6701
7064
  }
6702
- async query(searchQuery) {
7065
+ async query(searchQuery, queryOptions = {}) {
6703
7066
  const userOffset = this.offset ?? 0;
6704
7067
  const isFirstPage = userOffset === 0 && typeof this.userGroupCursor === "undefined";
6705
7068
  const previousUserPaginationState = this.latestUserPaginationState;
6706
7069
  const previousUserGroupCursor = this.userGroupCursor;
6707
7070
  const [userResultsState, userGroupResultsState, roleSuggestionsState] = await Promise.allSettled([
6708
- this.getUserSuggestionsPage(searchQuery, userOffset),
6709
- this.getUserGroupSuggestionsPage(searchQuery, previousUserGroupCursor),
6710
- isFirstPage ? this.getRoleMentionSuggestions(searchQuery) : Promise.resolve([])
7071
+ this.getUserSuggestionsPage(searchQuery, userOffset, queryOptions),
7072
+ this.getUserGroupSuggestionsPage(
7073
+ searchQuery,
7074
+ previousUserGroupCursor,
7075
+ queryOptions
7076
+ ),
7077
+ isFirstPage ? this.getRoleMentionSuggestions(searchQuery, queryOptions) : Promise.resolve([])
6711
7078
  ]);
7079
+ if (queryOptions.signal?.aborted) return { items: [] };
6712
7080
  const userResults = userResultsState.status === "fulfilled" ? userResultsState.value : {
6713
7081
  items: [],
6714
7082
  nextOffset: isFirstPage ? void 0 : previousUserPaginationState?.nextOffset
@@ -6983,7 +7351,7 @@ var DEFAULT_COMPOSER_CONFIG = {
6983
7351
  };
6984
7352
 
6985
7353
  // src/messageComposer/CustomDataManager.ts
6986
- var initState3 = (options) => {
7354
+ var initState4 = (options) => {
6987
7355
  if (!options)
6988
7356
  return { message: {}, custom: {} };
6989
7357
  return { message: {}, custom: {} };
@@ -6992,14 +7360,14 @@ var CustomDataManager = class {
6992
7360
  constructor({ composer, message }) {
6993
7361
  this.isMessageDataEqual = (nextState, previousState) => JSON.stringify(nextState.message) === JSON.stringify(previousState?.message);
6994
7362
  this.initState = ({ message } = {}) => {
6995
- this.state.next(initState3({ composer: this.composer, message }));
7363
+ this.state.next(initState4({ composer: this.composer, message }));
6996
7364
  };
6997
7365
  this.getSnapshot = () => this.state.getLatestValue();
6998
7366
  this.restoreSnapshot = (snapshot) => {
6999
7367
  this.state.next(snapshot);
7000
7368
  };
7001
7369
  this.composer = composer;
7002
- this.state = new StateStore(initState3({ composer, message }));
7370
+ this.state = new StateStore(initState4({ composer, message }));
7003
7371
  }
7004
7372
  get customMessageData() {
7005
7373
  return this.state.getLatestValue().message;
@@ -7035,7 +7403,7 @@ var LinkPreviewStatus = /* @__PURE__ */ ((LinkPreviewStatus2) => {
7035
7403
  return LinkPreviewStatus2;
7036
7404
  })(LinkPreviewStatus || {});
7037
7405
  var linkPreviewArrayToMap = (linkPreviews) => new Map(linkPreviews.map((linkPreview) => [linkPreview.og_scrape_url, linkPreview]));
7038
- var initState4 = ({
7406
+ var initState5 = ({
7039
7407
  message
7040
7408
  }) => message ? {
7041
7409
  previews: message.attachments?.reduce((acc, attachment) => {
@@ -7054,7 +7422,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7054
7422
  this.shouldDiscardEnrichQueries = false;
7055
7423
  this.initState = ({ message } = {}) => {
7056
7424
  this.cancelURLEnrichment();
7057
- this.state.next(initState4({ message: this.enabled ? message : void 0 }));
7425
+ this.state.next(initState5({ message: this.enabled ? message : void 0 }));
7058
7426
  };
7059
7427
  this.getSnapshot = () => this.state.getLatestValue();
7060
7428
  this.restoreSnapshot = (snapshot) => {
@@ -7157,7 +7525,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7157
7525
  };
7158
7526
  this.composer = composer;
7159
7527
  this.state = new StateStore(
7160
- initState4({ message: this.enabled ? message : void 0 })
7528
+ initState5({ message: this.enabled ? message : void 0 })
7161
7529
  );
7162
7530
  this.findAndEnrichUrls = debounce(
7163
7531
  this._findAndEnrichUrls.bind(this),
@@ -7245,7 +7613,7 @@ var LinkPreviewsManager = _LinkPreviewsManager;
7245
7613
 
7246
7614
  // src/messageComposer/LocationComposer.ts
7247
7615
  var MIN_LIVE_LOCATION_SHARE_DURATION = 60 * 1e3;
7248
- var initState5 = ({
7616
+ var initState6 = ({
7249
7617
  message
7250
7618
  }) => ({
7251
7619
  location: message?.shared_location ?? null
@@ -7253,7 +7621,7 @@ var initState5 = ({
7253
7621
  var LocationComposer = class {
7254
7622
  constructor({ composer, message }) {
7255
7623
  this.initState = ({ message } = {}) => {
7256
- this.state.next(initState5({ message }));
7624
+ this.state.next(initState6({ message }));
7257
7625
  };
7258
7626
  this.getSnapshot = () => this.state.getLatestValue();
7259
7627
  this.restoreSnapshot = (snapshot) => {
@@ -7271,7 +7639,7 @@ var LocationComposer = class {
7271
7639
  });
7272
7640
  };
7273
7641
  this.composer = composer;
7274
- this.state = new StateStore(initState5({ message }));
7642
+ this.state = new StateStore(initState6({ message }));
7275
7643
  this._deviceId = this.config.getDeviceId();
7276
7644
  }
7277
7645
  get config() {
@@ -7992,7 +8360,7 @@ var initEditingAuditState = (composition) => {
7992
8360
  }
7993
8361
  };
7994
8362
  };
7995
- var initState6 = (composition) => {
8363
+ var initState7 = (composition) => {
7996
8364
  if (!composition) {
7997
8365
  return {
7998
8366
  draftId: null,
@@ -8079,7 +8447,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8079
8447
  this.textComposer.initState({ message });
8080
8448
  this.pollComposer.initState();
8081
8449
  this.customDataManager.initState({ message });
8082
- this.state.next(initState6(composition));
8450
+ this.state.next(initState7(composition));
8083
8451
  };
8084
8452
  this.initStateFromChannelResponse = (channelApiResponse) => {
8085
8453
  if (this.channel.cid !== channelApiResponse.channel.cid) {
@@ -8545,7 +8913,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8545
8913
  this.editingAuditState = new StateStore(
8546
8914
  this.initEditingAuditState(composition)
8547
8915
  );
8548
- this.state = new StateStore(initState6(composition));
8916
+ this.state = new StateStore(initState7(composition));
8549
8917
  this.compositionMiddlewareExecutor = new MessageComposerMiddlewareExecutor({
8550
8918
  composer: this
8551
8919
  });
@@ -8624,8 +8992,32 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8624
8992
  const currentCommand = this.textComposer.command;
8625
8993
  return !currentCommand || this.validateCommandSendability(currentCommand).ready;
8626
8994
  }
8995
+ /**
8996
+ * Whether the installed composition middleware composes messages whose uploads are still in
8997
+ * flight — see `MessageCompositionMiddleware.allowsPendingUploads`.
8998
+ *
8999
+ * Derived from the chain rather than from config, so there is exactly one switch: the UI SDK
9000
+ * that implements the rest of the flow installs the middleware, and everything that has to
9001
+ * follow from that (starting with {@link hasSendableData}) follows on its own.
9002
+ *
9003
+ * **Temporary API.** In v10 sending with pending uploads becomes part of the composer
9004
+ * configuration (`MessageComposerConfig`, set through `updateConfig`), and this getter,
9005
+ * together with the middleware declaration it reads, is replaced by that config field.
9006
+ */
9007
+ get allowsPendingUploads() {
9008
+ return this.compositionMiddlewareExecutor.installedMiddleware.some(
9009
+ (middleware) => middleware.allowsPendingUploads
9010
+ );
9011
+ }
8627
9012
  get hasSendableData() {
8628
- return this.isCommandSendable && !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
9013
+ if (!this.isCommandSendable) return false;
9014
+ if (this.allowsPendingUploads) {
9015
+ const hasSendableAttachment = this.attachmentManager.attachments.some(
9016
+ (attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment)
9017
+ );
9018
+ return !!(!this.textComposer.textIsEmpty || hasSendableAttachment || this.pollId || this.locationComposer.validLocation);
9019
+ }
9020
+ return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
8629
9021
  }
8630
9022
  get compositionIsEmpty() {
8631
9023
  return !this.quotedMessage && this.contentIsEmpty;
@@ -9252,14 +9644,16 @@ var Channel = class {
9252
9644
  *
9253
9645
  * @return {Promise<SendMessageAPIResponse>} The Server Response
9254
9646
  */
9255
- async _sendMessage(message, options) {
9256
- return await this.getClient().post(
9257
- this._channelURL() + "/message",
9258
- {
9259
- message,
9260
- ...options
9261
- }
9262
- );
9647
+ async _sendMessage(rawMessage, options) {
9648
+ const client = this.getClient();
9649
+ const message = sanitizeOutgoingAttachments({
9650
+ client,
9651
+ message: rawMessage
9652
+ });
9653
+ return await client.post(this._channelURL() + "/message", {
9654
+ message,
9655
+ ...options
9656
+ });
9263
9657
  }
9264
9658
  async sendMessage(message, options) {
9265
9659
  try {
@@ -9384,10 +9778,11 @@ var Channel = class {
9384
9778
  * @param {MemberSort} [sort] Sort options, for instance [{created_at: -1}].
9385
9779
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{name: -1}, {created_at: 1}]
9386
9780
  * @param {{ limit?: number; offset?: number }} [options] Option object, {limit: 10, offset:10}
9781
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
9387
9782
  *
9388
9783
  * @return {Promise<ChannelMemberAPIResponse>} Query Members response
9389
9784
  */
9390
- async queryMembers(filterConditions, sort = [], options = {}) {
9785
+ async queryMembers(filterConditions, sort = [], options = {}, requestOptions = {}) {
9391
9786
  let id;
9392
9787
  const type = this.type;
9393
9788
  let members;
@@ -9407,7 +9802,8 @@ var Channel = class {
9407
9802
  filter_conditions: filterConditions,
9408
9803
  ...options
9409
9804
  }
9410
- }
9805
+ },
9806
+ requestOptions
9411
9807
  );
9412
9808
  }
9413
9809
  /**
@@ -10708,6 +11104,7 @@ var Channel = class {
10708
11104
  // delivery-report network sync below is skipped for it.
10709
11105
  case "message.read_locally":
10710
11106
  case "message.read":
11107
+ if (event.thread) break;
10711
11108
  if (event.user?.id && event.created_at) {
10712
11109
  const previousReadState = channelState.read[event.user.id];
10713
11110
  channelState.read[event.user.id] = {
@@ -11157,7 +11554,7 @@ var ClientState = class {
11157
11554
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
11158
11555
 
11159
11556
  // src/insights.ts
11160
- var import_axios = __toESM(require("axios"));
11557
+ var import_axios2 = __toESM(require("axios"));
11161
11558
  var InsightMetrics = class {
11162
11559
  constructor() {
11163
11560
  this.connectionStartTimestamp = null;
@@ -11170,7 +11567,7 @@ var postInsights = async (insightType, insights) => {
11170
11567
  const maxAttempts = 3;
11171
11568
  for (let i = 0; i < maxAttempts; i++) {
11172
11569
  try {
11173
- await import_axios.default.post(
11570
+ await import_axios2.default.post(
11174
11571
  `https://chat-insights.getstream.io/insights/${insightType}`,
11175
11572
  insights
11176
11573
  );
@@ -11678,131 +12075,6 @@ var StableWSConnection = class {
11678
12075
  }
11679
12076
  };
11680
12077
 
11681
- // src/uploadManager.ts
11682
- var initState7 = () => ({ uploads: {} });
11683
- var upsertById = (uploads, record) => ({
11684
- ...uploads,
11685
- [record.id]: { ...uploads[record.id], ...record }
11686
- });
11687
- var updateById = (uploads, record) => {
11688
- if (!(record.id in uploads)) return null;
11689
- const current = uploads[record.id];
11690
- return { ...uploads, [record.id]: { ...current, ...record } };
11691
- };
11692
- var UploadManager = class {
11693
- constructor(client) {
11694
- this.client = client;
11695
- this.inFlightUploads = /* @__PURE__ */ new Map();
11696
- this.getUpload = (id) => this.uploads[id];
11697
- /**
11698
- * Clears all upload records.
11699
- * Invoked when the user disconnects so a later session does not inherit stale upload state.
11700
- * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
11701
- */
11702
- this.reset = () => {
11703
- for (const { abortController } of this.inFlightUploads.values()) {
11704
- abortController.abort();
11705
- }
11706
- this.inFlightUploads.clear();
11707
- this.state.next(initState7());
11708
- };
11709
- /**
11710
- * Removes the upload record for `id` if present.
11711
- * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
11712
- */
11713
- this.deleteUploadRecord = (id) => {
11714
- const flight = this.inFlightUploads.get(id);
11715
- if (flight) {
11716
- this.inFlightUploads.delete(id);
11717
- flight.abortController.abort();
11718
- }
11719
- this.state.next((current) => {
11720
- if (!(id in current.uploads)) return current;
11721
- const uploads = { ...current.uploads };
11722
- delete uploads[id];
11723
- return { ...current, uploads };
11724
- });
11725
- };
11726
- /**
11727
- * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
11728
- * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
11729
- * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
11730
- */
11731
- this.upload = ({
11732
- id,
11733
- channelCid,
11734
- file
11735
- }) => {
11736
- const existing = this.inFlightUploads.get(id);
11737
- if (existing) return existing.promise;
11738
- let resolvePromise;
11739
- let rejectPromise;
11740
- const promise = new Promise((resolve, reject) => {
11741
- resolvePromise = resolve;
11742
- rejectPromise = reject;
11743
- });
11744
- const abortController = new AbortController();
11745
- this.inFlightUploads.set(id, { promise, abortController });
11746
- void (async () => {
11747
- const attachmentManager = this.resolveAttachmentManager(channelCid);
11748
- const trackProgress = attachmentManager.config.trackUploadProgress;
11749
- try {
11750
- this.upsertUpload({
11751
- id,
11752
- uploadProgress: trackProgress ? 0 : void 0
11753
- });
11754
- const onProgress = trackProgress ? (progress) => {
11755
- this.updateUpload({
11756
- id,
11757
- uploadProgress: progress
11758
- });
11759
- } : void 0;
11760
- const uploadRequestOptions = {
11761
- abortSignal: abortController.signal,
11762
- ...onProgress ? { onProgress } : {}
11763
- };
11764
- const response = await attachmentManager.doUploadRequest(
11765
- file,
11766
- uploadRequestOptions
11767
- );
11768
- resolvePromise(response);
11769
- } catch (error) {
11770
- rejectPromise(error);
11771
- } finally {
11772
- this.inFlightUploads.delete(id);
11773
- this.deleteUploadRecord(id);
11774
- }
11775
- })();
11776
- return promise;
11777
- };
11778
- this.upsertUpload = (record) => {
11779
- this.state.partialNext({
11780
- uploads: upsertById(this.uploads, record)
11781
- });
11782
- };
11783
- this.updateUpload = (record) => {
11784
- this.state.next((current) => {
11785
- const nextUploads = updateById(current.uploads, record);
11786
- if (!nextUploads) return current;
11787
- return { ...current, uploads: nextUploads };
11788
- });
11789
- };
11790
- this.state = new StateStore(initState7());
11791
- }
11792
- resolveAttachmentManager(channelCid) {
11793
- const colon = channelCid.indexOf(":");
11794
- if (colon <= 0 || colon === channelCid.length - 1) {
11795
- throw new Error(`Invalid channelCid: ${channelCid}`);
11796
- }
11797
- const channelType = channelCid.slice(0, colon);
11798
- const channelId = channelCid.slice(colon + 1);
11799
- return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
11800
- }
11801
- get uploads() {
11802
- return this.state.getLatestValue().uploads;
11803
- }
11804
- };
11805
-
11806
12078
  // src/signing.ts
11807
12079
  var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
11808
12080
  var import_crypto = __toESM(require("crypto"));
@@ -12061,7 +12333,7 @@ var TokenManager = class {
12061
12333
  };
12062
12334
 
12063
12335
  // src/connection_fallback.ts
12064
- var import_axios2 = __toESM(require("axios"));
12336
+ var import_axios3 = __toESM(require("axios"));
12065
12337
  var WSConnectionFallback = class {
12066
12338
  constructor({ client }) {
12067
12339
  /** @private */
@@ -12080,7 +12352,7 @@ var WSConnectionFallback = class {
12080
12352
  /** @private */
12081
12353
  this._req = async (params, config, retry) => {
12082
12354
  if (!this.cancelToken && !params.close) {
12083
- this.cancelToken = import_axios2.default.CancelToken.source();
12355
+ this.cancelToken = import_axios3.default.CancelToken.source();
12084
12356
  }
12085
12357
  try {
12086
12358
  const res = await this.client.doAxiosRequest(
@@ -12116,7 +12388,7 @@ var WSConnectionFallback = class {
12116
12388
  }
12117
12389
  }
12118
12390
  } catch (error) {
12119
- if (import_axios2.default.isCancel(error)) {
12391
+ if (import_axios3.default.isCancel(error)) {
12120
12392
  this._log(`_poll() - axios canceled request`);
12121
12393
  return;
12122
12394
  }
@@ -14539,6 +14811,7 @@ var StreamChat = class _StreamChat {
14539
14811
  return this.handleResponse(response);
14540
14812
  } catch (e) {
14541
14813
  e.client_request_id = requestConfig.headers?.["x-client-request-id"];
14814
+ if (import_axios4.default.isCancel(e)) throw e;
14542
14815
  this._logApiError(type, url, e);
14543
14816
  this.consecutiveFailures += 1;
14544
14817
  if (e.response) {
@@ -14850,7 +15123,7 @@ var StreamChat = class _StreamChat {
14850
15123
  return this.userAgent;
14851
15124
  }
14852
15125
  if (!this.cachedUserAgent) {
14853
- const version = "9.50.3";
15126
+ const version = "9.52.0";
14854
15127
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14855
15128
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14856
15129
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -14918,7 +15191,7 @@ var StreamChat = class _StreamChat {
14918
15191
  keepAliveMsecs: 3e3
14919
15192
  });
14920
15193
  }
14921
- this.axiosInstance = import_axios3.default.create(this.options);
15194
+ this.axiosInstance = import_axios4.default.create(this.options);
14922
15195
  this.setBaseURL(this.options.baseURL || "https://chat.stream-io-api.com");
14923
15196
  if (typeof process !== "undefined" && "env" in process && process.env.STREAM_LOCAL_TEST_RUN) {
14924
15197
  this.setBaseURL("http://localhost:3030");
@@ -15226,14 +15499,14 @@ var StreamChat = class _StreamChat {
15226
15499
  error
15227
15500
  });
15228
15501
  }
15229
- get(url, params) {
15230
- return this.doAxiosRequest("get", url, null, { params });
15502
+ get(url, params, config) {
15503
+ return this.doAxiosRequest("get", url, null, { params, config });
15231
15504
  }
15232
15505
  put(url, data) {
15233
15506
  return this.doAxiosRequest("put", url, data);
15234
15507
  }
15235
- post(url, data) {
15236
- return this.doAxiosRequest("post", url, data);
15508
+ post(url, data, config) {
15509
+ return this.doAxiosRequest("post", url, data, { config });
15237
15510
  }
15238
15511
  patch(url, data) {
15239
15512
  return this.doAxiosRequest("patch", url, data);
@@ -15428,10 +15701,11 @@ var StreamChat = class _StreamChat {
15428
15701
  * @param {UserSort} sort Sort options, for instance [{last_active: -1}].
15429
15702
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_active: -1}, {created_at: 1}]
15430
15703
  * @param {UserOptions} options Option object, {presence: true}
15704
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15431
15705
  *
15432
15706
  * @return {Promise<{ users: Array<UserResponse> }>} User Query Response
15433
15707
  */
15434
- async queryUsers(filterConditions, sort = [], options = {}) {
15708
+ async queryUsers(filterConditions, sort = [], options = {}, requestOptions = {}) {
15435
15709
  const defaultOptions = {
15436
15710
  presence: false
15437
15711
  };
@@ -15448,7 +15722,8 @@ var StreamChat = class _StreamChat {
15448
15722
  ...defaultOptions,
15449
15723
  ...options
15450
15724
  }
15451
- }
15725
+ },
15726
+ requestOptions
15452
15727
  );
15453
15728
  this.state.updateUsers(data.users);
15454
15729
  return data;
@@ -15494,13 +15769,14 @@ var StreamChat = class _StreamChat {
15494
15769
  * searchUserGroups - Search user groups by prefix for autocomplete
15495
15770
  *
15496
15771
  * @param {SearchUserGroupsOptions} options The search options
15497
- *
15772
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15498
15773
  * @return {Promise<SearchUserGroupsResponse>} User Group Search Response
15499
15774
  */
15500
- async searchUserGroups(options) {
15775
+ async searchUserGroups(options, requestOptions = {}) {
15501
15776
  return await this.get(
15502
15777
  this.baseURL + "/usergroups/search",
15503
- options
15778
+ options,
15779
+ requestOptions
15504
15780
  );
15505
15781
  }
15506
15782
  /**
@@ -15620,10 +15896,11 @@ var StreamChat = class _StreamChat {
15620
15896
  * @param {ChannelSort} [sort] Sort options, for instance {created_at: -1}.
15621
15897
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_updated: -1}, {created_at: 1}]
15622
15898
  * @param {ChannelOptions} [options] Options object. Can include predefined_filter, filter_values, and sort_values for using predefined filters.
15899
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15623
15900
  *
15624
15901
  * @return {Promise<QueryChannelsAPIResponse>} full search channels response
15625
15902
  */
15626
- async queryChannelsRequestWithResponse(filterConditions, sort = [], options = {}) {
15903
+ async queryChannelsRequestWithResponse(filterConditions, sort = [], options = {}, requestOptions = {}) {
15627
15904
  const defaultOptions = {
15628
15905
  state: true,
15629
15906
  watch: true,
@@ -15648,7 +15925,11 @@ var StreamChat = class _StreamChat {
15648
15925
  ...defaultOptions,
15649
15926
  ...restOptions
15650
15927
  };
15651
- return await this.post(this.baseURL + "/channels", payload);
15928
+ return await this.post(
15929
+ this.baseURL + "/channels",
15930
+ payload,
15931
+ requestOptions
15932
+ );
15652
15933
  }
15653
15934
  /**
15654
15935
  * queryChannelsRequest - Queries channels and returns the raw channel response list.
@@ -15677,7 +15958,9 @@ var StreamChat = class _StreamChat {
15677
15958
  const queryChannelsResponse = await this.queryChannelsRequestWithResponse(
15678
15959
  filterConditions,
15679
15960
  sort,
15680
- options
15961
+ options,
15962
+ // stateOptions is never serialized, so it carries the abort signal
15963
+ { signal: stateOptions.signal }
15681
15964
  );
15682
15965
  const channels = queryChannelsResponse.channels;
15683
15966
  this.dispatchEvent({
@@ -15794,10 +16077,11 @@ var StreamChat = class _StreamChat {
15794
16077
  * @param {ChannelFilters} filterConditions MongoDB style filter conditions
15795
16078
  * @param {MessageFilters | string} query search query or object MongoDB style filters
15796
16079
  * @param {SearchOptions} [options] Option object, {user_id: 'tommaso'}
16080
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15797
16081
  *
15798
16082
  * @return {Promise<SearchAPIResponse>} search messages response
15799
16083
  */
15800
- async search(filterConditions, query, options = {}) {
16084
+ async search(filterConditions, query, options = {}, requestOptions = {}) {
15801
16085
  if (options.offset && options.next) {
15802
16086
  throw Error(`Cannot specify offset with next`);
15803
16087
  }
@@ -15814,7 +16098,11 @@ var StreamChat = class _StreamChat {
15814
16098
  throw Error(`Invalid type ${typeof query} for query parameter`);
15815
16099
  }
15816
16100
  await this.wsPromise;
15817
- return await this.get(this.baseURL + "/search", { payload });
16101
+ return await this.get(
16102
+ this.baseURL + "/search",
16103
+ { payload },
16104
+ requestOptions
16105
+ );
15818
16106
  }
15819
16107
  /**
15820
16108
  * setLocalDevice - Set the device info for the current client(device) that will be sent via WS connection automatically
@@ -16255,6 +16543,11 @@ var StreamChat = class _StreamChat {
16255
16543
  }
16256
16544
  /**
16257
16545
  * unflagMessage - unflag a message
16546
+ *
16547
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16548
+ * the backend - calling this resolves successfully but does not remove the flag.
16549
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16550
+ *
16258
16551
  * @param {string} targetMessageID
16259
16552
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16260
16553
  * @returns {Promise<APIResponse>}
@@ -16267,6 +16560,11 @@ var StreamChat = class _StreamChat {
16267
16560
  }
16268
16561
  /**
16269
16562
  * unflagUser - unflag a user
16563
+ *
16564
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16565
+ * the backend - calling this resolves successfully but does not remove the flag.
16566
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16567
+ *
16270
16568
  * @param {string} targetID
16271
16569
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16272
16570
  * @returns {Promise<APIResponse>}
@@ -16551,7 +16849,10 @@ var StreamChat = class _StreamChat {
16551
16849
  if (!message.id) {
16552
16850
  throw Error("Please specify the message.id when calling updateMessage");
16553
16851
  }
16554
- const payload = toUpdatedMessagePayload(message);
16852
+ const payload = sanitizeOutgoingAttachments({
16853
+ client: this,
16854
+ message: toUpdatedMessagePayload(message)
16855
+ });
16555
16856
  if (typeof partialUserOrUserId === "string") {
16556
16857
  payload.user_id = partialUserOrUserId;
16557
16858
  } else if (typeof partialUserOrUserId?.id === "string") {
@@ -16851,6 +17152,7 @@ var StreamChat = class _StreamChat {
16851
17152
  headers: axiosRequestConfigHeaders,
16852
17153
  ...axiosRequestConfigRest
16853
17154
  } = this.options.axiosRequestConfig || {};
17155
+ const resolvedSignal = options.config?.signal ?? axiosRequestConfigRest.signal ?? signal;
16854
17156
  return {
16855
17157
  params: {
16856
17158
  user_id: this.userID,
@@ -16866,9 +17168,9 @@ var StreamChat = class _StreamChat {
16866
17168
  ...options.headers,
16867
17169
  ...axiosRequestConfigHeaders || {}
16868
17170
  },
16869
- ...signal ? { signal } : {},
16870
17171
  ...options.config,
16871
- ...axiosRequestConfigRest || {}
17172
+ ...axiosRequestConfigRest,
17173
+ ...resolvedSignal ? { signal: resolvedSignal } : {}
16872
17174
  };
16873
17175
  }
16874
17176
  _getToken() {
@@ -17008,8 +17310,12 @@ var StreamChat = class _StreamChat {
17008
17310
  *
17009
17311
  * @returns {Promise<APIResponse>}
17010
17312
  */
17011
- searchRoles(options) {
17012
- return this.get(`${this.baseURL}/roles/search`, options);
17313
+ searchRoles(options, requestOptions = {}) {
17314
+ return this.get(
17315
+ `${this.baseURL}/roles/search`,
17316
+ options,
17317
+ requestOptions
17318
+ );
17013
17319
  }
17014
17320
  /** deleteRole - deletes a custom role
17015
17321
  *
@@ -18362,11 +18668,12 @@ var OfflineError = class extends Error {
18362
18668
  };
18363
18669
 
18364
18670
  // src/offline-support/offline_sync_manager.ts
18365
- var import_axios4 = require("axios");
18671
+ var import_axios5 = require("axios");
18366
18672
  var OfflineDBSyncManager = class {
18367
18673
  constructor({
18368
18674
  client,
18369
- offlineDb
18675
+ offlineDb,
18676
+ syncMaxEventCount
18370
18677
  }) {
18371
18678
  this.syncStatus = false;
18372
18679
  this.connectionChangedListener = null;
@@ -18483,13 +18790,23 @@ var OfflineDBSyncManager = class {
18483
18790
  await this.offlineDb.resetDB();
18484
18791
  } else {
18485
18792
  const result = await this.client.sync(cids, lastSyncedAtDate.toISOString());
18486
- const queryPromises = result.events.map(
18487
- (event) => this.offlineDb.handleEvent({ event, execute: false })
18488
- );
18489
- const queriesArray = await Promise.all(queryPromises);
18490
- const queries = queriesArray.flat();
18491
- if (queries.length) {
18492
- await this.offlineDb.executeSqlBatch(queries);
18793
+ const { syncMaxEventCount } = this;
18794
+ const exceedsLimit = typeof syncMaxEventCount === "number" && syncMaxEventCount > 0 && result.events.length > syncMaxEventCount;
18795
+ if (exceedsLimit) {
18796
+ this.client.logger(
18797
+ "warn",
18798
+ `Skipping sync event replay: received ${result.events.length} events, which exceeds the configured limit of ${syncMaxEventCount}. Visible channels are refreshed on reconnect instead.`,
18799
+ { tags: ["sync", "offline"] }
18800
+ );
18801
+ } else {
18802
+ const queryPromises = result.events.map(
18803
+ (event) => this.offlineDb.handleEvent({ event, execute: false })
18804
+ );
18805
+ const queriesArray = await Promise.all(queryPromises);
18806
+ const queries = queriesArray.flat();
18807
+ if (queries.length) {
18808
+ await this.offlineDb.executeSqlBatch(queries);
18809
+ }
18493
18810
  }
18494
18811
  }
18495
18812
  }
@@ -18499,7 +18816,7 @@ var OfflineDBSyncManager = class {
18499
18816
  });
18500
18817
  } catch (e) {
18501
18818
  console.log("An error has occurred while syncing the DB.", e);
18502
- if ((0, import_axios4.isAxiosError)(e) && e.code === "ECONNABORTED") {
18819
+ if ((0, import_axios5.isAxiosError)(e) && e.code === "ECONNABORTED") {
18503
18820
  return;
18504
18821
  }
18505
18822
  const error = e;
@@ -18533,12 +18850,16 @@ var OfflineDBSyncManager = class {
18533
18850
  };
18534
18851
  this.client = client;
18535
18852
  this.offlineDb = offlineDb;
18853
+ this.syncMaxEventCount = syncMaxEventCount;
18536
18854
  }
18537
18855
  };
18538
18856
 
18539
18857
  // src/offline-support/offline_support_api.ts
18540
18858
  var AbstractOfflineDB = class {
18541
- constructor({ client }) {
18859
+ constructor({
18860
+ client,
18861
+ syncMaxEventCount
18862
+ }) {
18542
18863
  /**
18543
18864
  * Initializes the DB as well as its syncManager for a given userId.
18544
18865
  * It will update the DBs reactive state with initialization values.
@@ -19004,6 +19325,7 @@ var AbstractOfflineDB = class {
19004
19325
  return this.handleMessageUpdatedEvent({ event, execute });
19005
19326
  }
19006
19327
  if (type === "message.read" || type === "notification.mark_read") {
19328
+ if (event.thread) return [];
19007
19329
  return this.handleRead({ event, unreadMessages: 0, execute });
19008
19330
  }
19009
19331
  if (type === "message.read_locally") {
@@ -19228,7 +19550,11 @@ var AbstractOfflineDB = class {
19228
19550
  }
19229
19551
  };
19230
19552
  this.client = client;
19231
- this.syncManager = new OfflineDBSyncManager({ client, offlineDb: this });
19553
+ this.syncManager = new OfflineDBSyncManager({
19554
+ client,
19555
+ offlineDb: this,
19556
+ syncMaxEventCount
19557
+ });
19232
19558
  this.state = new StateStore({
19233
19559
  initialized: false,
19234
19560
  userId: this.client.userID
@@ -19567,6 +19893,7 @@ var LiveLocationManager = _LiveLocationManager;
19567
19893
  createMessageComposerStateCompositionMiddleware,
19568
19894
  createPollComposerStateMiddleware,
19569
19895
  createPostUploadAttachmentEnrichmentMiddleware,
19896
+ createSendWithPendingUploadsAttachmentsMiddleware,
19570
19897
  createSharedLocationCompositionMiddleware,
19571
19898
  createTextComposerCompositionMiddleware,
19572
19899
  createTextComposerPreValidationMiddleware,
@@ -19595,6 +19922,7 @@ var LiveLocationManager = _LiveLocationManager;
19595
19922
  getTokenizedSuggestionDisplayName,
19596
19923
  getTriggerCharWithToken,
19597
19924
  gunzipPayload,
19925
+ hasPending,
19598
19926
  insertItemWithTrigger,
19599
19927
  isAudioAttachment,
19600
19928
  isBlobButNotFile,
@@ -19602,6 +19930,7 @@ var LiveLocationManager = _LiveLocationManager;
19602
19930
  isFileAttachment,
19603
19931
  isFileList,
19604
19932
  isFileReference,
19933
+ isFinishedUpload,
19605
19934
  isGiphyAttachment,
19606
19935
  isImageAttachment,
19607
19936
  isImageFile,
@@ -19614,9 +19943,11 @@ var LiveLocationManager = _LiveLocationManager;
19614
19943
  isLocalVoiceRecordingAttachment,
19615
19944
  isOwnUser,
19616
19945
  isPatch,
19946
+ isPendingUpload,
19617
19947
  isScrapedContent,
19618
19948
  isSharedLocationResponse,
19619
19949
  isTargetedOptionTextUpdate,
19950
+ isUploadCancellation,
19620
19951
  isUploadedAttachment,
19621
19952
  isVideoAttachment,
19622
19953
  isVoiceRecordingAttachment,
@@ -19636,11 +19967,14 @@ var LiveLocationManager = _LiveLocationManager;
19636
19967
  readFileAsArrayBuffer,
19637
19968
  removeDiacritics,
19638
19969
  replaceWordWithEntity,
19970
+ settled,
19639
19971
  stripCommandFromText,
19640
19972
  stripMentionTokens,
19641
19973
  textIsEmpty,
19642
19974
  timeLeftMs,
19643
19975
  verifyAndParseWebhook,
19644
- verifySignature
19976
+ verifySignature,
19977
+ withCancellation,
19978
+ withoutConcurrency
19645
19979
  });
19646
19980
  //# sourceMappingURL=index.node.js.map