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
@@ -316,7 +316,7 @@ var ChannelBatchUpdater = class {
316
316
 
317
317
  // src/client.ts
318
318
  var import_https = __toESM(require_https());
319
- import axios3 from "axios";
319
+ import axios4 from "axios";
320
320
 
321
321
  // src/utils.ts
322
322
  import FormData from "form-data";
@@ -575,6 +575,38 @@ function unformatMessage(message) {
575
575
  quoted_message: toMessageResponseBase(message.quoted_message)
576
576
  };
577
577
  }
578
+ var isRemoteUrl = (url) => !!url && /^https?:\/\//i.test(url.trim());
579
+ var sanitizeOutgoingAttachments = ({
580
+ client,
581
+ message
582
+ }) => {
583
+ const attachments = message.attachments;
584
+ if (!attachments?.length) return message;
585
+ const unresolved = [];
586
+ let changed = false;
587
+ const sanitized = attachments.reduce((acc, attachment) => {
588
+ const { localMetadata, ...rest } = attachment;
589
+ if (!localMetadata) {
590
+ acc.push(attachment);
591
+ return acc;
592
+ }
593
+ changed = true;
594
+ const hasRemoteSource = isRemoteUrl(rest.asset_url) || isRemoteUrl(rest.image_url) || isRemoteUrl(rest.og_scrape_url) || isRemoteUrl(rest.title_link);
595
+ if (hasRemoteSource) {
596
+ acc.push(rest);
597
+ } else {
598
+ unresolved.push(rest);
599
+ }
600
+ return acc;
601
+ }, []);
602
+ if (unresolved.length) {
603
+ const text = `Dropped ${unresolved.length} attachment(s) whose upload never completed. Composing with pending uploads requires awaiting them before sending.`;
604
+ const extraData = { attachments: unresolved, message, tags: ["attachments"] };
605
+ client.logger("warn", text, extraData);
606
+ console.warn(text, extraData);
607
+ }
608
+ return changed ? { ...message, attachments: sanitized } : message;
609
+ };
578
610
  var localMessageToNewMessagePayload = (localMessage) => {
579
611
  const {
580
612
  // Remove all timestamp fields and client-specific fields.
@@ -783,7 +815,8 @@ var debounce = (fn, timeout = 0, { leading = false, trailing = true } = {}) => {
783
815
  }
784
816
  runningTimeout = null;
785
817
  };
786
- runningTimeout = setTimeout(timeoutHandler, timeout);
818
+ const delay = typeof timeout === "function" ? timeout(...args) : timeout;
819
+ runningTimeout = setTimeout(timeoutHandler, delay);
787
820
  return lastResult;
788
821
  };
789
822
  debouncedFn.cancel = () => {
@@ -2346,6 +2379,12 @@ var CooldownTimer = class extends WithSubscriptions {
2346
2379
  var isScrapedContent = (attachment) => !!attachment?.og_scrape_url || !!attachment?.title_link;
2347
2380
  var isLocalAttachment = (attachment) => !!attachment?.localMetadata?.id;
2348
2381
  var isLocalUploadAttachment = (attachment) => !!attachment?.localMetadata?.uploadState;
2382
+ var PENDING_UPLOAD_STATES = [
2383
+ "pending",
2384
+ "uploading"
2385
+ ];
2386
+ var isPendingUpload = (attachment) => isLocalUploadAttachment(attachment) && PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
2387
+ var isFinishedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "finished";
2349
2388
  var isFileAttachment = (attachment, supportedVideoFormat = []) => attachment.type === "file" || !!(attachment.mime_type && supportedVideoFormat.indexOf(attachment.mime_type) === -1 && attachment.type !== "video");
2350
2389
  var isLocalFileAttachment = (attachment) => isFileAttachment(attachment) && isLocalAttachment(attachment);
2351
2390
  var isImageAttachment = (attachment) => attachment.type === "image" && !isScrapedContent(attachment);
@@ -2421,7 +2460,7 @@ var ensureIsLocalAttachment = (attachment) => {
2421
2460
  };
2422
2461
 
2423
2462
  // src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts
2424
- var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2463
+ var createPostUploadAttachmentEnrichmentMiddleware = (composer) => ({
2425
2464
  id: "stream-io/attachment-manager-middleware/post-upload-enrichment",
2426
2465
  handlers: {
2427
2466
  postProcess: ({
@@ -2434,9 +2473,10 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2434
2473
  if (error) return forward();
2435
2474
  if (!attachment || !response) return discard();
2436
2475
  const enrichedAttachment = { ...attachment };
2437
- const previewUri = attachment.localMetadata.previewUri;
2438
- if (previewUri) {
2439
- if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
2476
+ const { id, previewUri } = attachment.localMetadata;
2477
+ const attachmentIsStillInComposer = !composer || !!composer.attachmentManager.attachmentsById[id];
2478
+ if (previewUri && attachmentIsStillInComposer) {
2479
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
2440
2480
  delete enrichedAttachment.localMetadata.previewUri;
2441
2481
  }
2442
2482
  if (isLocalImageAttachment(attachment)) {
@@ -2459,6 +2499,12 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2459
2499
  var withoutConcurrency = createRunner(wrapWithContinuationTracking);
2460
2500
  var withCancellation = createRunner(wrapWithCancellation);
2461
2501
  var pendingPromises = /* @__PURE__ */ new Map();
2502
+ function hasPending(tag) {
2503
+ return pendingPromises.has(tag);
2504
+ }
2505
+ async function settled(tag) {
2506
+ await pendingPromises.get(tag)?.promise;
2507
+ }
2462
2508
  function createRunner(wrapper) {
2463
2509
  return function run(tag, cb) {
2464
2510
  const { cb: wrapped, onContinued } = wrapper(tag, cb);
@@ -2501,6 +2547,16 @@ var MiddlewareExecutor = class {
2501
2547
  this.middleware = [];
2502
2548
  this.id = generateUUIDv4();
2503
2549
  }
2550
+ /**
2551
+ * The middleware currently installed, in execution order.
2552
+ *
2553
+ * Read-only: use `use` / `insert` / `replace` / `setOrder` / `remove` to change the chain. It
2554
+ * exists so components can ask what the chain is capable of - a middleware may declare
2555
+ * behaviour its host has to account for, and the host has no other way to find out.
2556
+ */
2557
+ get installedMiddleware() {
2558
+ return this.middleware;
2559
+ }
2504
2560
  use(middleware) {
2505
2561
  this.middleware = this.middleware.concat(middleware);
2506
2562
  return this;
@@ -2604,6 +2660,142 @@ var MiddlewareExecutor = class {
2604
2660
  }
2605
2661
  };
2606
2662
 
2663
+ // src/uploadManager.ts
2664
+ import axios from "axios";
2665
+ var isUploadCancellation = (error) => axios.isCancel(error) || error instanceof Error && error.name === "AbortError";
2666
+ var initState = () => ({ uploads: {} });
2667
+ var upsertById = (uploads, record) => ({
2668
+ ...uploads,
2669
+ [record.id]: { ...uploads[record.id], ...record }
2670
+ });
2671
+ var updateById = (uploads, record) => {
2672
+ if (!(record.id in uploads)) return null;
2673
+ const current = uploads[record.id];
2674
+ return { ...uploads, [record.id]: { ...current, ...record } };
2675
+ };
2676
+ var UploadManager = class {
2677
+ constructor(client) {
2678
+ this.client = client;
2679
+ this.inFlightUploads = /* @__PURE__ */ new Map();
2680
+ this.getUpload = (id) => this.uploads[id];
2681
+ /**
2682
+ * Clears all upload records.
2683
+ * Invoked when the user disconnects so a later session does not inherit stale upload state.
2684
+ * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
2685
+ */
2686
+ this.reset = () => {
2687
+ for (const { abortController } of this.inFlightUploads.values()) {
2688
+ abortController.abort();
2689
+ }
2690
+ this.inFlightUploads.clear();
2691
+ this.state.next(initState());
2692
+ };
2693
+ /**
2694
+ * Removes the upload record for `id` if present.
2695
+ * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
2696
+ */
2697
+ this.deleteUploadRecord = (id) => {
2698
+ const flight = this.inFlightUploads.get(id);
2699
+ if (flight) {
2700
+ this.inFlightUploads.delete(id);
2701
+ flight.abortController.abort();
2702
+ }
2703
+ this.state.next((current) => {
2704
+ if (!(id in current.uploads)) return current;
2705
+ const uploads = { ...current.uploads };
2706
+ delete uploads[id];
2707
+ return { ...current, uploads };
2708
+ });
2709
+ };
2710
+ /**
2711
+ * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
2712
+ * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
2713
+ * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
2714
+ */
2715
+ this.upload = ({
2716
+ id,
2717
+ channelCid,
2718
+ file
2719
+ }) => {
2720
+ const existing = this.inFlightUploads.get(id);
2721
+ if (existing) return existing.promise;
2722
+ let resolvePromise;
2723
+ let rejectPromise;
2724
+ const promise = new Promise((resolve, reject) => {
2725
+ resolvePromise = resolve;
2726
+ rejectPromise = reject;
2727
+ });
2728
+ const abortController = new AbortController();
2729
+ this.inFlightUploads.set(id, { promise, abortController });
2730
+ void (async () => {
2731
+ const attachmentManager = this.resolveAttachmentManager(channelCid);
2732
+ const trackProgress = attachmentManager.config.trackUploadProgress;
2733
+ try {
2734
+ this.upsertUpload({
2735
+ uploadConfirmationPending: false,
2736
+ id,
2737
+ uploadProgress: trackProgress ? 0 : void 0
2738
+ });
2739
+ const onProgress = trackProgress ? (progress) => {
2740
+ this.updateUpload({
2741
+ id,
2742
+ uploadProgress: progress,
2743
+ // Only a number says anything about the flush, so a report without one leaves
2744
+ // the flag alone: `undefined` means the transport cannot measure this upload,
2745
+ // not that bytes were un-sent. Lowering it there would drop a UI that had
2746
+ // already gone indeterminate back to a determinate one with nothing to show.
2747
+ //
2748
+ // The record is removed in the `finally` below, so a record that still exists
2749
+ // while reporting 100% can only mean "flushed, awaiting the response".
2750
+ ...typeof progress === "number" ? { uploadConfirmationPending: progress >= 100 } : {}
2751
+ });
2752
+ } : void 0;
2753
+ const uploadRequestOptions = {
2754
+ abortSignal: abortController.signal,
2755
+ ...onProgress ? { onProgress } : {}
2756
+ };
2757
+ const response = await attachmentManager.doUploadRequest(
2758
+ file,
2759
+ uploadRequestOptions
2760
+ );
2761
+ resolvePromise(response);
2762
+ } catch (error) {
2763
+ rejectPromise(error);
2764
+ } finally {
2765
+ this.inFlightUploads.delete(id);
2766
+ this.deleteUploadRecord(id);
2767
+ }
2768
+ })();
2769
+ return promise;
2770
+ };
2771
+ this.upsertUpload = (record) => {
2772
+ this.state.partialNext({
2773
+ uploads: upsertById(this.uploads, record)
2774
+ });
2775
+ };
2776
+ this.updateUpload = (record) => {
2777
+ this.state.next((current) => {
2778
+ const nextUploads = updateById(current.uploads, record);
2779
+ if (!nextUploads) return current;
2780
+ return { ...current, uploads: nextUploads };
2781
+ });
2782
+ };
2783
+ this.state = new StateStore(initState());
2784
+ }
2785
+ resolveAttachmentManager(channelCid) {
2786
+ const colon = channelCid.indexOf(":");
2787
+ if (colon <= 0 || colon === channelCid.length - 1) {
2788
+ throw new Error(`Invalid channelCid: ${channelCid}`);
2789
+ }
2790
+ const channelType = channelCid.slice(0, colon);
2791
+ const channelId = channelCid.slice(colon + 1);
2792
+ return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
2793
+ }
2794
+ get uploads() {
2795
+ return this.state.getLatestValue().uploads;
2796
+ }
2797
+ };
2798
+
2607
2799
  // src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts
2608
2800
  var createUploadErrorHandlerMiddleware = (composer) => ({
2609
2801
  id: "stream-io/attachment-manager-middleware/upload-error",
@@ -2616,6 +2808,7 @@ var createUploadErrorHandlerMiddleware = (composer) => ({
2616
2808
  const { attachment, error } = state;
2617
2809
  if (!error) return forward();
2618
2810
  if (!attachment) return discard();
2811
+ if (isUploadCancellation(error)) return forward();
2619
2812
  const reason = error instanceof Error ? error.message : "unknown error";
2620
2813
  composer.client.notifications.addError({
2621
2814
  message: "Error uploading attachment",
@@ -2640,7 +2833,7 @@ var AttachmentPostUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2640
2833
  super();
2641
2834
  this.use([
2642
2835
  createUploadErrorHandlerMiddleware(composer),
2643
- createPostUploadAttachmentEnrichmentMiddleware()
2836
+ createPostUploadAttachmentEnrichmentMiddleware(composer)
2644
2837
  ]);
2645
2838
  }
2646
2839
  };
@@ -3088,7 +3281,7 @@ function mergeWithDiff(target, source, customizer) {
3088
3281
  }
3089
3282
 
3090
3283
  // src/messageComposer/attachmentManager.ts
3091
- var initState = ({
3284
+ var initState2 = ({
3092
3285
  message
3093
3286
  }) => ({
3094
3287
  attachments: (message?.attachments ?? [])?.filter(({ og_scrape_url }) => !og_scrape_url).map((att) => {
@@ -3116,13 +3309,14 @@ var _AttachmentManager = class _AttachmentManager {
3116
3309
  ...attachment,
3117
3310
  localMetadata: {
3118
3311
  ...attachment.localMetadata,
3312
+ uploadConfirmationPending: false,
3119
3313
  uploadProgress: void 0,
3120
3314
  uploadState: "failed"
3121
3315
  }
3122
3316
  };
3123
3317
  };
3124
3318
  this.initState = ({ message } = {}) => {
3125
- this.state.next(initState({ message }));
3319
+ this.state.next(initState2({ message }));
3126
3320
  };
3127
3321
  this.getSnapshot = () => {
3128
3322
  const state = this.state.getLatestValue();
@@ -3199,8 +3393,22 @@ var _AttachmentManager = class _AttachmentManager {
3199
3393
  this.state.partialNext({ attachments });
3200
3394
  }
3201
3395
  };
3396
+ /**
3397
+ * Releases a local preview URL the SDK minted, if it minted one.
3398
+ *
3399
+ * Only `blob:` URLs are ours to revoke: `toLocalUploadAttachment` puts a
3400
+ * `URL.createObjectURL` result in `previewUri` for a `File`, but for a React Native
3401
+ * `FileReference` it stores the caller's own native file URI, which must be left alone.
3402
+ */
3403
+ this.revokePreviewUri = (attachment) => {
3404
+ const previewUri = attachment.localMetadata?.previewUri;
3405
+ if (previewUri?.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
3406
+ };
3202
3407
  this.removeAttachments = (localAttachmentIds) => {
3203
3408
  if (!localAttachmentIds.length) return;
3409
+ const removedAttachments = this.attachments.filter(
3410
+ (attachment) => localAttachmentIds.includes(attachment.localMetadata?.id)
3411
+ );
3204
3412
  this.state.partialNext({
3205
3413
  attachments: this.attachments.filter(
3206
3414
  (attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id)
@@ -3209,6 +3417,7 @@ var _AttachmentManager = class _AttachmentManager {
3209
3417
  for (const id of localAttachmentIds) {
3210
3418
  this.client.uploadManager.deleteUploadRecord(id);
3211
3419
  }
3420
+ removedAttachments.forEach(this.revokePreviewUri);
3212
3421
  };
3213
3422
  this.getUploadConfigCheck = async (fileLike) => {
3214
3423
  const client = this.channel.getClient();
@@ -3360,21 +3569,27 @@ var _AttachmentManager = class _AttachmentManager {
3360
3569
  localMetadata: {
3361
3570
  ...attachment.localMetadata,
3362
3571
  uploadState: "failed",
3363
- uploadProgress: void 0
3572
+ uploadProgress: void 0,
3573
+ // `false`, not `undefined`: these updates go through `updateAttachment`, which merges
3574
+ // via `mergeWith` — and that skips `undefined` source values, so `undefined` would
3575
+ // leave a stale `true` in place.
3576
+ uploadConfirmationPending: false
3364
3577
  }
3365
3578
  };
3366
- this.client.notifications.addError({
3367
- message: "Error uploading attachment",
3368
- origin: {
3369
- emitter: "AttachmentManager",
3370
- context: { attachment, failedAttachment }
3371
- },
3372
- options: {
3373
- type: "api:attachment:upload:failed",
3374
- metadata: { reason },
3375
- originalError: error instanceof Error ? error : void 0
3376
- }
3377
- });
3579
+ if (!isUploadCancellation(error)) {
3580
+ this.client.notifications.addError({
3581
+ message: "Error uploading attachment",
3582
+ origin: {
3583
+ emitter: "AttachmentManager",
3584
+ context: { attachment, failedAttachment }
3585
+ },
3586
+ options: {
3587
+ type: "api:attachment:upload:failed",
3588
+ metadata: { reason },
3589
+ originalError: error instanceof Error ? error : void 0
3590
+ }
3591
+ });
3592
+ }
3378
3593
  this.updateAttachment(failedAttachment);
3379
3594
  return failedAttachment;
3380
3595
  }
@@ -3387,7 +3602,9 @@ var _AttachmentManager = class _AttachmentManager {
3387
3602
  localMetadata: {
3388
3603
  ...attachment.localMetadata,
3389
3604
  uploadState: "finished",
3390
- uploadProgress: void 0
3605
+ uploadProgress: void 0,
3606
+ // See the note in the failure branch: `undefined` cannot clear through `mergeWith`.
3607
+ uploadConfirmationPending: false
3391
3608
  }
3392
3609
  };
3393
3610
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3436,7 +3653,9 @@ var _AttachmentManager = class _AttachmentManager {
3436
3653
  localMetadata: {
3437
3654
  ...attachment.localMetadata,
3438
3655
  uploadState: error ? "failed" : "finished",
3439
- uploadProgress: void 0
3656
+ uploadProgress: void 0,
3657
+ // See the note in `uploadAttachment`: `undefined` cannot clear through `mergeWith`.
3658
+ uploadConfirmationPending: false
3440
3659
  }
3441
3660
  },
3442
3661
  error,
@@ -3461,7 +3680,7 @@ var _AttachmentManager = class _AttachmentManager {
3461
3680
  );
3462
3681
  };
3463
3682
  this.composer = composer;
3464
- this.state = new StateStore(initState({ message }));
3683
+ this.state = new StateStore(initState2({ message }));
3465
3684
  this.attachmentsByIdGetterCache = { attachmentsById: {}, attachments: [] };
3466
3685
  this.preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor({
3467
3686
  composer
@@ -3566,7 +3785,8 @@ var _AttachmentManager = class _AttachmentManager {
3566
3785
  localMetadata: {
3567
3786
  ...attachment.localMetadata,
3568
3787
  uploadState: "uploading",
3569
- uploadProgress: this.config.trackUploadProgress ? 0 : void 0
3788
+ uploadProgress: this.config.trackUploadProgress ? 0 : void 0,
3789
+ uploadConfirmationPending: false
3570
3790
  }
3571
3791
  }
3572
3792
  ]);
@@ -3579,7 +3799,8 @@ var _AttachmentManager = class _AttachmentManager {
3579
3799
  localMetadata: {
3580
3800
  ...attachment.localMetadata,
3581
3801
  uploadState: "uploading",
3582
- uploadProgress: nextUpload.uploadProgress
3802
+ uploadProgress: nextUpload.uploadProgress,
3803
+ uploadConfirmationPending: nextUpload.uploadConfirmationPending
3583
3804
  }
3584
3805
  });
3585
3806
  }
@@ -3633,6 +3854,50 @@ var localAttachmentToAttachment = (localAttachment) => {
3633
3854
  const { localMetadata, ...attachment } = localAttachment;
3634
3855
  return attachment;
3635
3856
  };
3857
+ var composeWithPendingUploads = ({
3858
+ composer,
3859
+ state
3860
+ }) => {
3861
+ const composerIsKeptAsDraft = !!composer.pollId;
3862
+ const relevantAttachments = composer.attachmentManager.attachments.filter(
3863
+ (attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && isPendingUpload(attachment)
3864
+ );
3865
+ const localAttachments = (state.localMessage.attachments ?? []).concat(
3866
+ relevantAttachments.map(
3867
+ (attachment) => isPendingUpload(attachment) ? attachment : localAttachmentToAttachment(attachment)
3868
+ )
3869
+ );
3870
+ const messageAttachments = (state.message.attachments ?? []).concat(
3871
+ relevantAttachments.filter(isFinishedUpload).map(localAttachmentToAttachment)
3872
+ );
3873
+ if (!localAttachments.length && !messageAttachments.length) return state;
3874
+ return {
3875
+ ...state,
3876
+ localMessage: localAttachments.length ? {
3877
+ ...state.localMessage,
3878
+ attachments: localAttachments
3879
+ } : state.localMessage,
3880
+ message: messageAttachments.length ? {
3881
+ ...state.message,
3882
+ attachments: messageAttachments
3883
+ } : state.message
3884
+ };
3885
+ };
3886
+ var createSendWithPendingUploadsAttachmentsMiddleware = (composer) => ({
3887
+ allowsPendingUploads: true,
3888
+ id: "stream-io/message-composer-middleware/attachments",
3889
+ handlers: {
3890
+ compose: ({
3891
+ state,
3892
+ next,
3893
+ forward
3894
+ }) => {
3895
+ const { attachmentManager } = composer;
3896
+ if (!attachmentManager) return forward();
3897
+ return next(composeWithPendingUploads({ composer, state }));
3898
+ }
3899
+ }
3900
+ });
3636
3901
  var createAttachmentsCompositionMiddleware = (composer) => ({
3637
3902
  id: "stream-io/message-composer-middleware/attachments",
3638
3903
  handlers: {
@@ -3849,7 +4114,7 @@ var getInitialMentions = (message) => {
3849
4114
  };
3850
4115
  var isSameMentionEntity = (first, second) => first.id === second.id && first.mentionType === second.mentionType;
3851
4116
  var getMentionsFromState = (state) => state.mentions ?? state.mentionedUsers.map(userResponseToMentionEntity);
3852
- var initState2 = ({
4117
+ var initState3 = ({
3853
4118
  composer,
3854
4119
  message
3855
4120
  }) => {
@@ -3878,7 +4143,7 @@ var initState2 = ({
3878
4143
  var TextComposer = class {
3879
4144
  constructor({ composer, message }) {
3880
4145
  this.initState = ({ message } = {}) => {
3881
- this.state.next(initState2({ composer: this.composer, message }));
4146
+ this.state.next(initState3({ composer: this.composer, message }));
3882
4147
  };
3883
4148
  this.getSnapshot = (state = this.state.getLatestValue()) => state;
3884
4149
  this.restoreSnapshot = (snapshot) => {
@@ -4071,7 +4336,7 @@ var TextComposer = class {
4071
4336
  this.commitState(output.state);
4072
4337
  };
4073
4338
  this.composer = composer;
4074
- this.state = new StateStore(initState2({ composer, message }));
4339
+ this.state = new StateStore(initState3({ composer, message }));
4075
4340
  this.middlewareExecutor = new TextComposerMiddlewareExecutor({ composer });
4076
4341
  }
4077
4342
  get channel() {
@@ -4221,13 +4486,41 @@ function isErrorResponse(res) {
4221
4486
 
4222
4487
  // src/search/BaseSearchSource.ts
4223
4488
  var DEFAULT_SEARCH_SOURCE_OPTIONS = {
4224
- debounceMs: 300,
4225
4489
  pageSize: 10,
4226
4490
  allowEmptySearchString: false,
4227
4491
  resetOnNewSearchQuery: true
4228
4492
  };
4493
+ var resolveDebounceOptions = ({
4494
+ debounceMs,
4495
+ shortQueryDebounceMs,
4496
+ longQueryDebounceMs,
4497
+ shortQueryMaxLength
4498
+ }) => ({
4499
+ shortQueryDebounceMs: shortQueryDebounceMs ?? debounceMs ?? 500,
4500
+ longQueryDebounceMs: longQueryDebounceMs ?? debounceMs ?? 300,
4501
+ shortQueryMaxLength: shortQueryMaxLength ?? 2
4502
+ });
4229
4503
  var BaseSearchSourceBase = class {
4230
4504
  constructor(options) {
4505
+ this.setDebounceOptions = (options = {}) => {
4506
+ const resolved = resolveDebounceOptions(options);
4507
+ this.shortQueryDebounceMs = resolved.shortQueryDebounceMs;
4508
+ this.longQueryDebounceMs = resolved.longQueryDebounceMs;
4509
+ this.shortQueryMaxLength = resolved.shortQueryMaxLength;
4510
+ this.searchDebounced = debounce(
4511
+ this.executeQuery.bind(this),
4512
+ (searchString) => this.getDebounceMs(searchString)
4513
+ );
4514
+ };
4515
+ /**
4516
+ * Short queries match too much and are slow server-side, so they get a longer debounce.
4517
+ * Resolved per call, which is what lets the interval switch as the user keeps typing.
4518
+ */
4519
+ this.getDebounceMs = (searchString) => {
4520
+ const { length } = searchString ?? this.searchQuery;
4521
+ return length <= this.shortQueryMaxLength ? this.shortQueryDebounceMs : this.longQueryDebounceMs;
4522
+ };
4523
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
4231
4524
  this.activate = () => {
4232
4525
  if (this.isActive) return;
4233
4526
  this.state.partialNext({ isActive: true });
@@ -4239,7 +4532,7 @@ var BaseSearchSourceBase = class {
4239
4532
  this.canExecuteQuery = (newSearchString) => {
4240
4533
  const hasNewSearchQuery = typeof newSearchString !== "undefined";
4241
4534
  const searchString = newSearchString ?? this.searchQuery;
4242
- return !!(this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery) && (this.allowEmptySearchString || searchString));
4535
+ return !!(this.isActive && this.canDispatchQuery(hasNewSearchQuery) && (this.allowEmptySearchString || searchString));
4243
4536
  };
4244
4537
  const { pageSize, allowEmptySearchString, resetOnNewSearchQuery } = {
4245
4538
  ...DEFAULT_SEARCH_SOURCE_OPTIONS,
@@ -4249,6 +4542,14 @@ var BaseSearchSourceBase = class {
4249
4542
  this.allowEmptySearchString = allowEmptySearchString;
4250
4543
  this.resetOnNewSearchQuery = resetOnNewSearchQuery;
4251
4544
  this.state = new StateStore(this.initialState);
4545
+ this.setDebounceOptions(options ?? {});
4546
+ }
4547
+ /**
4548
+ * A new search query preempts an in-flight one; pagination waits for it to finish
4549
+ * and needs a next page to fetch.
4550
+ */
4551
+ canDispatchQuery(hasNewSearchQuery) {
4552
+ return hasNewSearchQuery || !this.isLoading && this.hasNext;
4252
4553
  }
4253
4554
  get lastQueryError() {
4254
4555
  return this.state.getLatestValue().lastQueryError;
@@ -4343,21 +4644,30 @@ var BaseSearchSourceBase = class {
4343
4644
  }
4344
4645
  };
4345
4646
  var BaseSearchSource = class extends BaseSearchSourceBase {
4346
- constructor(options) {
4347
- const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
4348
- super(options);
4349
- this.setDebounceOptions = ({ debounceMs }) => {
4350
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
4351
- };
4352
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
4353
- this.setDebounceOptions({ debounceMs });
4647
+ constructor() {
4648
+ super(...arguments);
4649
+ /** Aborts the in-flight request, if any, once a newer query is dispatched. */
4650
+ this.queryAbortController = null;
4651
+ }
4652
+ /** Aborts the in-flight request, if any. Returns true when one was aborted. */
4653
+ abortInFlightQuery() {
4654
+ if (!this.queryAbortController) return false;
4655
+ this.queryAbortController.abort();
4656
+ this.queryAbortController = null;
4657
+ return true;
4658
+ }
4659
+ resetState() {
4660
+ this.abortInFlightQuery();
4661
+ super.resetState();
4354
4662
  }
4355
4663
  async executeQuery(newSearchString) {
4356
4664
  if (!this.canExecuteQuery(newSearchString)) return;
4665
+ this.abortInFlightQuery();
4666
+ const { signal } = this.queryAbortController = new AbortController();
4357
4667
  const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
4358
4668
  let stateUpdate = {};
4359
4669
  try {
4360
- const results = await this.query(searchString);
4670
+ const results = await this.query(searchString, { signal });
4361
4671
  if (!results) return;
4362
4672
  const { items } = results;
4363
4673
  stateUpdate = this.updatePaginationStateFromQuery(results);
@@ -4368,23 +4678,19 @@ var BaseSearchSource = class extends BaseSearchSourceBase {
4368
4678
  stateUpdate.hasNext = false;
4369
4679
  }
4370
4680
  } finally {
4371
- this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
4681
+ if (!signal.aborted) {
4682
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
4683
+ }
4372
4684
  }
4373
4685
  }
4374
4686
  cancelScheduledQuery() {
4375
4687
  this.searchDebounced.cancel();
4688
+ if (this.abortInFlightQuery() && this.isLoading) {
4689
+ this.state.partialNext({ isLoading: false });
4690
+ }
4376
4691
  }
4377
4692
  };
4378
4693
  var BaseSearchSourceSync = class extends BaseSearchSourceBase {
4379
- constructor(options) {
4380
- const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
4381
- super(options);
4382
- this.setDebounceOptions = ({ debounceMs }) => {
4383
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
4384
- };
4385
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
4386
- this.setDebounceOptions({ debounceMs });
4387
- }
4388
4694
  executeQuery(newSearchString) {
4389
4695
  if (!this.canExecuteQuery(newSearchString)) return;
4390
4696
  const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
@@ -4680,13 +4986,13 @@ var UserGroupPaginator = class extends BasePaginator {
4680
4986
  // src/search/ChannelMemberSearchSource.ts
4681
4987
  var ChannelMemberSearchSource = class extends BaseSearchSource {
4682
4988
  constructor(channel, options, filterBuilderOptions = {}) {
4683
- super(options);
4989
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
4990
+ super({ ...restOptions, allowEmptySearchString: true });
4684
4991
  this.type = "members";
4685
- this.canExecuteQuery = (newSearchString) => {
4686
- const hasNewSearchQuery = typeof newSearchString !== "undefined";
4687
- return this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery);
4688
- };
4689
4992
  this.channel = channel;
4993
+ this.filters = filters;
4994
+ this.sort = sort;
4995
+ this.searchOptions = searchOptions;
4690
4996
  this.filterBuilder = new FilterBuilder({
4691
4997
  initialFilterConfig: {
4692
4998
  default: {
@@ -4702,7 +5008,7 @@ var ChannelMemberSearchSource = class extends BaseSearchSource {
4702
5008
  ...filterBuilderOptions
4703
5009
  });
4704
5010
  }
4705
- async query(searchQuery) {
5011
+ async query(searchQuery, queryOptions = {}) {
4706
5012
  const filters = this.filterBuilder.buildFilters({
4707
5013
  baseFilters: this.filters,
4708
5014
  context: {
@@ -4711,7 +5017,12 @@ var ChannelMemberSearchSource = class extends BaseSearchSource {
4711
5017
  });
4712
5018
  const sort = this.sort ?? [];
4713
5019
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
4714
- const { members } = await this.channel.queryMembers(filters ?? {}, sort, options);
5020
+ const { members } = await this.channel.queryMembers(
5021
+ filters ?? {},
5022
+ sort,
5023
+ options,
5024
+ queryOptions
5025
+ );
4715
5026
  return { items: members };
4716
5027
  }
4717
5028
  filterQueryResults(items) {
@@ -4826,9 +5137,13 @@ var SearchController = class {
4826
5137
  // src/search/UserSearchSource.ts
4827
5138
  var UserSearchSource = class extends BaseSearchSource {
4828
5139
  constructor(client, options, filterBuilderOptions = {}) {
4829
- super(options);
5140
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
5141
+ super(restOptions);
4830
5142
  this.type = "users";
4831
5143
  this.client = client;
5144
+ this.filters = filters;
5145
+ this.sort = sort;
5146
+ this.searchOptions = searchOptions;
4832
5147
  this.filterBuilder = new FilterBuilder({
4833
5148
  initialFilterConfig: {
4834
5149
  $or: {
@@ -4844,7 +5159,7 @@ var UserSearchSource = class extends BaseSearchSource {
4844
5159
  ...filterBuilderOptions
4845
5160
  });
4846
5161
  }
4847
- async query(searchQuery) {
5162
+ async query(searchQuery, queryOptions = {}) {
4848
5163
  const filters = this.filterBuilder.buildFilters({
4849
5164
  baseFilters: this.filters,
4850
5165
  context: { searchQuery }
@@ -4857,7 +5172,7 @@ var UserSearchSource = class extends BaseSearchSource {
4857
5172
  sort = { id: 1, ...this.sort };
4858
5173
  }
4859
5174
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
4860
- const { users } = await this.client.queryUsers(filters, sort, options);
5175
+ const { users } = await this.client.queryUsers(filters, sort, options, queryOptions);
4861
5176
  return { items: users };
4862
5177
  }
4863
5178
  filterQueryResults(items) {
@@ -4868,9 +5183,13 @@ var UserSearchSource = class extends BaseSearchSource {
4868
5183
  // src/search/ChannelSearchSource.ts
4869
5184
  var ChannelSearchSource = class extends BaseSearchSource {
4870
5185
  constructor(client, options, filterBuilderOptions = {}) {
4871
- super(options);
5186
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
5187
+ super(restOptions);
4872
5188
  this.type = "channels";
4873
5189
  this.client = client;
5190
+ this.filters = filters;
5191
+ this.sort = sort;
5192
+ this.searchOptions = searchOptions;
4874
5193
  this.filterBuilder = new FilterBuilder({
4875
5194
  ...filterBuilderOptions,
4876
5195
  initialFilterConfig: {
@@ -4882,7 +5201,7 @@ var ChannelSearchSource = class extends BaseSearchSource {
4882
5201
  }
4883
5202
  });
4884
5203
  }
4885
- async query(searchQuery) {
5204
+ async query(searchQuery, queryOptions = {}) {
4886
5205
  const filters = this.filterBuilder.buildFilters({
4887
5206
  baseFilters: {
4888
5207
  ...this.client.userID ? { members: { $in: [this.client.userID] } } : {},
@@ -4892,7 +5211,7 @@ var ChannelSearchSource = class extends BaseSearchSource {
4892
5211
  });
4893
5212
  const sort = this.sort ?? {};
4894
5213
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
4895
- const items = await this.client.queryChannels(filters, sort, options);
5214
+ const items = await this.client.queryChannels(filters, sort, options, queryOptions);
4896
5215
  return { items };
4897
5216
  }
4898
5217
  filterQueryResults(items) {
@@ -4903,9 +5222,24 @@ var ChannelSearchSource = class extends BaseSearchSource {
4903
5222
  // src/search/MessageSearchSource.ts
4904
5223
  var MessageSearchSource = class extends BaseSearchSource {
4905
5224
  constructor(client, options, filterBuilderOptions) {
4906
- super(options);
5225
+ const {
5226
+ messageSearchChannelFilters,
5227
+ messageSearchFilters,
5228
+ messageSearchSort,
5229
+ channelQueryFilters,
5230
+ channelQuerySort,
5231
+ channelQueryOptions,
5232
+ ...restOptions
5233
+ } = options || {};
5234
+ super(restOptions);
4907
5235
  this.type = "messages";
4908
5236
  this.client = client;
5237
+ this.messageSearchChannelFilters = messageSearchChannelFilters;
5238
+ this.messageSearchFilters = messageSearchFilters;
5239
+ this.messageSearchSort = messageSearchSort;
5240
+ this.channelQueryFilters = channelQueryFilters;
5241
+ this.channelQuerySort = channelQuerySort;
5242
+ this.channelQueryOptions = channelQueryOptions;
4909
5243
  this.messageSearchChannelFilterBuilder = new FilterBuilder(filterBuilderOptions?.messageSearchChannel);
4910
5244
  this.messageSearchFilterBuilder = new FilterBuilder({
4911
5245
  ...filterBuilderOptions?.messageSearch,
@@ -4928,7 +5262,7 @@ var MessageSearchSource = class extends BaseSearchSource {
4928
5262
  }
4929
5263
  });
4930
5264
  }
4931
- async query(searchQuery) {
5265
+ async query(searchQuery, queryOptions = {}) {
4932
5266
  if (!this.client.userID || this.next === null) return { items: [] };
4933
5267
  const channelFilters = this.messageSearchChannelFilterBuilder.buildFilters({
4934
5268
  baseFilters: {
@@ -4956,9 +5290,11 @@ var MessageSearchSource = class extends BaseSearchSource {
4956
5290
  const { next, results } = await this.client.search(
4957
5291
  channelFilters,
4958
5292
  messageFilters,
4959
- options
5293
+ options,
5294
+ queryOptions
4960
5295
  );
4961
5296
  const items = results.map(({ message }) => message);
5297
+ if (queryOptions.signal?.aborted) return { items, next };
4962
5298
  const cids = Array.from(
4963
5299
  items.reduce((acc, message) => {
4964
5300
  if (message.cid && !this.client.activeChannels[message.cid]) acc.add(message.cid);
@@ -4976,7 +5312,8 @@ var MessageSearchSource = class extends BaseSearchSource {
4976
5312
  last_message_at: -1,
4977
5313
  ...this.channelQuerySort
4978
5314
  },
4979
- this.channelQueryOptions
5315
+ this.channelQueryOptions,
5316
+ queryOptions
4980
5317
  );
4981
5318
  }
4982
5319
  return { items, next };
@@ -5701,6 +6038,13 @@ var MessageComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5701
6038
  createCompositionDataCleanupMiddleware(composer)
5702
6039
  ]);
5703
6040
  }
6041
+ /**
6042
+ * Narrows the base accessor to the composition middleware type, so callers can read the
6043
+ * declarations it carries (`allowsPendingUploads`) without casting.
6044
+ */
6045
+ get installedMiddleware() {
6046
+ return super.installedMiddleware;
6047
+ }
5704
6048
  };
5705
6049
  var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5706
6050
  constructor({ composer }) {
@@ -6270,13 +6614,18 @@ var MentionsSearchSource = class extends BaseSearchSource {
6270
6614
  constructor(channel, options) {
6271
6615
  const {
6272
6616
  mentionAllAppUsers,
6617
+ memberFilters,
6618
+ memberSort,
6619
+ searchOptions,
6273
6620
  suggestionFactoryMappers,
6274
6621
  textComposerText,
6275
6622
  transliterate,
6276
6623
  trigger,
6624
+ userFilters,
6625
+ userSort,
6277
6626
  ...restOptions
6278
6627
  } = options || {};
6279
- super(restOptions);
6628
+ super({ ...restOptions, allowEmptySearchString: true });
6280
6629
  this.type = "mentions";
6281
6630
  this.normalizeSearchValue = (value) => this.transliterate(removeDiacritics(value)).toLowerCase();
6282
6631
  this.matchesSearchQuery = (value, searchQuery) => {
@@ -6314,10 +6663,6 @@ var MentionsSearchSource = class extends BaseSearchSource {
6314
6663
  this.toHereMentionSuggestion = (searchToken = this.searchQuery) => this.mapMentionSuggestion("here", "here", searchToken);
6315
6664
  this.toRoleMentionSuggestion = (role, searchToken = this.searchQuery) => this.mapMentionSuggestion("role", role, searchToken);
6316
6665
  this.toUserGroupMentionSuggestion = (userGroup, searchToken = this.searchQuery) => this.mapMentionSuggestion("user_group", userGroup, searchToken);
6317
- this.canExecuteQuery = (newSearchString) => {
6318
- const hasNewSearchQuery = typeof newSearchString !== "undefined";
6319
- return this.isActive && !this.isLoading && (hasNewSearchQuery || this.hasNext);
6320
- };
6321
6666
  this.transliterate = (text) => text;
6322
6667
  this.getMembersAndWatchers = () => {
6323
6668
  const memberUsers = Object.values(this.channel.state.members ?? {}).map(
@@ -6337,10 +6682,10 @@ var MentionsSearchSource = class extends BaseSearchSource {
6337
6682
  ...this.isMentionTypeAllowed("channel") ? [this.toChannelMentionSuggestion(searchQuery)] : [],
6338
6683
  ...this.isMentionTypeAllowed("here") ? [this.toHereMentionSuggestion(searchQuery)] : []
6339
6684
  ].filter(({ name }) => this.matchesPrefixSearchQuery(name, searchQuery));
6340
- this.getRoleMentionSuggestions = async (query) => {
6685
+ this.getRoleMentionSuggestions = async (query, queryOptions = {}) => {
6341
6686
  if (!this.isMentionTypeAllowed("role")) return [];
6342
6687
  if (!query) return [];
6343
- const { roles } = await this.client.searchRoles({ query });
6688
+ const { roles } = await this.client.searchRoles({ query }, queryOptions);
6344
6689
  return [...roles?.map((role) => role.name) ?? []].sort((left, right) => left.localeCompare(right)).map((role) => this.toRoleMentionSuggestion(role, query));
6345
6690
  };
6346
6691
  this.searchMembersLocally = (searchQuery) => {
@@ -6402,20 +6747,25 @@ var MentionsSearchSource = class extends BaseSearchSource {
6402
6747
  options: { ...this.searchOptions, limit: this.pageSize, offset }
6403
6748
  };
6404
6749
  };
6405
- this.queryUsers = async (searchQuery, offset = 0) => {
6750
+ this.queryUsers = async (searchQuery, offset = 0, queryOptions = {}) => {
6406
6751
  const { filters, sort, options } = this.prepareQueryUsersParams(searchQuery, offset);
6407
- const { users } = await this.client.queryUsers(filters, sort, options);
6752
+ const { users } = await this.client.queryUsers(filters, sort, options, queryOptions);
6408
6753
  return users;
6409
6754
  };
6410
- this.queryMembers = async (searchQuery, offset = 0) => {
6755
+ this.queryMembers = async (searchQuery, offset = 0, queryOptions = {}) => {
6411
6756
  const { filters, sort, options } = this.prepareQueryMembersParams(
6412
6757
  searchQuery,
6413
6758
  offset
6414
6759
  );
6415
- const response = await this.channel.queryMembers(filters, sort, options);
6760
+ const response = await this.channel.queryMembers(
6761
+ filters,
6762
+ sort,
6763
+ options,
6764
+ queryOptions
6765
+ );
6416
6766
  return response.members.map((member) => member.user);
6417
6767
  };
6418
- this.getUserSuggestionsPage = async (searchQuery, userOffset = 0) => {
6768
+ this.getUserSuggestionsPage = async (searchQuery, userOffset = 0, queryOptions = {}) => {
6419
6769
  if (!this.isMentionTypeAllowed("user")) {
6420
6770
  return {
6421
6771
  items: [],
@@ -6425,7 +6775,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6425
6775
  let users;
6426
6776
  const shouldSearchLocally = this.allMembersLoadedWithInitialChannelQuery || !searchQuery;
6427
6777
  if (this.config.mentionAllAppUsers) {
6428
- users = await this.queryUsers(searchQuery, userOffset);
6778
+ users = await this.queryUsers(searchQuery, userOffset, queryOptions);
6429
6779
  } else if (shouldSearchLocally) {
6430
6780
  const localUsers = this.searchMembersLocally(searchQuery);
6431
6781
  const items2 = localUsers.slice(userOffset, userOffset + this.pageSize).map((user) => this.toUserSuggestion(user, searchQuery));
@@ -6434,7 +6784,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6434
6784
  nextOffset: localUsers.length > userOffset + this.pageSize ? userOffset + items2.length : void 0
6435
6785
  };
6436
6786
  } else {
6437
- users = await this.queryMembers(searchQuery, userOffset);
6787
+ users = await this.queryMembers(searchQuery, userOffset, queryOptions);
6438
6788
  }
6439
6789
  const items = users.map((user) => this.toUserSuggestion(user, searchQuery));
6440
6790
  return {
@@ -6451,7 +6801,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6451
6801
  name_gt: lastItem.name
6452
6802
  });
6453
6803
  };
6454
- this.getUserGroupSuggestionsPage = async (searchQuery, cursor) => {
6804
+ this.getUserGroupSuggestionsPage = async (searchQuery, cursor, queryOptions = {}) => {
6455
6805
  if (!this.isMentionTypeAllowed("user_group")) {
6456
6806
  return {
6457
6807
  items: [],
@@ -6473,7 +6823,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6473
6823
  ...userGroupCursor?.id_gt ? { id_gt: userGroupCursor.id_gt } : {},
6474
6824
  ...userGroupCursor?.name_gt ? { name_gt: userGroupCursor.name_gt } : {}
6475
6825
  };
6476
- const { user_groups } = await this.client.searchUserGroups(options);
6826
+ const { user_groups } = await this.client.searchUserGroups(options, queryOptions);
6477
6827
  return {
6478
6828
  items: user_groups.map(
6479
6829
  (userGroup) => this.toUserGroupMentionSuggestion(userGroup, searchQuery)
@@ -6483,6 +6833,11 @@ var MentionsSearchSource = class extends BaseSearchSource {
6483
6833
  };
6484
6834
  this.client = channel.getClient();
6485
6835
  this.channel = channel;
6836
+ this.userFilters = userFilters;
6837
+ this.userSort = userSort;
6838
+ this.memberFilters = memberFilters;
6839
+ this.memberSort = memberSort;
6840
+ this.searchOptions = searchOptions;
6486
6841
  this.config = {
6487
6842
  mentionAllAppUsers,
6488
6843
  suggestionFactoryMappers,
@@ -6515,16 +6870,21 @@ var MentionsSearchSource = class extends BaseSearchSource {
6515
6870
  offset: (this.offset ?? 0) + userPaginationState.itemCount
6516
6871
  };
6517
6872
  }
6518
- async query(searchQuery) {
6873
+ async query(searchQuery, queryOptions = {}) {
6519
6874
  const userOffset = this.offset ?? 0;
6520
6875
  const isFirstPage = userOffset === 0 && typeof this.userGroupCursor === "undefined";
6521
6876
  const previousUserPaginationState = this.latestUserPaginationState;
6522
6877
  const previousUserGroupCursor = this.userGroupCursor;
6523
6878
  const [userResultsState, userGroupResultsState, roleSuggestionsState] = await Promise.allSettled([
6524
- this.getUserSuggestionsPage(searchQuery, userOffset),
6525
- this.getUserGroupSuggestionsPage(searchQuery, previousUserGroupCursor),
6526
- isFirstPage ? this.getRoleMentionSuggestions(searchQuery) : Promise.resolve([])
6879
+ this.getUserSuggestionsPage(searchQuery, userOffset, queryOptions),
6880
+ this.getUserGroupSuggestionsPage(
6881
+ searchQuery,
6882
+ previousUserGroupCursor,
6883
+ queryOptions
6884
+ ),
6885
+ isFirstPage ? this.getRoleMentionSuggestions(searchQuery, queryOptions) : Promise.resolve([])
6527
6886
  ]);
6887
+ if (queryOptions.signal?.aborted) return { items: [] };
6528
6888
  const userResults = userResultsState.status === "fulfilled" ? userResultsState.value : {
6529
6889
  items: [],
6530
6890
  nextOffset: isFirstPage ? void 0 : previousUserPaginationState?.nextOffset
@@ -6799,7 +7159,7 @@ var DEFAULT_COMPOSER_CONFIG = {
6799
7159
  };
6800
7160
 
6801
7161
  // src/messageComposer/CustomDataManager.ts
6802
- var initState3 = (options) => {
7162
+ var initState4 = (options) => {
6803
7163
  if (!options)
6804
7164
  return { message: {}, custom: {} };
6805
7165
  return { message: {}, custom: {} };
@@ -6808,14 +7168,14 @@ var CustomDataManager = class {
6808
7168
  constructor({ composer, message }) {
6809
7169
  this.isMessageDataEqual = (nextState, previousState) => JSON.stringify(nextState.message) === JSON.stringify(previousState?.message);
6810
7170
  this.initState = ({ message } = {}) => {
6811
- this.state.next(initState3({ composer: this.composer, message }));
7171
+ this.state.next(initState4({ composer: this.composer, message }));
6812
7172
  };
6813
7173
  this.getSnapshot = () => this.state.getLatestValue();
6814
7174
  this.restoreSnapshot = (snapshot) => {
6815
7175
  this.state.next(snapshot);
6816
7176
  };
6817
7177
  this.composer = composer;
6818
- this.state = new StateStore(initState3({ composer, message }));
7178
+ this.state = new StateStore(initState4({ composer, message }));
6819
7179
  }
6820
7180
  get customMessageData() {
6821
7181
  return this.state.getLatestValue().message;
@@ -6851,7 +7211,7 @@ var LinkPreviewStatus = /* @__PURE__ */ ((LinkPreviewStatus2) => {
6851
7211
  return LinkPreviewStatus2;
6852
7212
  })(LinkPreviewStatus || {});
6853
7213
  var linkPreviewArrayToMap = (linkPreviews) => new Map(linkPreviews.map((linkPreview) => [linkPreview.og_scrape_url, linkPreview]));
6854
- var initState4 = ({
7214
+ var initState5 = ({
6855
7215
  message
6856
7216
  }) => message ? {
6857
7217
  previews: message.attachments?.reduce((acc, attachment) => {
@@ -6870,7 +7230,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
6870
7230
  this.shouldDiscardEnrichQueries = false;
6871
7231
  this.initState = ({ message } = {}) => {
6872
7232
  this.cancelURLEnrichment();
6873
- this.state.next(initState4({ message: this.enabled ? message : void 0 }));
7233
+ this.state.next(initState5({ message: this.enabled ? message : void 0 }));
6874
7234
  };
6875
7235
  this.getSnapshot = () => this.state.getLatestValue();
6876
7236
  this.restoreSnapshot = (snapshot) => {
@@ -6973,7 +7333,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
6973
7333
  };
6974
7334
  this.composer = composer;
6975
7335
  this.state = new StateStore(
6976
- initState4({ message: this.enabled ? message : void 0 })
7336
+ initState5({ message: this.enabled ? message : void 0 })
6977
7337
  );
6978
7338
  this.findAndEnrichUrls = debounce(
6979
7339
  this._findAndEnrichUrls.bind(this),
@@ -7061,7 +7421,7 @@ var LinkPreviewsManager = _LinkPreviewsManager;
7061
7421
 
7062
7422
  // src/messageComposer/LocationComposer.ts
7063
7423
  var MIN_LIVE_LOCATION_SHARE_DURATION = 60 * 1e3;
7064
- var initState5 = ({
7424
+ var initState6 = ({
7065
7425
  message
7066
7426
  }) => ({
7067
7427
  location: message?.shared_location ?? null
@@ -7069,7 +7429,7 @@ var initState5 = ({
7069
7429
  var LocationComposer = class {
7070
7430
  constructor({ composer, message }) {
7071
7431
  this.initState = ({ message } = {}) => {
7072
- this.state.next(initState5({ message }));
7432
+ this.state.next(initState6({ message }));
7073
7433
  };
7074
7434
  this.getSnapshot = () => this.state.getLatestValue();
7075
7435
  this.restoreSnapshot = (snapshot) => {
@@ -7087,7 +7447,7 @@ var LocationComposer = class {
7087
7447
  });
7088
7448
  };
7089
7449
  this.composer = composer;
7090
- this.state = new StateStore(initState5({ message }));
7450
+ this.state = new StateStore(initState6({ message }));
7091
7451
  this._deviceId = this.config.getDeviceId();
7092
7452
  }
7093
7453
  get config() {
@@ -7808,7 +8168,7 @@ var initEditingAuditState = (composition) => {
7808
8168
  }
7809
8169
  };
7810
8170
  };
7811
- var initState6 = (composition) => {
8171
+ var initState7 = (composition) => {
7812
8172
  if (!composition) {
7813
8173
  return {
7814
8174
  draftId: null,
@@ -7895,7 +8255,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7895
8255
  this.textComposer.initState({ message });
7896
8256
  this.pollComposer.initState();
7897
8257
  this.customDataManager.initState({ message });
7898
- this.state.next(initState6(composition));
8258
+ this.state.next(initState7(composition));
7899
8259
  };
7900
8260
  this.initStateFromChannelResponse = (channelApiResponse) => {
7901
8261
  if (this.channel.cid !== channelApiResponse.channel.cid) {
@@ -8361,7 +8721,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8361
8721
  this.editingAuditState = new StateStore(
8362
8722
  this.initEditingAuditState(composition)
8363
8723
  );
8364
- this.state = new StateStore(initState6(composition));
8724
+ this.state = new StateStore(initState7(composition));
8365
8725
  this.compositionMiddlewareExecutor = new MessageComposerMiddlewareExecutor({
8366
8726
  composer: this
8367
8727
  });
@@ -8440,8 +8800,32 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8440
8800
  const currentCommand = this.textComposer.command;
8441
8801
  return !currentCommand || this.validateCommandSendability(currentCommand).ready;
8442
8802
  }
8803
+ /**
8804
+ * Whether the installed composition middleware composes messages whose uploads are still in
8805
+ * flight — see `MessageCompositionMiddleware.allowsPendingUploads`.
8806
+ *
8807
+ * Derived from the chain rather than from config, so there is exactly one switch: the UI SDK
8808
+ * that implements the rest of the flow installs the middleware, and everything that has to
8809
+ * follow from that (starting with {@link hasSendableData}) follows on its own.
8810
+ *
8811
+ * **Temporary API.** In v10 sending with pending uploads becomes part of the composer
8812
+ * configuration (`MessageComposerConfig`, set through `updateConfig`), and this getter,
8813
+ * together with the middleware declaration it reads, is replaced by that config field.
8814
+ */
8815
+ get allowsPendingUploads() {
8816
+ return this.compositionMiddlewareExecutor.installedMiddleware.some(
8817
+ (middleware) => middleware.allowsPendingUploads
8818
+ );
8819
+ }
8443
8820
  get hasSendableData() {
8444
- return this.isCommandSendable && !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
8821
+ if (!this.isCommandSendable) return false;
8822
+ if (this.allowsPendingUploads) {
8823
+ const hasSendableAttachment = this.attachmentManager.attachments.some(
8824
+ (attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment)
8825
+ );
8826
+ return !!(!this.textComposer.textIsEmpty || hasSendableAttachment || this.pollId || this.locationComposer.validLocation);
8827
+ }
8828
+ return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
8445
8829
  }
8446
8830
  get compositionIsEmpty() {
8447
8831
  return !this.quotedMessage && this.contentIsEmpty;
@@ -9068,14 +9452,16 @@ var Channel = class {
9068
9452
  *
9069
9453
  * @return {Promise<SendMessageAPIResponse>} The Server Response
9070
9454
  */
9071
- async _sendMessage(message, options) {
9072
- return await this.getClient().post(
9073
- this._channelURL() + "/message",
9074
- {
9075
- message,
9076
- ...options
9077
- }
9078
- );
9455
+ async _sendMessage(rawMessage, options) {
9456
+ const client = this.getClient();
9457
+ const message = sanitizeOutgoingAttachments({
9458
+ client,
9459
+ message: rawMessage
9460
+ });
9461
+ return await client.post(this._channelURL() + "/message", {
9462
+ message,
9463
+ ...options
9464
+ });
9079
9465
  }
9080
9466
  async sendMessage(message, options) {
9081
9467
  try {
@@ -9200,10 +9586,11 @@ var Channel = class {
9200
9586
  * @param {MemberSort} [sort] Sort options, for instance [{created_at: -1}].
9201
9587
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{name: -1}, {created_at: 1}]
9202
9588
  * @param {{ limit?: number; offset?: number }} [options] Option object, {limit: 10, offset:10}
9589
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
9203
9590
  *
9204
9591
  * @return {Promise<ChannelMemberAPIResponse>} Query Members response
9205
9592
  */
9206
- async queryMembers(filterConditions, sort = [], options = {}) {
9593
+ async queryMembers(filterConditions, sort = [], options = {}, requestOptions = {}) {
9207
9594
  let id;
9208
9595
  const type = this.type;
9209
9596
  let members;
@@ -9223,7 +9610,8 @@ var Channel = class {
9223
9610
  filter_conditions: filterConditions,
9224
9611
  ...options
9225
9612
  }
9226
- }
9613
+ },
9614
+ requestOptions
9227
9615
  );
9228
9616
  }
9229
9617
  /**
@@ -10524,6 +10912,7 @@ var Channel = class {
10524
10912
  // delivery-report network sync below is skipped for it.
10525
10913
  case "message.read_locally":
10526
10914
  case "message.read":
10915
+ if (event.thread) break;
10527
10916
  if (event.user?.id && event.created_at) {
10528
10917
  const previousReadState = channelState.read[event.user.id];
10529
10918
  channelState.read[event.user.id] = {
@@ -10973,7 +11362,7 @@ var ClientState = class {
10973
11362
  import WebSocket from "isomorphic-ws";
10974
11363
 
10975
11364
  // src/insights.ts
10976
- import axios from "axios";
11365
+ import axios2 from "axios";
10977
11366
  var InsightMetrics = class {
10978
11367
  constructor() {
10979
11368
  this.connectionStartTimestamp = null;
@@ -10986,7 +11375,7 @@ var postInsights = async (insightType, insights) => {
10986
11375
  const maxAttempts = 3;
10987
11376
  for (let i = 0; i < maxAttempts; i++) {
10988
11377
  try {
10989
- await axios.post(
11378
+ await axios2.post(
10990
11379
  `https://chat-insights.getstream.io/insights/${insightType}`,
10991
11380
  insights
10992
11381
  );
@@ -11494,131 +11883,6 @@ var StableWSConnection = class {
11494
11883
  }
11495
11884
  };
11496
11885
 
11497
- // src/uploadManager.ts
11498
- var initState7 = () => ({ uploads: {} });
11499
- var upsertById = (uploads, record) => ({
11500
- ...uploads,
11501
- [record.id]: { ...uploads[record.id], ...record }
11502
- });
11503
- var updateById = (uploads, record) => {
11504
- if (!(record.id in uploads)) return null;
11505
- const current = uploads[record.id];
11506
- return { ...uploads, [record.id]: { ...current, ...record } };
11507
- };
11508
- var UploadManager = class {
11509
- constructor(client) {
11510
- this.client = client;
11511
- this.inFlightUploads = /* @__PURE__ */ new Map();
11512
- this.getUpload = (id) => this.uploads[id];
11513
- /**
11514
- * Clears all upload records.
11515
- * Invoked when the user disconnects so a later session does not inherit stale upload state.
11516
- * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
11517
- */
11518
- this.reset = () => {
11519
- for (const { abortController } of this.inFlightUploads.values()) {
11520
- abortController.abort();
11521
- }
11522
- this.inFlightUploads.clear();
11523
- this.state.next(initState7());
11524
- };
11525
- /**
11526
- * Removes the upload record for `id` if present.
11527
- * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
11528
- */
11529
- this.deleteUploadRecord = (id) => {
11530
- const flight = this.inFlightUploads.get(id);
11531
- if (flight) {
11532
- this.inFlightUploads.delete(id);
11533
- flight.abortController.abort();
11534
- }
11535
- this.state.next((current) => {
11536
- if (!(id in current.uploads)) return current;
11537
- const uploads = { ...current.uploads };
11538
- delete uploads[id];
11539
- return { ...current, uploads };
11540
- });
11541
- };
11542
- /**
11543
- * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
11544
- * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
11545
- * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
11546
- */
11547
- this.upload = ({
11548
- id,
11549
- channelCid,
11550
- file
11551
- }) => {
11552
- const existing = this.inFlightUploads.get(id);
11553
- if (existing) return existing.promise;
11554
- let resolvePromise;
11555
- let rejectPromise;
11556
- const promise = new Promise((resolve, reject) => {
11557
- resolvePromise = resolve;
11558
- rejectPromise = reject;
11559
- });
11560
- const abortController = new AbortController();
11561
- this.inFlightUploads.set(id, { promise, abortController });
11562
- void (async () => {
11563
- const attachmentManager = this.resolveAttachmentManager(channelCid);
11564
- const trackProgress = attachmentManager.config.trackUploadProgress;
11565
- try {
11566
- this.upsertUpload({
11567
- id,
11568
- uploadProgress: trackProgress ? 0 : void 0
11569
- });
11570
- const onProgress = trackProgress ? (progress) => {
11571
- this.updateUpload({
11572
- id,
11573
- uploadProgress: progress
11574
- });
11575
- } : void 0;
11576
- const uploadRequestOptions = {
11577
- abortSignal: abortController.signal,
11578
- ...onProgress ? { onProgress } : {}
11579
- };
11580
- const response = await attachmentManager.doUploadRequest(
11581
- file,
11582
- uploadRequestOptions
11583
- );
11584
- resolvePromise(response);
11585
- } catch (error) {
11586
- rejectPromise(error);
11587
- } finally {
11588
- this.inFlightUploads.delete(id);
11589
- this.deleteUploadRecord(id);
11590
- }
11591
- })();
11592
- return promise;
11593
- };
11594
- this.upsertUpload = (record) => {
11595
- this.state.partialNext({
11596
- uploads: upsertById(this.uploads, record)
11597
- });
11598
- };
11599
- this.updateUpload = (record) => {
11600
- this.state.next((current) => {
11601
- const nextUploads = updateById(current.uploads, record);
11602
- if (!nextUploads) return current;
11603
- return { ...current, uploads: nextUploads };
11604
- });
11605
- };
11606
- this.state = new StateStore(initState7());
11607
- }
11608
- resolveAttachmentManager(channelCid) {
11609
- const colon = channelCid.indexOf(":");
11610
- if (colon <= 0 || colon === channelCid.length - 1) {
11611
- throw new Error(`Invalid channelCid: ${channelCid}`);
11612
- }
11613
- const channelType = channelCid.slice(0, colon);
11614
- const channelId = channelCid.slice(colon + 1);
11615
- return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
11616
- }
11617
- get uploads() {
11618
- return this.state.getLatestValue().uploads;
11619
- }
11620
- };
11621
-
11622
11886
  // src/signing.ts
11623
11887
  var import_jsonwebtoken = __toESM(require_jsonwebtoken());
11624
11888
  var import_crypto = __toESM(require_crypto());
@@ -11877,7 +12141,7 @@ var TokenManager = class {
11877
12141
  };
11878
12142
 
11879
12143
  // src/connection_fallback.ts
11880
- import axios2 from "axios";
12144
+ import axios3 from "axios";
11881
12145
  var WSConnectionFallback = class {
11882
12146
  constructor({ client }) {
11883
12147
  /** @private */
@@ -11896,7 +12160,7 @@ var WSConnectionFallback = class {
11896
12160
  /** @private */
11897
12161
  this._req = async (params, config, retry) => {
11898
12162
  if (!this.cancelToken && !params.close) {
11899
- this.cancelToken = axios2.CancelToken.source();
12163
+ this.cancelToken = axios3.CancelToken.source();
11900
12164
  }
11901
12165
  try {
11902
12166
  const res = await this.client.doAxiosRequest(
@@ -11932,7 +12196,7 @@ var WSConnectionFallback = class {
11932
12196
  }
11933
12197
  }
11934
12198
  } catch (error) {
11935
- if (axios2.isCancel(error)) {
12199
+ if (axios3.isCancel(error)) {
11936
12200
  this._log(`_poll() - axios canceled request`);
11937
12201
  return;
11938
12202
  }
@@ -14355,6 +14619,7 @@ var StreamChat = class _StreamChat {
14355
14619
  return this.handleResponse(response);
14356
14620
  } catch (e) {
14357
14621
  e.client_request_id = requestConfig.headers?.["x-client-request-id"];
14622
+ if (axios4.isCancel(e)) throw e;
14358
14623
  this._logApiError(type, url, e);
14359
14624
  this.consecutiveFailures += 1;
14360
14625
  if (e.response) {
@@ -14666,7 +14931,7 @@ var StreamChat = class _StreamChat {
14666
14931
  return this.userAgent;
14667
14932
  }
14668
14933
  if (!this.cachedUserAgent) {
14669
- const version = "9.50.3";
14934
+ const version = "9.52.0";
14670
14935
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14671
14936
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14672
14937
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -14734,7 +14999,7 @@ var StreamChat = class _StreamChat {
14734
14999
  keepAliveMsecs: 3e3
14735
15000
  });
14736
15001
  }
14737
- this.axiosInstance = axios3.create(this.options);
15002
+ this.axiosInstance = axios4.create(this.options);
14738
15003
  this.setBaseURL(this.options.baseURL || "https://chat.stream-io-api.com");
14739
15004
  if (typeof process !== "undefined" && "env" in process && process.env.STREAM_LOCAL_TEST_RUN) {
14740
15005
  this.setBaseURL("http://localhost:3030");
@@ -15042,14 +15307,14 @@ var StreamChat = class _StreamChat {
15042
15307
  error
15043
15308
  });
15044
15309
  }
15045
- get(url, params) {
15046
- return this.doAxiosRequest("get", url, null, { params });
15310
+ get(url, params, config) {
15311
+ return this.doAxiosRequest("get", url, null, { params, config });
15047
15312
  }
15048
15313
  put(url, data) {
15049
15314
  return this.doAxiosRequest("put", url, data);
15050
15315
  }
15051
- post(url, data) {
15052
- return this.doAxiosRequest("post", url, data);
15316
+ post(url, data, config) {
15317
+ return this.doAxiosRequest("post", url, data, { config });
15053
15318
  }
15054
15319
  patch(url, data) {
15055
15320
  return this.doAxiosRequest("patch", url, data);
@@ -15244,10 +15509,11 @@ var StreamChat = class _StreamChat {
15244
15509
  * @param {UserSort} sort Sort options, for instance [{last_active: -1}].
15245
15510
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_active: -1}, {created_at: 1}]
15246
15511
  * @param {UserOptions} options Option object, {presence: true}
15512
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15247
15513
  *
15248
15514
  * @return {Promise<{ users: Array<UserResponse> }>} User Query Response
15249
15515
  */
15250
- async queryUsers(filterConditions, sort = [], options = {}) {
15516
+ async queryUsers(filterConditions, sort = [], options = {}, requestOptions = {}) {
15251
15517
  const defaultOptions = {
15252
15518
  presence: false
15253
15519
  };
@@ -15264,7 +15530,8 @@ var StreamChat = class _StreamChat {
15264
15530
  ...defaultOptions,
15265
15531
  ...options
15266
15532
  }
15267
- }
15533
+ },
15534
+ requestOptions
15268
15535
  );
15269
15536
  this.state.updateUsers(data.users);
15270
15537
  return data;
@@ -15310,13 +15577,14 @@ var StreamChat = class _StreamChat {
15310
15577
  * searchUserGroups - Search user groups by prefix for autocomplete
15311
15578
  *
15312
15579
  * @param {SearchUserGroupsOptions} options The search options
15313
- *
15580
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15314
15581
  * @return {Promise<SearchUserGroupsResponse>} User Group Search Response
15315
15582
  */
15316
- async searchUserGroups(options) {
15583
+ async searchUserGroups(options, requestOptions = {}) {
15317
15584
  return await this.get(
15318
15585
  this.baseURL + "/usergroups/search",
15319
- options
15586
+ options,
15587
+ requestOptions
15320
15588
  );
15321
15589
  }
15322
15590
  /**
@@ -15436,10 +15704,11 @@ var StreamChat = class _StreamChat {
15436
15704
  * @param {ChannelSort} [sort] Sort options, for instance {created_at: -1}.
15437
15705
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_updated: -1}, {created_at: 1}]
15438
15706
  * @param {ChannelOptions} [options] Options object. Can include predefined_filter, filter_values, and sort_values for using predefined filters.
15707
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15439
15708
  *
15440
15709
  * @return {Promise<QueryChannelsAPIResponse>} full search channels response
15441
15710
  */
15442
- async queryChannelsRequestWithResponse(filterConditions, sort = [], options = {}) {
15711
+ async queryChannelsRequestWithResponse(filterConditions, sort = [], options = {}, requestOptions = {}) {
15443
15712
  const defaultOptions = {
15444
15713
  state: true,
15445
15714
  watch: true,
@@ -15464,7 +15733,11 @@ var StreamChat = class _StreamChat {
15464
15733
  ...defaultOptions,
15465
15734
  ...restOptions
15466
15735
  };
15467
- return await this.post(this.baseURL + "/channels", payload);
15736
+ return await this.post(
15737
+ this.baseURL + "/channels",
15738
+ payload,
15739
+ requestOptions
15740
+ );
15468
15741
  }
15469
15742
  /**
15470
15743
  * queryChannelsRequest - Queries channels and returns the raw channel response list.
@@ -15493,7 +15766,9 @@ var StreamChat = class _StreamChat {
15493
15766
  const queryChannelsResponse = await this.queryChannelsRequestWithResponse(
15494
15767
  filterConditions,
15495
15768
  sort,
15496
- options
15769
+ options,
15770
+ // stateOptions is never serialized, so it carries the abort signal
15771
+ { signal: stateOptions.signal }
15497
15772
  );
15498
15773
  const channels = queryChannelsResponse.channels;
15499
15774
  this.dispatchEvent({
@@ -15610,10 +15885,11 @@ var StreamChat = class _StreamChat {
15610
15885
  * @param {ChannelFilters} filterConditions MongoDB style filter conditions
15611
15886
  * @param {MessageFilters | string} query search query or object MongoDB style filters
15612
15887
  * @param {SearchOptions} [options] Option object, {user_id: 'tommaso'}
15888
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15613
15889
  *
15614
15890
  * @return {Promise<SearchAPIResponse>} search messages response
15615
15891
  */
15616
- async search(filterConditions, query, options = {}) {
15892
+ async search(filterConditions, query, options = {}, requestOptions = {}) {
15617
15893
  if (options.offset && options.next) {
15618
15894
  throw Error(`Cannot specify offset with next`);
15619
15895
  }
@@ -15630,7 +15906,11 @@ var StreamChat = class _StreamChat {
15630
15906
  throw Error(`Invalid type ${typeof query} for query parameter`);
15631
15907
  }
15632
15908
  await this.wsPromise;
15633
- return await this.get(this.baseURL + "/search", { payload });
15909
+ return await this.get(
15910
+ this.baseURL + "/search",
15911
+ { payload },
15912
+ requestOptions
15913
+ );
15634
15914
  }
15635
15915
  /**
15636
15916
  * setLocalDevice - Set the device info for the current client(device) that will be sent via WS connection automatically
@@ -16071,6 +16351,11 @@ var StreamChat = class _StreamChat {
16071
16351
  }
16072
16352
  /**
16073
16353
  * unflagMessage - unflag a message
16354
+ *
16355
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16356
+ * the backend - calling this resolves successfully but does not remove the flag.
16357
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16358
+ *
16074
16359
  * @param {string} targetMessageID
16075
16360
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16076
16361
  * @returns {Promise<APIResponse>}
@@ -16083,6 +16368,11 @@ var StreamChat = class _StreamChat {
16083
16368
  }
16084
16369
  /**
16085
16370
  * unflagUser - unflag a user
16371
+ *
16372
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16373
+ * the backend - calling this resolves successfully but does not remove the flag.
16374
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16375
+ *
16086
16376
  * @param {string} targetID
16087
16377
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16088
16378
  * @returns {Promise<APIResponse>}
@@ -16367,7 +16657,10 @@ var StreamChat = class _StreamChat {
16367
16657
  if (!message.id) {
16368
16658
  throw Error("Please specify the message.id when calling updateMessage");
16369
16659
  }
16370
- const payload = toUpdatedMessagePayload(message);
16660
+ const payload = sanitizeOutgoingAttachments({
16661
+ client: this,
16662
+ message: toUpdatedMessagePayload(message)
16663
+ });
16371
16664
  if (typeof partialUserOrUserId === "string") {
16372
16665
  payload.user_id = partialUserOrUserId;
16373
16666
  } else if (typeof partialUserOrUserId?.id === "string") {
@@ -16667,6 +16960,7 @@ var StreamChat = class _StreamChat {
16667
16960
  headers: axiosRequestConfigHeaders,
16668
16961
  ...axiosRequestConfigRest
16669
16962
  } = this.options.axiosRequestConfig || {};
16963
+ const resolvedSignal = options.config?.signal ?? axiosRequestConfigRest.signal ?? signal;
16670
16964
  return {
16671
16965
  params: {
16672
16966
  user_id: this.userID,
@@ -16682,9 +16976,9 @@ var StreamChat = class _StreamChat {
16682
16976
  ...options.headers,
16683
16977
  ...axiosRequestConfigHeaders || {}
16684
16978
  },
16685
- ...signal ? { signal } : {},
16686
16979
  ...options.config,
16687
- ...axiosRequestConfigRest || {}
16980
+ ...axiosRequestConfigRest,
16981
+ ...resolvedSignal ? { signal: resolvedSignal } : {}
16688
16982
  };
16689
16983
  }
16690
16984
  _getToken() {
@@ -16824,8 +17118,12 @@ var StreamChat = class _StreamChat {
16824
17118
  *
16825
17119
  * @returns {Promise<APIResponse>}
16826
17120
  */
16827
- searchRoles(options) {
16828
- return this.get(`${this.baseURL}/roles/search`, options);
17121
+ searchRoles(options, requestOptions = {}) {
17122
+ return this.get(
17123
+ `${this.baseURL}/roles/search`,
17124
+ options,
17125
+ requestOptions
17126
+ );
16829
17127
  }
16830
17128
  /** deleteRole - deletes a custom role
16831
17129
  *
@@ -18182,7 +18480,8 @@ import { isAxiosError } from "axios";
18182
18480
  var OfflineDBSyncManager = class {
18183
18481
  constructor({
18184
18482
  client,
18185
- offlineDb
18483
+ offlineDb,
18484
+ syncMaxEventCount
18186
18485
  }) {
18187
18486
  this.syncStatus = false;
18188
18487
  this.connectionChangedListener = null;
@@ -18299,13 +18598,23 @@ var OfflineDBSyncManager = class {
18299
18598
  await this.offlineDb.resetDB();
18300
18599
  } else {
18301
18600
  const result = await this.client.sync(cids, lastSyncedAtDate.toISOString());
18302
- const queryPromises = result.events.map(
18303
- (event) => this.offlineDb.handleEvent({ event, execute: false })
18304
- );
18305
- const queriesArray = await Promise.all(queryPromises);
18306
- const queries = queriesArray.flat();
18307
- if (queries.length) {
18308
- await this.offlineDb.executeSqlBatch(queries);
18601
+ const { syncMaxEventCount } = this;
18602
+ const exceedsLimit = typeof syncMaxEventCount === "number" && syncMaxEventCount > 0 && result.events.length > syncMaxEventCount;
18603
+ if (exceedsLimit) {
18604
+ this.client.logger(
18605
+ "warn",
18606
+ `Skipping sync event replay: received ${result.events.length} events, which exceeds the configured limit of ${syncMaxEventCount}. Visible channels are refreshed on reconnect instead.`,
18607
+ { tags: ["sync", "offline"] }
18608
+ );
18609
+ } else {
18610
+ const queryPromises = result.events.map(
18611
+ (event) => this.offlineDb.handleEvent({ event, execute: false })
18612
+ );
18613
+ const queriesArray = await Promise.all(queryPromises);
18614
+ const queries = queriesArray.flat();
18615
+ if (queries.length) {
18616
+ await this.offlineDb.executeSqlBatch(queries);
18617
+ }
18309
18618
  }
18310
18619
  }
18311
18620
  }
@@ -18349,12 +18658,16 @@ var OfflineDBSyncManager = class {
18349
18658
  };
18350
18659
  this.client = client;
18351
18660
  this.offlineDb = offlineDb;
18661
+ this.syncMaxEventCount = syncMaxEventCount;
18352
18662
  }
18353
18663
  };
18354
18664
 
18355
18665
  // src/offline-support/offline_support_api.ts
18356
18666
  var AbstractOfflineDB = class {
18357
- constructor({ client }) {
18667
+ constructor({
18668
+ client,
18669
+ syncMaxEventCount
18670
+ }) {
18358
18671
  /**
18359
18672
  * Initializes the DB as well as its syncManager for a given userId.
18360
18673
  * It will update the DBs reactive state with initialization values.
@@ -18820,6 +19133,7 @@ var AbstractOfflineDB = class {
18820
19133
  return this.handleMessageUpdatedEvent({ event, execute });
18821
19134
  }
18822
19135
  if (type === "message.read" || type === "notification.mark_read") {
19136
+ if (event.thread) return [];
18823
19137
  return this.handleRead({ event, unreadMessages: 0, execute });
18824
19138
  }
18825
19139
  if (type === "message.read_locally") {
@@ -19044,7 +19358,11 @@ var AbstractOfflineDB = class {
19044
19358
  }
19045
19359
  };
19046
19360
  this.client = client;
19047
- this.syncManager = new OfflineDBSyncManager({ client, offlineDb: this });
19361
+ this.syncManager = new OfflineDBSyncManager({
19362
+ client,
19363
+ offlineDb: this,
19364
+ syncMaxEventCount
19365
+ });
19048
19366
  this.state = new StateStore({
19049
19367
  initialized: false,
19050
19368
  userId: this.client.userID
@@ -19382,6 +19700,7 @@ export {
19382
19700
  createMessageComposerStateCompositionMiddleware,
19383
19701
  createPollComposerStateMiddleware,
19384
19702
  createPostUploadAttachmentEnrichmentMiddleware,
19703
+ createSendWithPendingUploadsAttachmentsMiddleware,
19385
19704
  createSharedLocationCompositionMiddleware,
19386
19705
  createTextComposerCompositionMiddleware,
19387
19706
  createTextComposerPreValidationMiddleware,
@@ -19410,6 +19729,7 @@ export {
19410
19729
  getTokenizedSuggestionDisplayName,
19411
19730
  getTriggerCharWithToken,
19412
19731
  gunzipPayload,
19732
+ hasPending,
19413
19733
  insertItemWithTrigger,
19414
19734
  isAudioAttachment,
19415
19735
  isBlobButNotFile,
@@ -19417,6 +19737,7 @@ export {
19417
19737
  isFileAttachment,
19418
19738
  isFileList,
19419
19739
  isFileReference,
19740
+ isFinishedUpload,
19420
19741
  isGiphyAttachment,
19421
19742
  isImageAttachment,
19422
19743
  isImageFile,
@@ -19429,9 +19750,11 @@ export {
19429
19750
  isLocalVoiceRecordingAttachment,
19430
19751
  isOwnUser,
19431
19752
  isPatch,
19753
+ isPendingUpload,
19432
19754
  isScrapedContent,
19433
19755
  isSharedLocationResponse,
19434
19756
  isTargetedOptionTextUpdate,
19757
+ isUploadCancellation,
19435
19758
  isUploadedAttachment,
19436
19759
  isVideoAttachment,
19437
19760
  isVoiceRecordingAttachment,
@@ -19451,11 +19774,14 @@ export {
19451
19774
  readFileAsArrayBuffer,
19452
19775
  removeDiacritics,
19453
19776
  replaceWordWithEntity,
19777
+ settled,
19454
19778
  stripCommandFromText,
19455
19779
  stripMentionTokens,
19456
19780
  textIsEmpty,
19457
19781
  timeLeftMs,
19458
19782
  verifyAndParseWebhook,
19459
- verifySignature
19783
+ verifySignature,
19784
+ withCancellation,
19785
+ withoutConcurrency
19460
19786
  };
19461
19787
  //# sourceMappingURL=index.mjs.map