stream-chat 9.51.0 → 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 (38) hide show
  1. package/dist/cjs/index.browser.js +382 -187
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +391 -188
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +380 -185
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/channel.d.ts +1 -1
  8. package/dist/types/client.d.ts +10 -0
  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/types.d.ts +5 -0
  18. package/dist/types/middleware.d.ts +8 -0
  19. package/dist/types/uploadManager.d.ts +30 -0
  20. package/dist/types/utils.d.ts +20 -1
  21. package/package.json +1 -1
  22. package/src/channel.ts +16 -8
  23. package/src/client.ts +17 -1
  24. package/src/index.ts +12 -0
  25. package/src/messageComposer/attachmentIdentity.ts +27 -0
  26. package/src/messageComposer/attachmentManager.ts +61 -12
  27. package/src/messageComposer/messageComposer.ts +43 -8
  28. package/src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts +1 -1
  29. package/src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts +58 -33
  30. package/src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts +4 -0
  31. package/src/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.ts +9 -0
  32. package/src/messageComposer/middleware/messageComposer/attachments.ts +109 -0
  33. package/src/messageComposer/middleware/messageComposer/types.ts +22 -1
  34. package/src/messageComposer/types.ts +5 -0
  35. package/src/middleware.ts +11 -0
  36. package/src/offline-support/offline_support_api.ts +4 -0
  37. package/src/uploadManager.ts +45 -0
  38. package/src/utils.ts +72 -0
@@ -189,6 +189,7 @@ __export(index_exports, {
189
189
  createMessageComposerStateCompositionMiddleware: () => createMessageComposerStateCompositionMiddleware,
190
190
  createPollComposerStateMiddleware: () => createPollComposerStateMiddleware,
191
191
  createPostUploadAttachmentEnrichmentMiddleware: () => createPostUploadAttachmentEnrichmentMiddleware,
192
+ createSendWithPendingUploadsAttachmentsMiddleware: () => createSendWithPendingUploadsAttachmentsMiddleware,
192
193
  createSharedLocationCompositionMiddleware: () => createSharedLocationCompositionMiddleware,
193
194
  createTextComposerCompositionMiddleware: () => createTextComposerCompositionMiddleware,
194
195
  createTextComposerPreValidationMiddleware: () => createTextComposerPreValidationMiddleware,
@@ -217,6 +218,7 @@ __export(index_exports, {
217
218
  getTokenizedSuggestionDisplayName: () => getTokenizedSuggestionDisplayName,
218
219
  getTriggerCharWithToken: () => getTriggerCharWithToken,
219
220
  gunzipPayload: () => gunzipPayload,
221
+ hasPending: () => hasPending,
220
222
  insertItemWithTrigger: () => insertItemWithTrigger,
221
223
  isAudioAttachment: () => isAudioAttachment,
222
224
  isBlobButNotFile: () => isBlobButNotFile,
@@ -224,6 +226,7 @@ __export(index_exports, {
224
226
  isFileAttachment: () => isFileAttachment,
225
227
  isFileList: () => isFileList,
226
228
  isFileReference: () => isFileReference,
229
+ isFinishedUpload: () => isFinishedUpload,
227
230
  isGiphyAttachment: () => isGiphyAttachment,
228
231
  isImageAttachment: () => isImageAttachment,
229
232
  isImageFile: () => isImageFile,
@@ -236,9 +239,11 @@ __export(index_exports, {
236
239
  isLocalVoiceRecordingAttachment: () => isLocalVoiceRecordingAttachment,
237
240
  isOwnUser: () => isOwnUser,
238
241
  isPatch: () => isPatch,
242
+ isPendingUpload: () => isPendingUpload,
239
243
  isScrapedContent: () => isScrapedContent,
240
244
  isSharedLocationResponse: () => isSharedLocationResponse,
241
245
  isTargetedOptionTextUpdate: () => isTargetedOptionTextUpdate,
246
+ isUploadCancellation: () => isUploadCancellation,
242
247
  isUploadedAttachment: () => isUploadedAttachment,
243
248
  isVideoAttachment: () => isVideoAttachment,
244
249
  isVoiceRecordingAttachment: () => isVoiceRecordingAttachment,
@@ -258,12 +263,15 @@ __export(index_exports, {
258
263
  readFileAsArrayBuffer: () => readFileAsArrayBuffer,
259
264
  removeDiacritics: () => removeDiacritics,
260
265
  replaceWordWithEntity: () => replaceWordWithEntity,
266
+ settled: () => settled,
261
267
  stripCommandFromText: () => stripCommandFromText,
262
268
  stripMentionTokens: () => stripMentionTokens,
263
269
  textIsEmpty: () => textIsEmpty,
264
270
  timeLeftMs: () => timeLeftMs,
265
271
  verifyAndParseWebhook: () => verifyAndParseWebhook,
266
- verifySignature: () => verifySignature
272
+ verifySignature: () => verifySignature,
273
+ withCancellation: () => withCancellation,
274
+ withoutConcurrency: () => withoutConcurrency
267
275
  });
268
276
  module.exports = __toCommonJS(index_exports);
269
277
 
@@ -530,7 +538,7 @@ var ChannelBatchUpdater = class {
530
538
  };
531
539
 
532
540
  // src/client.ts
533
- var import_axios3 = __toESM(require("axios"));
541
+ var import_axios4 = __toESM(require("axios"));
534
542
  var import_https = __toESM(require_https());
535
543
 
536
544
  // src/utils.ts
@@ -790,6 +798,38 @@ function unformatMessage(message) {
790
798
  quoted_message: toMessageResponseBase(message.quoted_message)
791
799
  };
792
800
  }
801
+ var isRemoteUrl = (url) => !!url && /^https?:\/\//i.test(url.trim());
802
+ var sanitizeOutgoingAttachments = ({
803
+ client,
804
+ message
805
+ }) => {
806
+ const attachments = message.attachments;
807
+ if (!attachments?.length) return message;
808
+ const unresolved = [];
809
+ let changed = false;
810
+ const sanitized = attachments.reduce((acc, attachment) => {
811
+ const { localMetadata, ...rest } = attachment;
812
+ if (!localMetadata) {
813
+ acc.push(attachment);
814
+ return acc;
815
+ }
816
+ changed = true;
817
+ const hasRemoteSource = isRemoteUrl(rest.asset_url) || isRemoteUrl(rest.image_url) || isRemoteUrl(rest.og_scrape_url) || isRemoteUrl(rest.title_link);
818
+ if (hasRemoteSource) {
819
+ acc.push(rest);
820
+ } else {
821
+ unresolved.push(rest);
822
+ }
823
+ return acc;
824
+ }, []);
825
+ if (unresolved.length) {
826
+ const text = `Dropped ${unresolved.length} attachment(s) whose upload never completed. Composing with pending uploads requires awaiting them before sending.`;
827
+ const extraData = { attachments: unresolved, message, tags: ["attachments"] };
828
+ client.logger("warn", text, extraData);
829
+ console.warn(text, extraData);
830
+ }
831
+ return changed ? { ...message, attachments: sanitized } : message;
832
+ };
793
833
  var localMessageToNewMessagePayload = (localMessage) => {
794
834
  const {
795
835
  // Remove all timestamp fields and client-specific fields.
@@ -2562,6 +2602,12 @@ var CooldownTimer = class extends WithSubscriptions {
2562
2602
  var isScrapedContent = (attachment) => !!attachment?.og_scrape_url || !!attachment?.title_link;
2563
2603
  var isLocalAttachment = (attachment) => !!attachment?.localMetadata?.id;
2564
2604
  var isLocalUploadAttachment = (attachment) => !!attachment?.localMetadata?.uploadState;
2605
+ var PENDING_UPLOAD_STATES = [
2606
+ "pending",
2607
+ "uploading"
2608
+ ];
2609
+ var isPendingUpload = (attachment) => isLocalUploadAttachment(attachment) && PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
2610
+ var isFinishedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "finished";
2565
2611
  var isFileAttachment = (attachment, supportedVideoFormat = []) => attachment.type === "file" || !!(attachment.mime_type && supportedVideoFormat.indexOf(attachment.mime_type) === -1 && attachment.type !== "video");
2566
2612
  var isLocalFileAttachment = (attachment) => isFileAttachment(attachment) && isLocalAttachment(attachment);
2567
2613
  var isImageAttachment = (attachment) => attachment.type === "image" && !isScrapedContent(attachment);
@@ -2637,7 +2683,7 @@ var ensureIsLocalAttachment = (attachment) => {
2637
2683
  };
2638
2684
 
2639
2685
  // src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts
2640
- var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2686
+ var createPostUploadAttachmentEnrichmentMiddleware = (composer) => ({
2641
2687
  id: "stream-io/attachment-manager-middleware/post-upload-enrichment",
2642
2688
  handlers: {
2643
2689
  postProcess: ({
@@ -2650,9 +2696,10 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2650
2696
  if (error) return forward();
2651
2697
  if (!attachment || !response) return discard();
2652
2698
  const enrichedAttachment = { ...attachment };
2653
- const previewUri = attachment.localMetadata.previewUri;
2654
- if (previewUri) {
2655
- if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
2699
+ const { id, previewUri } = attachment.localMetadata;
2700
+ const attachmentIsStillInComposer = !composer || !!composer.attachmentManager.attachmentsById[id];
2701
+ if (previewUri && attachmentIsStillInComposer) {
2702
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
2656
2703
  delete enrichedAttachment.localMetadata.previewUri;
2657
2704
  }
2658
2705
  if (isLocalImageAttachment(attachment)) {
@@ -2675,6 +2722,12 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2675
2722
  var withoutConcurrency = createRunner(wrapWithContinuationTracking);
2676
2723
  var withCancellation = createRunner(wrapWithCancellation);
2677
2724
  var pendingPromises = /* @__PURE__ */ new Map();
2725
+ function hasPending(tag) {
2726
+ return pendingPromises.has(tag);
2727
+ }
2728
+ async function settled(tag) {
2729
+ await pendingPromises.get(tag)?.promise;
2730
+ }
2678
2731
  function createRunner(wrapper) {
2679
2732
  return function run(tag, cb) {
2680
2733
  const { cb: wrapped, onContinued } = wrapper(tag, cb);
@@ -2717,6 +2770,16 @@ var MiddlewareExecutor = class {
2717
2770
  this.middleware = [];
2718
2771
  this.id = generateUUIDv4();
2719
2772
  }
2773
+ /**
2774
+ * The middleware currently installed, in execution order.
2775
+ *
2776
+ * Read-only: use `use` / `insert` / `replace` / `setOrder` / `remove` to change the chain. It
2777
+ * exists so components can ask what the chain is capable of - a middleware may declare
2778
+ * behaviour its host has to account for, and the host has no other way to find out.
2779
+ */
2780
+ get installedMiddleware() {
2781
+ return this.middleware;
2782
+ }
2720
2783
  use(middleware) {
2721
2784
  this.middleware = this.middleware.concat(middleware);
2722
2785
  return this;
@@ -2820,6 +2883,142 @@ var MiddlewareExecutor = class {
2820
2883
  }
2821
2884
  };
2822
2885
 
2886
+ // src/uploadManager.ts
2887
+ var import_axios = __toESM(require("axios"));
2888
+ var isUploadCancellation = (error) => import_axios.default.isCancel(error) || error instanceof Error && error.name === "AbortError";
2889
+ var initState = () => ({ uploads: {} });
2890
+ var upsertById = (uploads, record) => ({
2891
+ ...uploads,
2892
+ [record.id]: { ...uploads[record.id], ...record }
2893
+ });
2894
+ var updateById = (uploads, record) => {
2895
+ if (!(record.id in uploads)) return null;
2896
+ const current = uploads[record.id];
2897
+ return { ...uploads, [record.id]: { ...current, ...record } };
2898
+ };
2899
+ var UploadManager = class {
2900
+ constructor(client) {
2901
+ this.client = client;
2902
+ this.inFlightUploads = /* @__PURE__ */ new Map();
2903
+ this.getUpload = (id) => this.uploads[id];
2904
+ /**
2905
+ * Clears all upload records.
2906
+ * Invoked when the user disconnects so a later session does not inherit stale upload state.
2907
+ * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
2908
+ */
2909
+ this.reset = () => {
2910
+ for (const { abortController } of this.inFlightUploads.values()) {
2911
+ abortController.abort();
2912
+ }
2913
+ this.inFlightUploads.clear();
2914
+ this.state.next(initState());
2915
+ };
2916
+ /**
2917
+ * Removes the upload record for `id` if present.
2918
+ * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
2919
+ */
2920
+ this.deleteUploadRecord = (id) => {
2921
+ const flight = this.inFlightUploads.get(id);
2922
+ if (flight) {
2923
+ this.inFlightUploads.delete(id);
2924
+ flight.abortController.abort();
2925
+ }
2926
+ this.state.next((current) => {
2927
+ if (!(id in current.uploads)) return current;
2928
+ const uploads = { ...current.uploads };
2929
+ delete uploads[id];
2930
+ return { ...current, uploads };
2931
+ });
2932
+ };
2933
+ /**
2934
+ * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
2935
+ * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
2936
+ * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
2937
+ */
2938
+ this.upload = ({
2939
+ id,
2940
+ channelCid,
2941
+ file
2942
+ }) => {
2943
+ const existing = this.inFlightUploads.get(id);
2944
+ if (existing) return existing.promise;
2945
+ let resolvePromise;
2946
+ let rejectPromise;
2947
+ const promise = new Promise((resolve, reject) => {
2948
+ resolvePromise = resolve;
2949
+ rejectPromise = reject;
2950
+ });
2951
+ const abortController = new AbortController();
2952
+ this.inFlightUploads.set(id, { promise, abortController });
2953
+ void (async () => {
2954
+ const attachmentManager = this.resolveAttachmentManager(channelCid);
2955
+ const trackProgress = attachmentManager.config.trackUploadProgress;
2956
+ try {
2957
+ this.upsertUpload({
2958
+ uploadConfirmationPending: false,
2959
+ id,
2960
+ uploadProgress: trackProgress ? 0 : void 0
2961
+ });
2962
+ const onProgress = trackProgress ? (progress) => {
2963
+ this.updateUpload({
2964
+ id,
2965
+ uploadProgress: progress,
2966
+ // Only a number says anything about the flush, so a report without one leaves
2967
+ // the flag alone: `undefined` means the transport cannot measure this upload,
2968
+ // not that bytes were un-sent. Lowering it there would drop a UI that had
2969
+ // already gone indeterminate back to a determinate one with nothing to show.
2970
+ //
2971
+ // The record is removed in the `finally` below, so a record that still exists
2972
+ // while reporting 100% can only mean "flushed, awaiting the response".
2973
+ ...typeof progress === "number" ? { uploadConfirmationPending: progress >= 100 } : {}
2974
+ });
2975
+ } : void 0;
2976
+ const uploadRequestOptions = {
2977
+ abortSignal: abortController.signal,
2978
+ ...onProgress ? { onProgress } : {}
2979
+ };
2980
+ const response = await attachmentManager.doUploadRequest(
2981
+ file,
2982
+ uploadRequestOptions
2983
+ );
2984
+ resolvePromise(response);
2985
+ } catch (error) {
2986
+ rejectPromise(error);
2987
+ } finally {
2988
+ this.inFlightUploads.delete(id);
2989
+ this.deleteUploadRecord(id);
2990
+ }
2991
+ })();
2992
+ return promise;
2993
+ };
2994
+ this.upsertUpload = (record) => {
2995
+ this.state.partialNext({
2996
+ uploads: upsertById(this.uploads, record)
2997
+ });
2998
+ };
2999
+ this.updateUpload = (record) => {
3000
+ this.state.next((current) => {
3001
+ const nextUploads = updateById(current.uploads, record);
3002
+ if (!nextUploads) return current;
3003
+ return { ...current, uploads: nextUploads };
3004
+ });
3005
+ };
3006
+ this.state = new StateStore(initState());
3007
+ }
3008
+ resolveAttachmentManager(channelCid) {
3009
+ const colon = channelCid.indexOf(":");
3010
+ if (colon <= 0 || colon === channelCid.length - 1) {
3011
+ throw new Error(`Invalid channelCid: ${channelCid}`);
3012
+ }
3013
+ const channelType = channelCid.slice(0, colon);
3014
+ const channelId = channelCid.slice(colon + 1);
3015
+ return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
3016
+ }
3017
+ get uploads() {
3018
+ return this.state.getLatestValue().uploads;
3019
+ }
3020
+ };
3021
+
2823
3022
  // src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts
2824
3023
  var createUploadErrorHandlerMiddleware = (composer) => ({
2825
3024
  id: "stream-io/attachment-manager-middleware/upload-error",
@@ -2832,6 +3031,7 @@ var createUploadErrorHandlerMiddleware = (composer) => ({
2832
3031
  const { attachment, error } = state;
2833
3032
  if (!error) return forward();
2834
3033
  if (!attachment) return discard();
3034
+ if (isUploadCancellation(error)) return forward();
2835
3035
  const reason = error instanceof Error ? error.message : "unknown error";
2836
3036
  composer.client.notifications.addError({
2837
3037
  message: "Error uploading attachment",
@@ -2856,7 +3056,7 @@ var AttachmentPostUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2856
3056
  super();
2857
3057
  this.use([
2858
3058
  createUploadErrorHandlerMiddleware(composer),
2859
- createPostUploadAttachmentEnrichmentMiddleware()
3059
+ createPostUploadAttachmentEnrichmentMiddleware(composer)
2860
3060
  ]);
2861
3061
  }
2862
3062
  };
@@ -3304,7 +3504,7 @@ function mergeWithDiff(target, source, customizer) {
3304
3504
  }
3305
3505
 
3306
3506
  // src/messageComposer/attachmentManager.ts
3307
- var initState = ({
3507
+ var initState2 = ({
3308
3508
  message
3309
3509
  }) => ({
3310
3510
  attachments: (message?.attachments ?? [])?.filter(({ og_scrape_url }) => !og_scrape_url).map((att) => {
@@ -3332,13 +3532,14 @@ var _AttachmentManager = class _AttachmentManager {
3332
3532
  ...attachment,
3333
3533
  localMetadata: {
3334
3534
  ...attachment.localMetadata,
3535
+ uploadConfirmationPending: false,
3335
3536
  uploadProgress: void 0,
3336
3537
  uploadState: "failed"
3337
3538
  }
3338
3539
  };
3339
3540
  };
3340
3541
  this.initState = ({ message } = {}) => {
3341
- this.state.next(initState({ message }));
3542
+ this.state.next(initState2({ message }));
3342
3543
  };
3343
3544
  this.getSnapshot = () => {
3344
3545
  const state = this.state.getLatestValue();
@@ -3415,8 +3616,22 @@ var _AttachmentManager = class _AttachmentManager {
3415
3616
  this.state.partialNext({ attachments });
3416
3617
  }
3417
3618
  };
3619
+ /**
3620
+ * Releases a local preview URL the SDK minted, if it minted one.
3621
+ *
3622
+ * Only `blob:` URLs are ours to revoke: `toLocalUploadAttachment` puts a
3623
+ * `URL.createObjectURL` result in `previewUri` for a `File`, but for a React Native
3624
+ * `FileReference` it stores the caller's own native file URI, which must be left alone.
3625
+ */
3626
+ this.revokePreviewUri = (attachment) => {
3627
+ const previewUri = attachment.localMetadata?.previewUri;
3628
+ if (previewUri?.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
3629
+ };
3418
3630
  this.removeAttachments = (localAttachmentIds) => {
3419
3631
  if (!localAttachmentIds.length) return;
3632
+ const removedAttachments = this.attachments.filter(
3633
+ (attachment) => localAttachmentIds.includes(attachment.localMetadata?.id)
3634
+ );
3420
3635
  this.state.partialNext({
3421
3636
  attachments: this.attachments.filter(
3422
3637
  (attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id)
@@ -3425,6 +3640,7 @@ var _AttachmentManager = class _AttachmentManager {
3425
3640
  for (const id of localAttachmentIds) {
3426
3641
  this.client.uploadManager.deleteUploadRecord(id);
3427
3642
  }
3643
+ removedAttachments.forEach(this.revokePreviewUri);
3428
3644
  };
3429
3645
  this.getUploadConfigCheck = async (fileLike) => {
3430
3646
  const client = this.channel.getClient();
@@ -3576,21 +3792,27 @@ var _AttachmentManager = class _AttachmentManager {
3576
3792
  localMetadata: {
3577
3793
  ...attachment.localMetadata,
3578
3794
  uploadState: "failed",
3579
- uploadProgress: void 0
3795
+ uploadProgress: void 0,
3796
+ // `false`, not `undefined`: these updates go through `updateAttachment`, which merges
3797
+ // via `mergeWith` — and that skips `undefined` source values, so `undefined` would
3798
+ // leave a stale `true` in place.
3799
+ uploadConfirmationPending: false
3580
3800
  }
3581
3801
  };
3582
- this.client.notifications.addError({
3583
- message: "Error uploading attachment",
3584
- origin: {
3585
- emitter: "AttachmentManager",
3586
- context: { attachment, failedAttachment }
3587
- },
3588
- options: {
3589
- type: "api:attachment:upload:failed",
3590
- metadata: { reason },
3591
- originalError: error instanceof Error ? error : void 0
3592
- }
3593
- });
3802
+ if (!isUploadCancellation(error)) {
3803
+ this.client.notifications.addError({
3804
+ message: "Error uploading attachment",
3805
+ origin: {
3806
+ emitter: "AttachmentManager",
3807
+ context: { attachment, failedAttachment }
3808
+ },
3809
+ options: {
3810
+ type: "api:attachment:upload:failed",
3811
+ metadata: { reason },
3812
+ originalError: error instanceof Error ? error : void 0
3813
+ }
3814
+ });
3815
+ }
3594
3816
  this.updateAttachment(failedAttachment);
3595
3817
  return failedAttachment;
3596
3818
  }
@@ -3603,7 +3825,9 @@ var _AttachmentManager = class _AttachmentManager {
3603
3825
  localMetadata: {
3604
3826
  ...attachment.localMetadata,
3605
3827
  uploadState: "finished",
3606
- uploadProgress: void 0
3828
+ uploadProgress: void 0,
3829
+ // See the note in the failure branch: `undefined` cannot clear through `mergeWith`.
3830
+ uploadConfirmationPending: false
3607
3831
  }
3608
3832
  };
3609
3833
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3652,7 +3876,9 @@ var _AttachmentManager = class _AttachmentManager {
3652
3876
  localMetadata: {
3653
3877
  ...attachment.localMetadata,
3654
3878
  uploadState: error ? "failed" : "finished",
3655
- uploadProgress: void 0
3879
+ uploadProgress: void 0,
3880
+ // See the note in `uploadAttachment`: `undefined` cannot clear through `mergeWith`.
3881
+ uploadConfirmationPending: false
3656
3882
  }
3657
3883
  },
3658
3884
  error,
@@ -3677,7 +3903,7 @@ var _AttachmentManager = class _AttachmentManager {
3677
3903
  );
3678
3904
  };
3679
3905
  this.composer = composer;
3680
- this.state = new StateStore(initState({ message }));
3906
+ this.state = new StateStore(initState2({ message }));
3681
3907
  this.attachmentsByIdGetterCache = { attachmentsById: {}, attachments: [] };
3682
3908
  this.preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor({
3683
3909
  composer
@@ -3782,7 +4008,8 @@ var _AttachmentManager = class _AttachmentManager {
3782
4008
  localMetadata: {
3783
4009
  ...attachment.localMetadata,
3784
4010
  uploadState: "uploading",
3785
- uploadProgress: this.config.trackUploadProgress ? 0 : void 0
4011
+ uploadProgress: this.config.trackUploadProgress ? 0 : void 0,
4012
+ uploadConfirmationPending: false
3786
4013
  }
3787
4014
  }
3788
4015
  ]);
@@ -3795,7 +4022,8 @@ var _AttachmentManager = class _AttachmentManager {
3795
4022
  localMetadata: {
3796
4023
  ...attachment.localMetadata,
3797
4024
  uploadState: "uploading",
3798
- uploadProgress: nextUpload.uploadProgress
4025
+ uploadProgress: nextUpload.uploadProgress,
4026
+ uploadConfirmationPending: nextUpload.uploadConfirmationPending
3799
4027
  }
3800
4028
  });
3801
4029
  }
@@ -3849,6 +4077,50 @@ var localAttachmentToAttachment = (localAttachment) => {
3849
4077
  const { localMetadata, ...attachment } = localAttachment;
3850
4078
  return attachment;
3851
4079
  };
4080
+ var composeWithPendingUploads = ({
4081
+ composer,
4082
+ state
4083
+ }) => {
4084
+ const composerIsKeptAsDraft = !!composer.pollId;
4085
+ const relevantAttachments = composer.attachmentManager.attachments.filter(
4086
+ (attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && isPendingUpload(attachment)
4087
+ );
4088
+ const localAttachments = (state.localMessage.attachments ?? []).concat(
4089
+ relevantAttachments.map(
4090
+ (attachment) => isPendingUpload(attachment) ? attachment : localAttachmentToAttachment(attachment)
4091
+ )
4092
+ );
4093
+ const messageAttachments = (state.message.attachments ?? []).concat(
4094
+ relevantAttachments.filter(isFinishedUpload).map(localAttachmentToAttachment)
4095
+ );
4096
+ if (!localAttachments.length && !messageAttachments.length) return state;
4097
+ return {
4098
+ ...state,
4099
+ localMessage: localAttachments.length ? {
4100
+ ...state.localMessage,
4101
+ attachments: localAttachments
4102
+ } : state.localMessage,
4103
+ message: messageAttachments.length ? {
4104
+ ...state.message,
4105
+ attachments: messageAttachments
4106
+ } : state.message
4107
+ };
4108
+ };
4109
+ var createSendWithPendingUploadsAttachmentsMiddleware = (composer) => ({
4110
+ allowsPendingUploads: true,
4111
+ id: "stream-io/message-composer-middleware/attachments",
4112
+ handlers: {
4113
+ compose: ({
4114
+ state,
4115
+ next,
4116
+ forward
4117
+ }) => {
4118
+ const { attachmentManager } = composer;
4119
+ if (!attachmentManager) return forward();
4120
+ return next(composeWithPendingUploads({ composer, state }));
4121
+ }
4122
+ }
4123
+ });
3852
4124
  var createAttachmentsCompositionMiddleware = (composer) => ({
3853
4125
  id: "stream-io/message-composer-middleware/attachments",
3854
4126
  handlers: {
@@ -4065,7 +4337,7 @@ var getInitialMentions = (message) => {
4065
4337
  };
4066
4338
  var isSameMentionEntity = (first, second) => first.id === second.id && first.mentionType === second.mentionType;
4067
4339
  var getMentionsFromState = (state) => state.mentions ?? state.mentionedUsers.map(userResponseToMentionEntity);
4068
- var initState2 = ({
4340
+ var initState3 = ({
4069
4341
  composer,
4070
4342
  message
4071
4343
  }) => {
@@ -4094,7 +4366,7 @@ var initState2 = ({
4094
4366
  var TextComposer = class {
4095
4367
  constructor({ composer, message }) {
4096
4368
  this.initState = ({ message } = {}) => {
4097
- this.state.next(initState2({ composer: this.composer, message }));
4369
+ this.state.next(initState3({ composer: this.composer, message }));
4098
4370
  };
4099
4371
  this.getSnapshot = (state = this.state.getLatestValue()) => state;
4100
4372
  this.restoreSnapshot = (snapshot) => {
@@ -4287,7 +4559,7 @@ var TextComposer = class {
4287
4559
  this.commitState(output.state);
4288
4560
  };
4289
4561
  this.composer = composer;
4290
- this.state = new StateStore(initState2({ composer, message }));
4562
+ this.state = new StateStore(initState3({ composer, message }));
4291
4563
  this.middlewareExecutor = new TextComposerMiddlewareExecutor({ composer });
4292
4564
  }
4293
4565
  get channel() {
@@ -5989,6 +6261,13 @@ var MessageComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5989
6261
  createCompositionDataCleanupMiddleware(composer)
5990
6262
  ]);
5991
6263
  }
6264
+ /**
6265
+ * Narrows the base accessor to the composition middleware type, so callers can read the
6266
+ * declarations it carries (`allowsPendingUploads`) without casting.
6267
+ */
6268
+ get installedMiddleware() {
6269
+ return super.installedMiddleware;
6270
+ }
5992
6271
  };
5993
6272
  var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5994
6273
  constructor({ composer }) {
@@ -7103,7 +7382,7 @@ var DEFAULT_COMPOSER_CONFIG = {
7103
7382
  };
7104
7383
 
7105
7384
  // src/messageComposer/CustomDataManager.ts
7106
- var initState3 = (options) => {
7385
+ var initState4 = (options) => {
7107
7386
  if (!options)
7108
7387
  return { message: {}, custom: {} };
7109
7388
  return { message: {}, custom: {} };
@@ -7112,14 +7391,14 @@ var CustomDataManager = class {
7112
7391
  constructor({ composer, message }) {
7113
7392
  this.isMessageDataEqual = (nextState, previousState) => JSON.stringify(nextState.message) === JSON.stringify(previousState?.message);
7114
7393
  this.initState = ({ message } = {}) => {
7115
- this.state.next(initState3({ composer: this.composer, message }));
7394
+ this.state.next(initState4({ composer: this.composer, message }));
7116
7395
  };
7117
7396
  this.getSnapshot = () => this.state.getLatestValue();
7118
7397
  this.restoreSnapshot = (snapshot) => {
7119
7398
  this.state.next(snapshot);
7120
7399
  };
7121
7400
  this.composer = composer;
7122
- this.state = new StateStore(initState3({ composer, message }));
7401
+ this.state = new StateStore(initState4({ composer, message }));
7123
7402
  }
7124
7403
  get customMessageData() {
7125
7404
  return this.state.getLatestValue().message;
@@ -7155,7 +7434,7 @@ var LinkPreviewStatus = /* @__PURE__ */ ((LinkPreviewStatus2) => {
7155
7434
  return LinkPreviewStatus2;
7156
7435
  })(LinkPreviewStatus || {});
7157
7436
  var linkPreviewArrayToMap = (linkPreviews) => new Map(linkPreviews.map((linkPreview) => [linkPreview.og_scrape_url, linkPreview]));
7158
- var initState4 = ({
7437
+ var initState5 = ({
7159
7438
  message
7160
7439
  }) => message ? {
7161
7440
  previews: message.attachments?.reduce((acc, attachment) => {
@@ -7174,7 +7453,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7174
7453
  this.shouldDiscardEnrichQueries = false;
7175
7454
  this.initState = ({ message } = {}) => {
7176
7455
  this.cancelURLEnrichment();
7177
- this.state.next(initState4({ message: this.enabled ? message : void 0 }));
7456
+ this.state.next(initState5({ message: this.enabled ? message : void 0 }));
7178
7457
  };
7179
7458
  this.getSnapshot = () => this.state.getLatestValue();
7180
7459
  this.restoreSnapshot = (snapshot) => {
@@ -7277,7 +7556,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7277
7556
  };
7278
7557
  this.composer = composer;
7279
7558
  this.state = new StateStore(
7280
- initState4({ message: this.enabled ? message : void 0 })
7559
+ initState5({ message: this.enabled ? message : void 0 })
7281
7560
  );
7282
7561
  this.findAndEnrichUrls = debounce(
7283
7562
  this._findAndEnrichUrls.bind(this),
@@ -7365,7 +7644,7 @@ var LinkPreviewsManager = _LinkPreviewsManager;
7365
7644
 
7366
7645
  // src/messageComposer/LocationComposer.ts
7367
7646
  var MIN_LIVE_LOCATION_SHARE_DURATION = 60 * 1e3;
7368
- var initState5 = ({
7647
+ var initState6 = ({
7369
7648
  message
7370
7649
  }) => ({
7371
7650
  location: message?.shared_location ?? null
@@ -7373,7 +7652,7 @@ var initState5 = ({
7373
7652
  var LocationComposer = class {
7374
7653
  constructor({ composer, message }) {
7375
7654
  this.initState = ({ message } = {}) => {
7376
- this.state.next(initState5({ message }));
7655
+ this.state.next(initState6({ message }));
7377
7656
  };
7378
7657
  this.getSnapshot = () => this.state.getLatestValue();
7379
7658
  this.restoreSnapshot = (snapshot) => {
@@ -7391,7 +7670,7 @@ var LocationComposer = class {
7391
7670
  });
7392
7671
  };
7393
7672
  this.composer = composer;
7394
- this.state = new StateStore(initState5({ message }));
7673
+ this.state = new StateStore(initState6({ message }));
7395
7674
  this._deviceId = this.config.getDeviceId();
7396
7675
  }
7397
7676
  get config() {
@@ -8112,7 +8391,7 @@ var initEditingAuditState = (composition) => {
8112
8391
  }
8113
8392
  };
8114
8393
  };
8115
- var initState6 = (composition) => {
8394
+ var initState7 = (composition) => {
8116
8395
  if (!composition) {
8117
8396
  return {
8118
8397
  draftId: null,
@@ -8199,7 +8478,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8199
8478
  this.textComposer.initState({ message });
8200
8479
  this.pollComposer.initState();
8201
8480
  this.customDataManager.initState({ message });
8202
- this.state.next(initState6(composition));
8481
+ this.state.next(initState7(composition));
8203
8482
  };
8204
8483
  this.initStateFromChannelResponse = (channelApiResponse) => {
8205
8484
  if (this.channel.cid !== channelApiResponse.channel.cid) {
@@ -8665,7 +8944,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8665
8944
  this.editingAuditState = new StateStore(
8666
8945
  this.initEditingAuditState(composition)
8667
8946
  );
8668
- this.state = new StateStore(initState6(composition));
8947
+ this.state = new StateStore(initState7(composition));
8669
8948
  this.compositionMiddlewareExecutor = new MessageComposerMiddlewareExecutor({
8670
8949
  composer: this
8671
8950
  });
@@ -8744,8 +9023,32 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8744
9023
  const currentCommand = this.textComposer.command;
8745
9024
  return !currentCommand || this.validateCommandSendability(currentCommand).ready;
8746
9025
  }
9026
+ /**
9027
+ * Whether the installed composition middleware composes messages whose uploads are still in
9028
+ * flight — see `MessageCompositionMiddleware.allowsPendingUploads`.
9029
+ *
9030
+ * Derived from the chain rather than from config, so there is exactly one switch: the UI SDK
9031
+ * that implements the rest of the flow installs the middleware, and everything that has to
9032
+ * follow from that (starting with {@link hasSendableData}) follows on its own.
9033
+ *
9034
+ * **Temporary API.** In v10 sending with pending uploads becomes part of the composer
9035
+ * configuration (`MessageComposerConfig`, set through `updateConfig`), and this getter,
9036
+ * together with the middleware declaration it reads, is replaced by that config field.
9037
+ */
9038
+ get allowsPendingUploads() {
9039
+ return this.compositionMiddlewareExecutor.installedMiddleware.some(
9040
+ (middleware) => middleware.allowsPendingUploads
9041
+ );
9042
+ }
8747
9043
  get hasSendableData() {
8748
- return this.isCommandSendable && !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
9044
+ if (!this.isCommandSendable) return false;
9045
+ if (this.allowsPendingUploads) {
9046
+ const hasSendableAttachment = this.attachmentManager.attachments.some(
9047
+ (attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment)
9048
+ );
9049
+ return !!(!this.textComposer.textIsEmpty || hasSendableAttachment || this.pollId || this.locationComposer.validLocation);
9050
+ }
9051
+ return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
8749
9052
  }
8750
9053
  get compositionIsEmpty() {
8751
9054
  return !this.quotedMessage && this.contentIsEmpty;
@@ -9372,14 +9675,16 @@ var Channel = class {
9372
9675
  *
9373
9676
  * @return {Promise<SendMessageAPIResponse>} The Server Response
9374
9677
  */
9375
- async _sendMessage(message, options) {
9376
- return await this.getClient().post(
9377
- this._channelURL() + "/message",
9378
- {
9379
- message,
9380
- ...options
9381
- }
9382
- );
9678
+ async _sendMessage(rawMessage, options) {
9679
+ const client = this.getClient();
9680
+ const message = sanitizeOutgoingAttachments({
9681
+ client,
9682
+ message: rawMessage
9683
+ });
9684
+ return await client.post(this._channelURL() + "/message", {
9685
+ message,
9686
+ ...options
9687
+ });
9383
9688
  }
9384
9689
  async sendMessage(message, options) {
9385
9690
  try {
@@ -10830,6 +11135,7 @@ var Channel = class {
10830
11135
  // delivery-report network sync below is skipped for it.
10831
11136
  case "message.read_locally":
10832
11137
  case "message.read":
11138
+ if (event.thread) break;
10833
11139
  if (event.user?.id && event.created_at) {
10834
11140
  const previousReadState = channelState.read[event.user.id];
10835
11141
  channelState.read[event.user.id] = {
@@ -11279,7 +11585,7 @@ var ClientState = class {
11279
11585
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
11280
11586
 
11281
11587
  // src/insights.ts
11282
- var import_axios = __toESM(require("axios"));
11588
+ var import_axios2 = __toESM(require("axios"));
11283
11589
  var InsightMetrics = class {
11284
11590
  constructor() {
11285
11591
  this.connectionStartTimestamp = null;
@@ -11292,7 +11598,7 @@ var postInsights = async (insightType, insights) => {
11292
11598
  const maxAttempts = 3;
11293
11599
  for (let i = 0; i < maxAttempts; i++) {
11294
11600
  try {
11295
- await import_axios.default.post(
11601
+ await import_axios2.default.post(
11296
11602
  `https://chat-insights.getstream.io/insights/${insightType}`,
11297
11603
  insights
11298
11604
  );
@@ -11800,131 +12106,6 @@ var StableWSConnection = class {
11800
12106
  }
11801
12107
  };
11802
12108
 
11803
- // src/uploadManager.ts
11804
- var initState7 = () => ({ uploads: {} });
11805
- var upsertById = (uploads, record) => ({
11806
- ...uploads,
11807
- [record.id]: { ...uploads[record.id], ...record }
11808
- });
11809
- var updateById = (uploads, record) => {
11810
- if (!(record.id in uploads)) return null;
11811
- const current = uploads[record.id];
11812
- return { ...uploads, [record.id]: { ...current, ...record } };
11813
- };
11814
- var UploadManager = class {
11815
- constructor(client) {
11816
- this.client = client;
11817
- this.inFlightUploads = /* @__PURE__ */ new Map();
11818
- this.getUpload = (id) => this.uploads[id];
11819
- /**
11820
- * Clears all upload records.
11821
- * Invoked when the user disconnects so a later session does not inherit stale upload state.
11822
- * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
11823
- */
11824
- this.reset = () => {
11825
- for (const { abortController } of this.inFlightUploads.values()) {
11826
- abortController.abort();
11827
- }
11828
- this.inFlightUploads.clear();
11829
- this.state.next(initState7());
11830
- };
11831
- /**
11832
- * Removes the upload record for `id` if present.
11833
- * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
11834
- */
11835
- this.deleteUploadRecord = (id) => {
11836
- const flight = this.inFlightUploads.get(id);
11837
- if (flight) {
11838
- this.inFlightUploads.delete(id);
11839
- flight.abortController.abort();
11840
- }
11841
- this.state.next((current) => {
11842
- if (!(id in current.uploads)) return current;
11843
- const uploads = { ...current.uploads };
11844
- delete uploads[id];
11845
- return { ...current, uploads };
11846
- });
11847
- };
11848
- /**
11849
- * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
11850
- * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
11851
- * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
11852
- */
11853
- this.upload = ({
11854
- id,
11855
- channelCid,
11856
- file
11857
- }) => {
11858
- const existing = this.inFlightUploads.get(id);
11859
- if (existing) return existing.promise;
11860
- let resolvePromise;
11861
- let rejectPromise;
11862
- const promise = new Promise((resolve, reject) => {
11863
- resolvePromise = resolve;
11864
- rejectPromise = reject;
11865
- });
11866
- const abortController = new AbortController();
11867
- this.inFlightUploads.set(id, { promise, abortController });
11868
- void (async () => {
11869
- const attachmentManager = this.resolveAttachmentManager(channelCid);
11870
- const trackProgress = attachmentManager.config.trackUploadProgress;
11871
- try {
11872
- this.upsertUpload({
11873
- id,
11874
- uploadProgress: trackProgress ? 0 : void 0
11875
- });
11876
- const onProgress = trackProgress ? (progress) => {
11877
- this.updateUpload({
11878
- id,
11879
- uploadProgress: progress
11880
- });
11881
- } : void 0;
11882
- const uploadRequestOptions = {
11883
- abortSignal: abortController.signal,
11884
- ...onProgress ? { onProgress } : {}
11885
- };
11886
- const response = await attachmentManager.doUploadRequest(
11887
- file,
11888
- uploadRequestOptions
11889
- );
11890
- resolvePromise(response);
11891
- } catch (error) {
11892
- rejectPromise(error);
11893
- } finally {
11894
- this.inFlightUploads.delete(id);
11895
- this.deleteUploadRecord(id);
11896
- }
11897
- })();
11898
- return promise;
11899
- };
11900
- this.upsertUpload = (record) => {
11901
- this.state.partialNext({
11902
- uploads: upsertById(this.uploads, record)
11903
- });
11904
- };
11905
- this.updateUpload = (record) => {
11906
- this.state.next((current) => {
11907
- const nextUploads = updateById(current.uploads, record);
11908
- if (!nextUploads) return current;
11909
- return { ...current, uploads: nextUploads };
11910
- });
11911
- };
11912
- this.state = new StateStore(initState7());
11913
- }
11914
- resolveAttachmentManager(channelCid) {
11915
- const colon = channelCid.indexOf(":");
11916
- if (colon <= 0 || colon === channelCid.length - 1) {
11917
- throw new Error(`Invalid channelCid: ${channelCid}`);
11918
- }
11919
- const channelType = channelCid.slice(0, colon);
11920
- const channelId = channelCid.slice(colon + 1);
11921
- return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
11922
- }
11923
- get uploads() {
11924
- return this.state.getLatestValue().uploads;
11925
- }
11926
- };
11927
-
11928
12109
  // src/signing.ts
11929
12110
  var import_jsonwebtoken = __toESM(require_jsonwebtoken());
11930
12111
  var import_crypto = __toESM(require_crypto());
@@ -12183,7 +12364,7 @@ var TokenManager = class {
12183
12364
  };
12184
12365
 
12185
12366
  // src/connection_fallback.ts
12186
- var import_axios2 = __toESM(require("axios"));
12367
+ var import_axios3 = __toESM(require("axios"));
12187
12368
  var WSConnectionFallback = class {
12188
12369
  constructor({ client }) {
12189
12370
  /** @private */
@@ -12202,7 +12383,7 @@ var WSConnectionFallback = class {
12202
12383
  /** @private */
12203
12384
  this._req = async (params, config, retry) => {
12204
12385
  if (!this.cancelToken && !params.close) {
12205
- this.cancelToken = import_axios2.default.CancelToken.source();
12386
+ this.cancelToken = import_axios3.default.CancelToken.source();
12206
12387
  }
12207
12388
  try {
12208
12389
  const res = await this.client.doAxiosRequest(
@@ -12238,7 +12419,7 @@ var WSConnectionFallback = class {
12238
12419
  }
12239
12420
  }
12240
12421
  } catch (error) {
12241
- if (import_axios2.default.isCancel(error)) {
12422
+ if (import_axios3.default.isCancel(error)) {
12242
12423
  this._log(`_poll() - axios canceled request`);
12243
12424
  return;
12244
12425
  }
@@ -14661,7 +14842,7 @@ var StreamChat = class _StreamChat {
14661
14842
  return this.handleResponse(response);
14662
14843
  } catch (e) {
14663
14844
  e.client_request_id = requestConfig.headers?.["x-client-request-id"];
14664
- if (import_axios3.default.isCancel(e)) throw e;
14845
+ if (import_axios4.default.isCancel(e)) throw e;
14665
14846
  this._logApiError(type, url, e);
14666
14847
  this.consecutiveFailures += 1;
14667
14848
  if (e.response) {
@@ -14973,7 +15154,7 @@ var StreamChat = class _StreamChat {
14973
15154
  return this.userAgent;
14974
15155
  }
14975
15156
  if (!this.cachedUserAgent) {
14976
- const version = "9.51.0";
15157
+ const version = "9.52.0";
14977
15158
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14978
15159
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14979
15160
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -15041,7 +15222,7 @@ var StreamChat = class _StreamChat {
15041
15222
  keepAliveMsecs: 3e3
15042
15223
  });
15043
15224
  }
15044
- this.axiosInstance = import_axios3.default.create(this.options);
15225
+ this.axiosInstance = import_axios4.default.create(this.options);
15045
15226
  this.setBaseURL(this.options.baseURL || "https://chat.stream-io-api.com");
15046
15227
  if (typeof process !== "undefined" && "env" in process && process.env.STREAM_LOCAL_TEST_RUN) {
15047
15228
  this.setBaseURL("http://localhost:3030");
@@ -16393,6 +16574,11 @@ var StreamChat = class _StreamChat {
16393
16574
  }
16394
16575
  /**
16395
16576
  * unflagMessage - unflag a message
16577
+ *
16578
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16579
+ * the backend - calling this resolves successfully but does not remove the flag.
16580
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16581
+ *
16396
16582
  * @param {string} targetMessageID
16397
16583
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16398
16584
  * @returns {Promise<APIResponse>}
@@ -16405,6 +16591,11 @@ var StreamChat = class _StreamChat {
16405
16591
  }
16406
16592
  /**
16407
16593
  * unflagUser - unflag a user
16594
+ *
16595
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16596
+ * the backend - calling this resolves successfully but does not remove the flag.
16597
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16598
+ *
16408
16599
  * @param {string} targetID
16409
16600
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16410
16601
  * @returns {Promise<APIResponse>}
@@ -16689,7 +16880,10 @@ var StreamChat = class _StreamChat {
16689
16880
  if (!message.id) {
16690
16881
  throw Error("Please specify the message.id when calling updateMessage");
16691
16882
  }
16692
- const payload = toUpdatedMessagePayload(message);
16883
+ const payload = sanitizeOutgoingAttachments({
16884
+ client: this,
16885
+ message: toUpdatedMessagePayload(message)
16886
+ });
16693
16887
  if (typeof partialUserOrUserId === "string") {
16694
16888
  payload.user_id = partialUserOrUserId;
16695
16889
  } else if (typeof partialUserOrUserId?.id === "string") {
@@ -18505,7 +18699,7 @@ var OfflineError = class extends Error {
18505
18699
  };
18506
18700
 
18507
18701
  // src/offline-support/offline_sync_manager.ts
18508
- var import_axios4 = require("axios");
18702
+ var import_axios5 = require("axios");
18509
18703
  var OfflineDBSyncManager = class {
18510
18704
  constructor({
18511
18705
  client,
@@ -18653,7 +18847,7 @@ var OfflineDBSyncManager = class {
18653
18847
  });
18654
18848
  } catch (e) {
18655
18849
  console.log("An error has occurred while syncing the DB.", e);
18656
- if ((0, import_axios4.isAxiosError)(e) && e.code === "ECONNABORTED") {
18850
+ if ((0, import_axios5.isAxiosError)(e) && e.code === "ECONNABORTED") {
18657
18851
  return;
18658
18852
  }
18659
18853
  const error = e;
@@ -19162,6 +19356,7 @@ var AbstractOfflineDB = class {
19162
19356
  return this.handleMessageUpdatedEvent({ event, execute });
19163
19357
  }
19164
19358
  if (type === "message.read" || type === "notification.mark_read") {
19359
+ if (event.thread) return [];
19165
19360
  return this.handleRead({ event, unreadMessages: 0, execute });
19166
19361
  }
19167
19362
  if (type === "message.read_locally") {