stream-chat 9.51.0 → 9.52.1

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 (39) hide show
  1. package/dist/cjs/index.browser.js +418 -215
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +427 -216
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +416 -213
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/channel.d.ts +5 -3
  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 +29 -6
  21. package/package.json +1 -1
  22. package/src/channel.ts +42 -26
  23. package/src/client.ts +27 -14
  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/messageDelivery/MessageDeliveryReporter.ts +5 -4
  36. package/src/middleware.ts +11 -0
  37. package/src/offline-support/offline_support_api.ts +4 -0
  38. package/src/uploadManager.ts +45 -0
  39. package/src/utils.ts +102 -7
@@ -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
@@ -579,6 +587,22 @@ function logChatPromiseExecution(promise, name) {
579
587
  console.warn(`failed to do ${name}, ran into error: `, error);
580
588
  });
581
589
  }
590
+ var noopLogger = () => null;
591
+ var logError = (logger, error, event) => {
592
+ const message = "Unhandled error in event listener";
593
+ logger("error", message, { error, event });
594
+ if (logger === noopLogger) console.error(message, error, event);
595
+ };
596
+ var invokeEventListener = (listener, event, logger) => {
597
+ try {
598
+ const result = listener(event);
599
+ if (result && typeof result.then === "function") {
600
+ Promise.resolve(result).catch((error) => logError(logger, error, event));
601
+ }
602
+ } catch (error) {
603
+ logError(logger, error, event);
604
+ }
605
+ };
582
606
  var sleep = (m) => new Promise((r) => setTimeout(r, m));
583
607
  function isFunction(value) {
584
608
  return typeof value === "function" || value instanceof Function || Object.prototype.toString.call(value) === "[object Function]";
@@ -626,7 +650,6 @@ var channelHasReadEvents = (channel) => {
626
650
  return !(Array.isArray(ownCapabilities) && !ownCapabilities.includes("read-events"));
627
651
  };
628
652
  var channelTracksReadLocally = (channel) => !channelHasReadEvents(channel) && !!channel?.getClient().options.isLocalUnreadCountEnabled;
629
- var userHasReadReceipts = (client) => client.user?.privacy_settings?.read_receipts?.enabled ?? true;
630
653
  function addFileToFormData(uri, name, contentType) {
631
654
  const data = new import_form_data.default();
632
655
  if (isReadableStream(uri) || isBuffer(uri) || isFileWebAPI(uri) || isBlobWebAPI(uri)) {
@@ -790,6 +813,38 @@ function unformatMessage(message) {
790
813
  quoted_message: toMessageResponseBase(message.quoted_message)
791
814
  };
792
815
  }
816
+ var isRemoteUrl = (url) => !!url && /^https?:\/\//i.test(url.trim());
817
+ var sanitizeOutgoingAttachments = ({
818
+ client,
819
+ message
820
+ }) => {
821
+ const attachments = message.attachments;
822
+ if (!attachments?.length) return message;
823
+ const unresolved = [];
824
+ let changed = false;
825
+ const sanitized = attachments.reduce((acc, attachment) => {
826
+ const { localMetadata, ...rest } = attachment;
827
+ if (!localMetadata) {
828
+ acc.push(attachment);
829
+ return acc;
830
+ }
831
+ changed = true;
832
+ const hasRemoteSource = isRemoteUrl(rest.asset_url) || isRemoteUrl(rest.image_url) || isRemoteUrl(rest.og_scrape_url) || isRemoteUrl(rest.title_link);
833
+ if (hasRemoteSource) {
834
+ acc.push(rest);
835
+ } else {
836
+ unresolved.push(rest);
837
+ }
838
+ return acc;
839
+ }, []);
840
+ if (unresolved.length) {
841
+ const text = `Dropped ${unresolved.length} attachment(s) whose upload never completed. Composing with pending uploads requires awaiting them before sending.`;
842
+ const extraData = { attachments: unresolved, message, tags: ["attachments"] };
843
+ client.logger("warn", text, extraData);
844
+ console.warn(text, extraData);
845
+ }
846
+ return changed ? { ...message, attachments: sanitized } : message;
847
+ };
793
848
  var localMessageToNewMessagePayload = (localMessage) => {
794
849
  const {
795
850
  // Remove all timestamp fields and client-specific fields.
@@ -2562,6 +2617,12 @@ var CooldownTimer = class extends WithSubscriptions {
2562
2617
  var isScrapedContent = (attachment) => !!attachment?.og_scrape_url || !!attachment?.title_link;
2563
2618
  var isLocalAttachment = (attachment) => !!attachment?.localMetadata?.id;
2564
2619
  var isLocalUploadAttachment = (attachment) => !!attachment?.localMetadata?.uploadState;
2620
+ var PENDING_UPLOAD_STATES = [
2621
+ "pending",
2622
+ "uploading"
2623
+ ];
2624
+ var isPendingUpload = (attachment) => isLocalUploadAttachment(attachment) && PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
2625
+ var isFinishedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "finished";
2565
2626
  var isFileAttachment = (attachment, supportedVideoFormat = []) => attachment.type === "file" || !!(attachment.mime_type && supportedVideoFormat.indexOf(attachment.mime_type) === -1 && attachment.type !== "video");
2566
2627
  var isLocalFileAttachment = (attachment) => isFileAttachment(attachment) && isLocalAttachment(attachment);
2567
2628
  var isImageAttachment = (attachment) => attachment.type === "image" && !isScrapedContent(attachment);
@@ -2637,7 +2698,7 @@ var ensureIsLocalAttachment = (attachment) => {
2637
2698
  };
2638
2699
 
2639
2700
  // src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts
2640
- var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2701
+ var createPostUploadAttachmentEnrichmentMiddleware = (composer) => ({
2641
2702
  id: "stream-io/attachment-manager-middleware/post-upload-enrichment",
2642
2703
  handlers: {
2643
2704
  postProcess: ({
@@ -2650,9 +2711,10 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2650
2711
  if (error) return forward();
2651
2712
  if (!attachment || !response) return discard();
2652
2713
  const enrichedAttachment = { ...attachment };
2653
- const previewUri = attachment.localMetadata.previewUri;
2654
- if (previewUri) {
2655
- if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
2714
+ const { id, previewUri } = attachment.localMetadata;
2715
+ const attachmentIsStillInComposer = !composer || !!composer.attachmentManager.attachmentsById[id];
2716
+ if (previewUri && attachmentIsStillInComposer) {
2717
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
2656
2718
  delete enrichedAttachment.localMetadata.previewUri;
2657
2719
  }
2658
2720
  if (isLocalImageAttachment(attachment)) {
@@ -2675,6 +2737,12 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2675
2737
  var withoutConcurrency = createRunner(wrapWithContinuationTracking);
2676
2738
  var withCancellation = createRunner(wrapWithCancellation);
2677
2739
  var pendingPromises = /* @__PURE__ */ new Map();
2740
+ function hasPending(tag) {
2741
+ return pendingPromises.has(tag);
2742
+ }
2743
+ async function settled(tag) {
2744
+ await pendingPromises.get(tag)?.promise;
2745
+ }
2678
2746
  function createRunner(wrapper) {
2679
2747
  return function run(tag, cb) {
2680
2748
  const { cb: wrapped, onContinued } = wrapper(tag, cb);
@@ -2717,6 +2785,16 @@ var MiddlewareExecutor = class {
2717
2785
  this.middleware = [];
2718
2786
  this.id = generateUUIDv4();
2719
2787
  }
2788
+ /**
2789
+ * The middleware currently installed, in execution order.
2790
+ *
2791
+ * Read-only: use `use` / `insert` / `replace` / `setOrder` / `remove` to change the chain. It
2792
+ * exists so components can ask what the chain is capable of - a middleware may declare
2793
+ * behaviour its host has to account for, and the host has no other way to find out.
2794
+ */
2795
+ get installedMiddleware() {
2796
+ return this.middleware;
2797
+ }
2720
2798
  use(middleware) {
2721
2799
  this.middleware = this.middleware.concat(middleware);
2722
2800
  return this;
@@ -2820,6 +2898,142 @@ var MiddlewareExecutor = class {
2820
2898
  }
2821
2899
  };
2822
2900
 
2901
+ // src/uploadManager.ts
2902
+ var import_axios = __toESM(require("axios"));
2903
+ var isUploadCancellation = (error) => import_axios.default.isCancel(error) || error instanceof Error && error.name === "AbortError";
2904
+ var initState = () => ({ uploads: {} });
2905
+ var upsertById = (uploads, record) => ({
2906
+ ...uploads,
2907
+ [record.id]: { ...uploads[record.id], ...record }
2908
+ });
2909
+ var updateById = (uploads, record) => {
2910
+ if (!(record.id in uploads)) return null;
2911
+ const current = uploads[record.id];
2912
+ return { ...uploads, [record.id]: { ...current, ...record } };
2913
+ };
2914
+ var UploadManager = class {
2915
+ constructor(client) {
2916
+ this.client = client;
2917
+ this.inFlightUploads = /* @__PURE__ */ new Map();
2918
+ this.getUpload = (id) => this.uploads[id];
2919
+ /**
2920
+ * Clears all upload records.
2921
+ * Invoked when the user disconnects so a later session does not inherit stale upload state.
2922
+ * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
2923
+ */
2924
+ this.reset = () => {
2925
+ for (const { abortController } of this.inFlightUploads.values()) {
2926
+ abortController.abort();
2927
+ }
2928
+ this.inFlightUploads.clear();
2929
+ this.state.next(initState());
2930
+ };
2931
+ /**
2932
+ * Removes the upload record for `id` if present.
2933
+ * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
2934
+ */
2935
+ this.deleteUploadRecord = (id) => {
2936
+ const flight = this.inFlightUploads.get(id);
2937
+ if (flight) {
2938
+ this.inFlightUploads.delete(id);
2939
+ flight.abortController.abort();
2940
+ }
2941
+ this.state.next((current) => {
2942
+ if (!(id in current.uploads)) return current;
2943
+ const uploads = { ...current.uploads };
2944
+ delete uploads[id];
2945
+ return { ...current, uploads };
2946
+ });
2947
+ };
2948
+ /**
2949
+ * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
2950
+ * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
2951
+ * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
2952
+ */
2953
+ this.upload = ({
2954
+ id,
2955
+ channelCid,
2956
+ file
2957
+ }) => {
2958
+ const existing = this.inFlightUploads.get(id);
2959
+ if (existing) return existing.promise;
2960
+ let resolvePromise;
2961
+ let rejectPromise;
2962
+ const promise = new Promise((resolve, reject) => {
2963
+ resolvePromise = resolve;
2964
+ rejectPromise = reject;
2965
+ });
2966
+ const abortController = new AbortController();
2967
+ this.inFlightUploads.set(id, { promise, abortController });
2968
+ void (async () => {
2969
+ const attachmentManager = this.resolveAttachmentManager(channelCid);
2970
+ const trackProgress = attachmentManager.config.trackUploadProgress;
2971
+ try {
2972
+ this.upsertUpload({
2973
+ uploadConfirmationPending: false,
2974
+ id,
2975
+ uploadProgress: trackProgress ? 0 : void 0
2976
+ });
2977
+ const onProgress = trackProgress ? (progress) => {
2978
+ this.updateUpload({
2979
+ id,
2980
+ uploadProgress: progress,
2981
+ // Only a number says anything about the flush, so a report without one leaves
2982
+ // the flag alone: `undefined` means the transport cannot measure this upload,
2983
+ // not that bytes were un-sent. Lowering it there would drop a UI that had
2984
+ // already gone indeterminate back to a determinate one with nothing to show.
2985
+ //
2986
+ // The record is removed in the `finally` below, so a record that still exists
2987
+ // while reporting 100% can only mean "flushed, awaiting the response".
2988
+ ...typeof progress === "number" ? { uploadConfirmationPending: progress >= 100 } : {}
2989
+ });
2990
+ } : void 0;
2991
+ const uploadRequestOptions = {
2992
+ abortSignal: abortController.signal,
2993
+ ...onProgress ? { onProgress } : {}
2994
+ };
2995
+ const response = await attachmentManager.doUploadRequest(
2996
+ file,
2997
+ uploadRequestOptions
2998
+ );
2999
+ resolvePromise(response);
3000
+ } catch (error) {
3001
+ rejectPromise(error);
3002
+ } finally {
3003
+ this.inFlightUploads.delete(id);
3004
+ this.deleteUploadRecord(id);
3005
+ }
3006
+ })();
3007
+ return promise;
3008
+ };
3009
+ this.upsertUpload = (record) => {
3010
+ this.state.partialNext({
3011
+ uploads: upsertById(this.uploads, record)
3012
+ });
3013
+ };
3014
+ this.updateUpload = (record) => {
3015
+ this.state.next((current) => {
3016
+ const nextUploads = updateById(current.uploads, record);
3017
+ if (!nextUploads) return current;
3018
+ return { ...current, uploads: nextUploads };
3019
+ });
3020
+ };
3021
+ this.state = new StateStore(initState());
3022
+ }
3023
+ resolveAttachmentManager(channelCid) {
3024
+ const colon = channelCid.indexOf(":");
3025
+ if (colon <= 0 || colon === channelCid.length - 1) {
3026
+ throw new Error(`Invalid channelCid: ${channelCid}`);
3027
+ }
3028
+ const channelType = channelCid.slice(0, colon);
3029
+ const channelId = channelCid.slice(colon + 1);
3030
+ return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
3031
+ }
3032
+ get uploads() {
3033
+ return this.state.getLatestValue().uploads;
3034
+ }
3035
+ };
3036
+
2823
3037
  // src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts
2824
3038
  var createUploadErrorHandlerMiddleware = (composer) => ({
2825
3039
  id: "stream-io/attachment-manager-middleware/upload-error",
@@ -2832,6 +3046,7 @@ var createUploadErrorHandlerMiddleware = (composer) => ({
2832
3046
  const { attachment, error } = state;
2833
3047
  if (!error) return forward();
2834
3048
  if (!attachment) return discard();
3049
+ if (isUploadCancellation(error)) return forward();
2835
3050
  const reason = error instanceof Error ? error.message : "unknown error";
2836
3051
  composer.client.notifications.addError({
2837
3052
  message: "Error uploading attachment",
@@ -2856,7 +3071,7 @@ var AttachmentPostUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2856
3071
  super();
2857
3072
  this.use([
2858
3073
  createUploadErrorHandlerMiddleware(composer),
2859
- createPostUploadAttachmentEnrichmentMiddleware()
3074
+ createPostUploadAttachmentEnrichmentMiddleware(composer)
2860
3075
  ]);
2861
3076
  }
2862
3077
  };
@@ -3304,7 +3519,7 @@ function mergeWithDiff(target, source, customizer) {
3304
3519
  }
3305
3520
 
3306
3521
  // src/messageComposer/attachmentManager.ts
3307
- var initState = ({
3522
+ var initState2 = ({
3308
3523
  message
3309
3524
  }) => ({
3310
3525
  attachments: (message?.attachments ?? [])?.filter(({ og_scrape_url }) => !og_scrape_url).map((att) => {
@@ -3332,13 +3547,14 @@ var _AttachmentManager = class _AttachmentManager {
3332
3547
  ...attachment,
3333
3548
  localMetadata: {
3334
3549
  ...attachment.localMetadata,
3550
+ uploadConfirmationPending: false,
3335
3551
  uploadProgress: void 0,
3336
3552
  uploadState: "failed"
3337
3553
  }
3338
3554
  };
3339
3555
  };
3340
3556
  this.initState = ({ message } = {}) => {
3341
- this.state.next(initState({ message }));
3557
+ this.state.next(initState2({ message }));
3342
3558
  };
3343
3559
  this.getSnapshot = () => {
3344
3560
  const state = this.state.getLatestValue();
@@ -3415,8 +3631,22 @@ var _AttachmentManager = class _AttachmentManager {
3415
3631
  this.state.partialNext({ attachments });
3416
3632
  }
3417
3633
  };
3634
+ /**
3635
+ * Releases a local preview URL the SDK minted, if it minted one.
3636
+ *
3637
+ * Only `blob:` URLs are ours to revoke: `toLocalUploadAttachment` puts a
3638
+ * `URL.createObjectURL` result in `previewUri` for a `File`, but for a React Native
3639
+ * `FileReference` it stores the caller's own native file URI, which must be left alone.
3640
+ */
3641
+ this.revokePreviewUri = (attachment) => {
3642
+ const previewUri = attachment.localMetadata?.previewUri;
3643
+ if (previewUri?.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
3644
+ };
3418
3645
  this.removeAttachments = (localAttachmentIds) => {
3419
3646
  if (!localAttachmentIds.length) return;
3647
+ const removedAttachments = this.attachments.filter(
3648
+ (attachment) => localAttachmentIds.includes(attachment.localMetadata?.id)
3649
+ );
3420
3650
  this.state.partialNext({
3421
3651
  attachments: this.attachments.filter(
3422
3652
  (attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id)
@@ -3425,6 +3655,7 @@ var _AttachmentManager = class _AttachmentManager {
3425
3655
  for (const id of localAttachmentIds) {
3426
3656
  this.client.uploadManager.deleteUploadRecord(id);
3427
3657
  }
3658
+ removedAttachments.forEach(this.revokePreviewUri);
3428
3659
  };
3429
3660
  this.getUploadConfigCheck = async (fileLike) => {
3430
3661
  const client = this.channel.getClient();
@@ -3576,21 +3807,27 @@ var _AttachmentManager = class _AttachmentManager {
3576
3807
  localMetadata: {
3577
3808
  ...attachment.localMetadata,
3578
3809
  uploadState: "failed",
3579
- uploadProgress: void 0
3810
+ uploadProgress: void 0,
3811
+ // `false`, not `undefined`: these updates go through `updateAttachment`, which merges
3812
+ // via `mergeWith` — and that skips `undefined` source values, so `undefined` would
3813
+ // leave a stale `true` in place.
3814
+ uploadConfirmationPending: false
3580
3815
  }
3581
3816
  };
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
- });
3817
+ if (!isUploadCancellation(error)) {
3818
+ this.client.notifications.addError({
3819
+ message: "Error uploading attachment",
3820
+ origin: {
3821
+ emitter: "AttachmentManager",
3822
+ context: { attachment, failedAttachment }
3823
+ },
3824
+ options: {
3825
+ type: "api:attachment:upload:failed",
3826
+ metadata: { reason },
3827
+ originalError: error instanceof Error ? error : void 0
3828
+ }
3829
+ });
3830
+ }
3594
3831
  this.updateAttachment(failedAttachment);
3595
3832
  return failedAttachment;
3596
3833
  }
@@ -3603,7 +3840,9 @@ var _AttachmentManager = class _AttachmentManager {
3603
3840
  localMetadata: {
3604
3841
  ...attachment.localMetadata,
3605
3842
  uploadState: "finished",
3606
- uploadProgress: void 0
3843
+ uploadProgress: void 0,
3844
+ // See the note in the failure branch: `undefined` cannot clear through `mergeWith`.
3845
+ uploadConfirmationPending: false
3607
3846
  }
3608
3847
  };
3609
3848
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3652,7 +3891,9 @@ var _AttachmentManager = class _AttachmentManager {
3652
3891
  localMetadata: {
3653
3892
  ...attachment.localMetadata,
3654
3893
  uploadState: error ? "failed" : "finished",
3655
- uploadProgress: void 0
3894
+ uploadProgress: void 0,
3895
+ // See the note in `uploadAttachment`: `undefined` cannot clear through `mergeWith`.
3896
+ uploadConfirmationPending: false
3656
3897
  }
3657
3898
  },
3658
3899
  error,
@@ -3677,7 +3918,7 @@ var _AttachmentManager = class _AttachmentManager {
3677
3918
  );
3678
3919
  };
3679
3920
  this.composer = composer;
3680
- this.state = new StateStore(initState({ message }));
3921
+ this.state = new StateStore(initState2({ message }));
3681
3922
  this.attachmentsByIdGetterCache = { attachmentsById: {}, attachments: [] };
3682
3923
  this.preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor({
3683
3924
  composer
@@ -3782,7 +4023,8 @@ var _AttachmentManager = class _AttachmentManager {
3782
4023
  localMetadata: {
3783
4024
  ...attachment.localMetadata,
3784
4025
  uploadState: "uploading",
3785
- uploadProgress: this.config.trackUploadProgress ? 0 : void 0
4026
+ uploadProgress: this.config.trackUploadProgress ? 0 : void 0,
4027
+ uploadConfirmationPending: false
3786
4028
  }
3787
4029
  }
3788
4030
  ]);
@@ -3795,7 +4037,8 @@ var _AttachmentManager = class _AttachmentManager {
3795
4037
  localMetadata: {
3796
4038
  ...attachment.localMetadata,
3797
4039
  uploadState: "uploading",
3798
- uploadProgress: nextUpload.uploadProgress
4040
+ uploadProgress: nextUpload.uploadProgress,
4041
+ uploadConfirmationPending: nextUpload.uploadConfirmationPending
3799
4042
  }
3800
4043
  });
3801
4044
  }
@@ -3849,6 +4092,50 @@ var localAttachmentToAttachment = (localAttachment) => {
3849
4092
  const { localMetadata, ...attachment } = localAttachment;
3850
4093
  return attachment;
3851
4094
  };
4095
+ var composeWithPendingUploads = ({
4096
+ composer,
4097
+ state
4098
+ }) => {
4099
+ const composerIsKeptAsDraft = !!composer.pollId;
4100
+ const relevantAttachments = composer.attachmentManager.attachments.filter(
4101
+ (attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && isPendingUpload(attachment)
4102
+ );
4103
+ const localAttachments = (state.localMessage.attachments ?? []).concat(
4104
+ relevantAttachments.map(
4105
+ (attachment) => isPendingUpload(attachment) ? attachment : localAttachmentToAttachment(attachment)
4106
+ )
4107
+ );
4108
+ const messageAttachments = (state.message.attachments ?? []).concat(
4109
+ relevantAttachments.filter(isFinishedUpload).map(localAttachmentToAttachment)
4110
+ );
4111
+ if (!localAttachments.length && !messageAttachments.length) return state;
4112
+ return {
4113
+ ...state,
4114
+ localMessage: localAttachments.length ? {
4115
+ ...state.localMessage,
4116
+ attachments: localAttachments
4117
+ } : state.localMessage,
4118
+ message: messageAttachments.length ? {
4119
+ ...state.message,
4120
+ attachments: messageAttachments
4121
+ } : state.message
4122
+ };
4123
+ };
4124
+ var createSendWithPendingUploadsAttachmentsMiddleware = (composer) => ({
4125
+ allowsPendingUploads: true,
4126
+ id: "stream-io/message-composer-middleware/attachments",
4127
+ handlers: {
4128
+ compose: ({
4129
+ state,
4130
+ next,
4131
+ forward
4132
+ }) => {
4133
+ const { attachmentManager } = composer;
4134
+ if (!attachmentManager) return forward();
4135
+ return next(composeWithPendingUploads({ composer, state }));
4136
+ }
4137
+ }
4138
+ });
3852
4139
  var createAttachmentsCompositionMiddleware = (composer) => ({
3853
4140
  id: "stream-io/message-composer-middleware/attachments",
3854
4141
  handlers: {
@@ -4065,7 +4352,7 @@ var getInitialMentions = (message) => {
4065
4352
  };
4066
4353
  var isSameMentionEntity = (first, second) => first.id === second.id && first.mentionType === second.mentionType;
4067
4354
  var getMentionsFromState = (state) => state.mentions ?? state.mentionedUsers.map(userResponseToMentionEntity);
4068
- var initState2 = ({
4355
+ var initState3 = ({
4069
4356
  composer,
4070
4357
  message
4071
4358
  }) => {
@@ -4094,7 +4381,7 @@ var initState2 = ({
4094
4381
  var TextComposer = class {
4095
4382
  constructor({ composer, message }) {
4096
4383
  this.initState = ({ message } = {}) => {
4097
- this.state.next(initState2({ composer: this.composer, message }));
4384
+ this.state.next(initState3({ composer: this.composer, message }));
4098
4385
  };
4099
4386
  this.getSnapshot = (state = this.state.getLatestValue()) => state;
4100
4387
  this.restoreSnapshot = (snapshot) => {
@@ -4287,7 +4574,7 @@ var TextComposer = class {
4287
4574
  this.commitState(output.state);
4288
4575
  };
4289
4576
  this.composer = composer;
4290
- this.state = new StateStore(initState2({ composer, message }));
4577
+ this.state = new StateStore(initState3({ composer, message }));
4291
4578
  this.middlewareExecutor = new TextComposerMiddlewareExecutor({ composer });
4292
4579
  }
4293
4580
  get channel() {
@@ -5989,6 +6276,13 @@ var MessageComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5989
6276
  createCompositionDataCleanupMiddleware(composer)
5990
6277
  ]);
5991
6278
  }
6279
+ /**
6280
+ * Narrows the base accessor to the composition middleware type, so callers can read the
6281
+ * declarations it carries (`allowsPendingUploads`) without casting.
6282
+ */
6283
+ get installedMiddleware() {
6284
+ return super.installedMiddleware;
6285
+ }
5992
6286
  };
5993
6287
  var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5994
6288
  constructor({ composer }) {
@@ -7103,7 +7397,7 @@ var DEFAULT_COMPOSER_CONFIG = {
7103
7397
  };
7104
7398
 
7105
7399
  // src/messageComposer/CustomDataManager.ts
7106
- var initState3 = (options) => {
7400
+ var initState4 = (options) => {
7107
7401
  if (!options)
7108
7402
  return { message: {}, custom: {} };
7109
7403
  return { message: {}, custom: {} };
@@ -7112,14 +7406,14 @@ var CustomDataManager = class {
7112
7406
  constructor({ composer, message }) {
7113
7407
  this.isMessageDataEqual = (nextState, previousState) => JSON.stringify(nextState.message) === JSON.stringify(previousState?.message);
7114
7408
  this.initState = ({ message } = {}) => {
7115
- this.state.next(initState3({ composer: this.composer, message }));
7409
+ this.state.next(initState4({ composer: this.composer, message }));
7116
7410
  };
7117
7411
  this.getSnapshot = () => this.state.getLatestValue();
7118
7412
  this.restoreSnapshot = (snapshot) => {
7119
7413
  this.state.next(snapshot);
7120
7414
  };
7121
7415
  this.composer = composer;
7122
- this.state = new StateStore(initState3({ composer, message }));
7416
+ this.state = new StateStore(initState4({ composer, message }));
7123
7417
  }
7124
7418
  get customMessageData() {
7125
7419
  return this.state.getLatestValue().message;
@@ -7155,7 +7449,7 @@ var LinkPreviewStatus = /* @__PURE__ */ ((LinkPreviewStatus2) => {
7155
7449
  return LinkPreviewStatus2;
7156
7450
  })(LinkPreviewStatus || {});
7157
7451
  var linkPreviewArrayToMap = (linkPreviews) => new Map(linkPreviews.map((linkPreview) => [linkPreview.og_scrape_url, linkPreview]));
7158
- var initState4 = ({
7452
+ var initState5 = ({
7159
7453
  message
7160
7454
  }) => message ? {
7161
7455
  previews: message.attachments?.reduce((acc, attachment) => {
@@ -7174,7 +7468,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7174
7468
  this.shouldDiscardEnrichQueries = false;
7175
7469
  this.initState = ({ message } = {}) => {
7176
7470
  this.cancelURLEnrichment();
7177
- this.state.next(initState4({ message: this.enabled ? message : void 0 }));
7471
+ this.state.next(initState5({ message: this.enabled ? message : void 0 }));
7178
7472
  };
7179
7473
  this.getSnapshot = () => this.state.getLatestValue();
7180
7474
  this.restoreSnapshot = (snapshot) => {
@@ -7277,7 +7571,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7277
7571
  };
7278
7572
  this.composer = composer;
7279
7573
  this.state = new StateStore(
7280
- initState4({ message: this.enabled ? message : void 0 })
7574
+ initState5({ message: this.enabled ? message : void 0 })
7281
7575
  );
7282
7576
  this.findAndEnrichUrls = debounce(
7283
7577
  this._findAndEnrichUrls.bind(this),
@@ -7365,7 +7659,7 @@ var LinkPreviewsManager = _LinkPreviewsManager;
7365
7659
 
7366
7660
  // src/messageComposer/LocationComposer.ts
7367
7661
  var MIN_LIVE_LOCATION_SHARE_DURATION = 60 * 1e3;
7368
- var initState5 = ({
7662
+ var initState6 = ({
7369
7663
  message
7370
7664
  }) => ({
7371
7665
  location: message?.shared_location ?? null
@@ -7373,7 +7667,7 @@ var initState5 = ({
7373
7667
  var LocationComposer = class {
7374
7668
  constructor({ composer, message }) {
7375
7669
  this.initState = ({ message } = {}) => {
7376
- this.state.next(initState5({ message }));
7670
+ this.state.next(initState6({ message }));
7377
7671
  };
7378
7672
  this.getSnapshot = () => this.state.getLatestValue();
7379
7673
  this.restoreSnapshot = (snapshot) => {
@@ -7391,7 +7685,7 @@ var LocationComposer = class {
7391
7685
  });
7392
7686
  };
7393
7687
  this.composer = composer;
7394
- this.state = new StateStore(initState5({ message }));
7688
+ this.state = new StateStore(initState6({ message }));
7395
7689
  this._deviceId = this.config.getDeviceId();
7396
7690
  }
7397
7691
  get config() {
@@ -8112,7 +8406,7 @@ var initEditingAuditState = (composition) => {
8112
8406
  }
8113
8407
  };
8114
8408
  };
8115
- var initState6 = (composition) => {
8409
+ var initState7 = (composition) => {
8116
8410
  if (!composition) {
8117
8411
  return {
8118
8412
  draftId: null,
@@ -8199,7 +8493,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8199
8493
  this.textComposer.initState({ message });
8200
8494
  this.pollComposer.initState();
8201
8495
  this.customDataManager.initState({ message });
8202
- this.state.next(initState6(composition));
8496
+ this.state.next(initState7(composition));
8203
8497
  };
8204
8498
  this.initStateFromChannelResponse = (channelApiResponse) => {
8205
8499
  if (this.channel.cid !== channelApiResponse.channel.cid) {
@@ -8665,7 +8959,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8665
8959
  this.editingAuditState = new StateStore(
8666
8960
  this.initEditingAuditState(composition)
8667
8961
  );
8668
- this.state = new StateStore(initState6(composition));
8962
+ this.state = new StateStore(initState7(composition));
8669
8963
  this.compositionMiddlewareExecutor = new MessageComposerMiddlewareExecutor({
8670
8964
  composer: this
8671
8965
  });
@@ -8744,8 +9038,32 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8744
9038
  const currentCommand = this.textComposer.command;
8745
9039
  return !currentCommand || this.validateCommandSendability(currentCommand).ready;
8746
9040
  }
9041
+ /**
9042
+ * Whether the installed composition middleware composes messages whose uploads are still in
9043
+ * flight — see `MessageCompositionMiddleware.allowsPendingUploads`.
9044
+ *
9045
+ * Derived from the chain rather than from config, so there is exactly one switch: the UI SDK
9046
+ * that implements the rest of the flow installs the middleware, and everything that has to
9047
+ * follow from that (starting with {@link hasSendableData}) follows on its own.
9048
+ *
9049
+ * **Temporary API.** In v10 sending with pending uploads becomes part of the composer
9050
+ * configuration (`MessageComposerConfig`, set through `updateConfig`), and this getter,
9051
+ * together with the middleware declaration it reads, is replaced by that config field.
9052
+ */
9053
+ get allowsPendingUploads() {
9054
+ return this.compositionMiddlewareExecutor.installedMiddleware.some(
9055
+ (middleware) => middleware.allowsPendingUploads
9056
+ );
9057
+ }
8747
9058
  get hasSendableData() {
8748
- return this.isCommandSendable && !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
9059
+ if (!this.isCommandSendable) return false;
9060
+ if (this.allowsPendingUploads) {
9061
+ const hasSendableAttachment = this.attachmentManager.attachments.some(
9062
+ (attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment)
9063
+ );
9064
+ return !!(!this.textComposer.textIsEmpty || hasSendableAttachment || this.pollId || this.locationComposer.validLocation);
9065
+ }
9066
+ return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
8749
9067
  }
8750
9068
  get compositionIsEmpty() {
8751
9069
  return !this.quotedMessage && this.contentIsEmpty;
@@ -8884,7 +9202,6 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
8884
9202
  * @param options
8885
9203
  */
8886
9204
  this.markRead = async (collection, options) => {
8887
- if (!userHasReadReceipts(this.client)) return null;
8888
9205
  let result = null;
8889
9206
  if (isChannel(collection)) {
8890
9207
  result = await collection.markAsReadRequest(options);
@@ -8894,7 +9211,7 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
8894
9211
  thread_id: collection.id
8895
9212
  });
8896
9213
  }
8897
- this.removeCandidateFor(collection);
9214
+ if (result) this.removeCandidateFor(collection);
8898
9215
  return result;
8899
9216
  };
8900
9217
  /**
@@ -9279,18 +9596,14 @@ var Channel = class {
9279
9596
  return await this.query(defaultOptions, "latest");
9280
9597
  };
9281
9598
  this._callChannelListeners = (event) => {
9282
- const channel = this;
9283
- const listeners = [];
9284
- if (channel.listeners.all) {
9285
- listeners.push(...channel.listeners.all);
9286
- }
9287
- if (channel.listeners[event.type]) {
9288
- listeners.push(...channel.listeners[event.type]);
9289
- }
9599
+ const listeners = [
9600
+ ...this.listeners.all ?? [],
9601
+ ...this.listeners[event.type] ?? []
9602
+ ];
9603
+ const { logger } = this._client;
9290
9604
  for (const listener of listeners) {
9291
- if (typeof listener !== "string") {
9292
- listener(event);
9293
- }
9605
+ if (typeof listener === "string") continue;
9606
+ invokeEventListener(listener, event, logger);
9294
9607
  }
9295
9608
  };
9296
9609
  /**
@@ -9372,14 +9685,16 @@ var Channel = class {
9372
9685
  *
9373
9686
  * @return {Promise<SendMessageAPIResponse>} The Server Response
9374
9687
  */
9375
- async _sendMessage(message, options) {
9376
- return await this.getClient().post(
9377
- this._channelURL() + "/message",
9378
- {
9379
- message,
9380
- ...options
9381
- }
9382
- );
9688
+ async _sendMessage(rawMessage, options) {
9689
+ const client = this.getClient();
9690
+ const message = sanitizeOutgoingAttachments({
9691
+ client,
9692
+ message: rawMessage
9693
+ });
9694
+ return await client.post(this._channelURL() + "/message", {
9695
+ message,
9696
+ ...options
9697
+ });
9383
9698
  }
9384
9699
  async sendMessage(message, options) {
9385
9700
  try {
@@ -10202,14 +10517,15 @@ var Channel = class {
10202
10517
  return await this.getClient().messageDeliveryReporter.markRead(this, data);
10203
10518
  }
10204
10519
  /**
10205
- * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
10520
+ * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
10521
+ * enabled and the user holds the `read-events` capability on this channel
10206
10522
  *
10207
10523
  * @param {MarkReadOptions} data
10208
10524
  * @return {Promise<EventAPIResponse | null>} Description
10209
10525
  */
10210
10526
  async markAsReadRequest(data = {}) {
10211
10527
  this._checkInitialized();
10212
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10528
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10213
10529
  return null;
10214
10530
  }
10215
10531
  return await this.getClient().post(this._channelURL() + "/read", {
@@ -10217,14 +10533,15 @@ var Channel = class {
10217
10533
  });
10218
10534
  }
10219
10535
  /**
10220
- * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is enabled
10536
+ * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
10537
+ * enabled and the user holds the `read-events` capability on this channel
10221
10538
  *
10222
10539
  * @param {MarkUnreadOptions} data
10223
10540
  * @return {APIResponse} An API response
10224
10541
  */
10225
10542
  async markUnread(data) {
10226
10543
  this._checkInitialized();
10227
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10544
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10228
10545
  return Promise.resolve(null);
10229
10546
  }
10230
10547
  return await this.getClient().post(this._channelURL() + "/unread", {
@@ -10830,6 +11147,7 @@ var Channel = class {
10830
11147
  // delivery-report network sync below is skipped for it.
10831
11148
  case "message.read_locally":
10832
11149
  case "message.read":
11150
+ if (event.thread) break;
10833
11151
  if (event.user?.id && event.created_at) {
10834
11152
  const previousReadState = channelState.read[event.user.id];
10835
11153
  channelState.read[event.user.id] = {
@@ -11279,7 +11597,7 @@ var ClientState = class {
11279
11597
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
11280
11598
 
11281
11599
  // src/insights.ts
11282
- var import_axios = __toESM(require("axios"));
11600
+ var import_axios2 = __toESM(require("axios"));
11283
11601
  var InsightMetrics = class {
11284
11602
  constructor() {
11285
11603
  this.connectionStartTimestamp = null;
@@ -11292,7 +11610,7 @@ var postInsights = async (insightType, insights) => {
11292
11610
  const maxAttempts = 3;
11293
11611
  for (let i = 0; i < maxAttempts; i++) {
11294
11612
  try {
11295
- await import_axios.default.post(
11613
+ await import_axios2.default.post(
11296
11614
  `https://chat-insights.getstream.io/insights/${insightType}`,
11297
11615
  insights
11298
11616
  );
@@ -11800,131 +12118,6 @@ var StableWSConnection = class {
11800
12118
  }
11801
12119
  };
11802
12120
 
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
12121
  // src/signing.ts
11929
12122
  var import_jsonwebtoken = __toESM(require_jsonwebtoken());
11930
12123
  var import_crypto = __toESM(require_crypto());
@@ -12183,7 +12376,7 @@ var TokenManager = class {
12183
12376
  };
12184
12377
 
12185
12378
  // src/connection_fallback.ts
12186
- var import_axios2 = __toESM(require("axios"));
12379
+ var import_axios3 = __toESM(require("axios"));
12187
12380
  var WSConnectionFallback = class {
12188
12381
  constructor({ client }) {
12189
12382
  /** @private */
@@ -12202,7 +12395,7 @@ var WSConnectionFallback = class {
12202
12395
  /** @private */
12203
12396
  this._req = async (params, config, retry) => {
12204
12397
  if (!this.cancelToken && !params.close) {
12205
- this.cancelToken = import_axios2.default.CancelToken.source();
12398
+ this.cancelToken = import_axios3.default.CancelToken.source();
12206
12399
  }
12207
12400
  try {
12208
12401
  const res = await this.client.doAxiosRequest(
@@ -12238,7 +12431,7 @@ var WSConnectionFallback = class {
12238
12431
  }
12239
12432
  }
12240
12433
  } catch (error) {
12241
- if (import_axios2.default.isCancel(error)) {
12434
+ if (import_axios3.default.isCancel(error)) {
12242
12435
  this._log(`_poll() - axios canceled request`);
12243
12436
  return;
12244
12437
  }
@@ -14661,7 +14854,7 @@ var StreamChat = class _StreamChat {
14661
14854
  return this.handleResponse(response);
14662
14855
  } catch (e) {
14663
14856
  e.client_request_id = requestConfig.headers?.["x-client-request-id"];
14664
- if (import_axios3.default.isCancel(e)) throw e;
14857
+ if (import_axios4.default.isCancel(e)) throw e;
14665
14858
  this._logApiError(type, url, e);
14666
14859
  this.consecutiveFailures += 1;
14667
14860
  if (e.response) {
@@ -14810,16 +15003,12 @@ var StreamChat = class _StreamChat {
14810
15003
  }
14811
15004
  };
14812
15005
  this._callClientListeners = (event) => {
14813
- const client = this;
14814
- const listeners = [];
14815
- if (client.listeners.all) {
14816
- listeners.push(...client.listeners.all);
14817
- }
14818
- if (client.listeners[event.type]) {
14819
- listeners.push(...client.listeners[event.type]);
14820
- }
15006
+ const listeners = [
15007
+ ...this.listeners.all ?? [],
15008
+ ...this.listeners[event.type] ?? []
15009
+ ];
14821
15010
  for (const listener of listeners) {
14822
- listener(event);
15011
+ invokeEventListener(listener, event, this.logger);
14823
15012
  }
14824
15013
  };
14825
15014
  this.recoverState = async () => {
@@ -14973,7 +15162,7 @@ var StreamChat = class _StreamChat {
14973
15162
  return this.userAgent;
14974
15163
  }
14975
15164
  if (!this.cachedUserAgent) {
14976
- const version = "9.51.0";
15165
+ const version = "9.52.1";
14977
15166
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14978
15167
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14979
15168
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -15041,7 +15230,7 @@ var StreamChat = class _StreamChat {
15041
15230
  keepAliveMsecs: 3e3
15042
15231
  });
15043
15232
  }
15044
- this.axiosInstance = import_axios3.default.create(this.options);
15233
+ this.axiosInstance = import_axios4.default.create(this.options);
15045
15234
  this.setBaseURL(this.options.baseURL || "https://chat.stream-io-api.com");
15046
15235
  if (typeof process !== "undefined" && "env" in process && process.env.STREAM_LOCAL_TEST_RUN) {
15047
15236
  this.setBaseURL("http://localhost:3030");
@@ -15062,7 +15251,7 @@ var StreamChat = class _StreamChat {
15062
15251
  this.defaultWSTimeoutWithFallback = 6 * 1e3;
15063
15252
  this.defaultWSTimeout = 15 * 1e3;
15064
15253
  this.axiosInstance.defaults.paramsSerializer = axiosParamsSerializer;
15065
- this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : () => null;
15254
+ this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : noopLogger;
15066
15255
  this.recoverStateOnReconnect = this.options.recoverStateOnReconnect;
15067
15256
  this.threads = new ThreadManager({ client: this });
15068
15257
  this.polls = new PollManager({ client: this });
@@ -16393,6 +16582,11 @@ var StreamChat = class _StreamChat {
16393
16582
  }
16394
16583
  /**
16395
16584
  * unflagMessage - unflag a message
16585
+ *
16586
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16587
+ * the backend - calling this resolves successfully but does not remove the flag.
16588
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16589
+ *
16396
16590
  * @param {string} targetMessageID
16397
16591
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16398
16592
  * @returns {Promise<APIResponse>}
@@ -16405,6 +16599,11 @@ var StreamChat = class _StreamChat {
16405
16599
  }
16406
16600
  /**
16407
16601
  * unflagUser - unflag a user
16602
+ *
16603
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16604
+ * the backend - calling this resolves successfully but does not remove the flag.
16605
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16606
+ *
16408
16607
  * @param {string} targetID
16409
16608
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16410
16609
  * @returns {Promise<APIResponse>}
@@ -16689,7 +16888,10 @@ var StreamChat = class _StreamChat {
16689
16888
  if (!message.id) {
16690
16889
  throw Error("Please specify the message.id when calling updateMessage");
16691
16890
  }
16692
- const payload = toUpdatedMessagePayload(message);
16891
+ const payload = sanitizeOutgoingAttachments({
16892
+ client: this,
16893
+ message: toUpdatedMessagePayload(message)
16894
+ });
16693
16895
  if (typeof partialUserOrUserId === "string") {
16694
16896
  payload.user_id = partialUserOrUserId;
16695
16897
  } else if (typeof partialUserOrUserId?.id === "string") {
@@ -18505,7 +18707,7 @@ var OfflineError = class extends Error {
18505
18707
  };
18506
18708
 
18507
18709
  // src/offline-support/offline_sync_manager.ts
18508
- var import_axios4 = require("axios");
18710
+ var import_axios5 = require("axios");
18509
18711
  var OfflineDBSyncManager = class {
18510
18712
  constructor({
18511
18713
  client,
@@ -18653,7 +18855,7 @@ var OfflineDBSyncManager = class {
18653
18855
  });
18654
18856
  } catch (e) {
18655
18857
  console.log("An error has occurred while syncing the DB.", e);
18656
- if ((0, import_axios4.isAxiosError)(e) && e.code === "ECONNABORTED") {
18858
+ if ((0, import_axios5.isAxiosError)(e) && e.code === "ECONNABORTED") {
18657
18859
  return;
18658
18860
  }
18659
18861
  const error = e;
@@ -19162,6 +19364,7 @@ var AbstractOfflineDB = class {
19162
19364
  return this.handleMessageUpdatedEvent({ event, execute });
19163
19365
  }
19164
19366
  if (type === "message.read" || type === "notification.mark_read") {
19367
+ if (event.thread) return [];
19165
19368
  return this.handleRead({ event, unreadMessages: 0, execute });
19166
19369
  }
19167
19370
  if (type === "message.read_locally") {