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
@@ -158,6 +158,7 @@ __export(index_exports, {
158
158
  createMessageComposerStateCompositionMiddleware: () => createMessageComposerStateCompositionMiddleware,
159
159
  createPollComposerStateMiddleware: () => createPollComposerStateMiddleware,
160
160
  createPostUploadAttachmentEnrichmentMiddleware: () => createPostUploadAttachmentEnrichmentMiddleware,
161
+ createSendWithPendingUploadsAttachmentsMiddleware: () => createSendWithPendingUploadsAttachmentsMiddleware,
161
162
  createSharedLocationCompositionMiddleware: () => createSharedLocationCompositionMiddleware,
162
163
  createTextComposerCompositionMiddleware: () => createTextComposerCompositionMiddleware,
163
164
  createTextComposerPreValidationMiddleware: () => createTextComposerPreValidationMiddleware,
@@ -186,6 +187,7 @@ __export(index_exports, {
186
187
  getTokenizedSuggestionDisplayName: () => getTokenizedSuggestionDisplayName,
187
188
  getTriggerCharWithToken: () => getTriggerCharWithToken,
188
189
  gunzipPayload: () => gunzipPayload,
190
+ hasPending: () => hasPending,
189
191
  insertItemWithTrigger: () => insertItemWithTrigger,
190
192
  isAudioAttachment: () => isAudioAttachment,
191
193
  isBlobButNotFile: () => isBlobButNotFile,
@@ -193,6 +195,7 @@ __export(index_exports, {
193
195
  isFileAttachment: () => isFileAttachment,
194
196
  isFileList: () => isFileList,
195
197
  isFileReference: () => isFileReference,
198
+ isFinishedUpload: () => isFinishedUpload,
196
199
  isGiphyAttachment: () => isGiphyAttachment,
197
200
  isImageAttachment: () => isImageAttachment,
198
201
  isImageFile: () => isImageFile,
@@ -205,9 +208,11 @@ __export(index_exports, {
205
208
  isLocalVoiceRecordingAttachment: () => isLocalVoiceRecordingAttachment,
206
209
  isOwnUser: () => isOwnUser,
207
210
  isPatch: () => isPatch,
211
+ isPendingUpload: () => isPendingUpload,
208
212
  isScrapedContent: () => isScrapedContent,
209
213
  isSharedLocationResponse: () => isSharedLocationResponse,
210
214
  isTargetedOptionTextUpdate: () => isTargetedOptionTextUpdate,
215
+ isUploadCancellation: () => isUploadCancellation,
211
216
  isUploadedAttachment: () => isUploadedAttachment,
212
217
  isVideoAttachment: () => isVideoAttachment,
213
218
  isVoiceRecordingAttachment: () => isVoiceRecordingAttachment,
@@ -227,12 +232,15 @@ __export(index_exports, {
227
232
  readFileAsArrayBuffer: () => readFileAsArrayBuffer,
228
233
  removeDiacritics: () => removeDiacritics,
229
234
  replaceWordWithEntity: () => replaceWordWithEntity,
235
+ settled: () => settled,
230
236
  stripCommandFromText: () => stripCommandFromText,
231
237
  stripMentionTokens: () => stripMentionTokens,
232
238
  textIsEmpty: () => textIsEmpty,
233
239
  timeLeftMs: () => timeLeftMs,
234
240
  verifyAndParseWebhook: () => verifyAndParseWebhook,
235
- verifySignature: () => verifySignature
241
+ verifySignature: () => verifySignature,
242
+ withCancellation: () => withCancellation,
243
+ withoutConcurrency: () => withoutConcurrency
236
244
  });
237
245
  module.exports = __toCommonJS(index_exports);
238
246
 
@@ -499,7 +507,7 @@ var ChannelBatchUpdater = class {
499
507
  };
500
508
 
501
509
  // src/client.ts
502
- var import_axios3 = __toESM(require("axios"));
510
+ var import_axios4 = __toESM(require("axios"));
503
511
  var import_https = __toESM(require("https"));
504
512
 
505
513
  // src/utils.ts
@@ -548,6 +556,22 @@ function logChatPromiseExecution(promise, name) {
548
556
  console.warn(`failed to do ${name}, ran into error: `, error);
549
557
  });
550
558
  }
559
+ var noopLogger = () => null;
560
+ var logError = (logger, error, event) => {
561
+ const message = "Unhandled error in event listener";
562
+ logger("error", message, { error, event });
563
+ if (logger === noopLogger) console.error(message, error, event);
564
+ };
565
+ var invokeEventListener = (listener, event, logger) => {
566
+ try {
567
+ const result = listener(event);
568
+ if (result && typeof result.then === "function") {
569
+ Promise.resolve(result).catch((error) => logError(logger, error, event));
570
+ }
571
+ } catch (error) {
572
+ logError(logger, error, event);
573
+ }
574
+ };
551
575
  var sleep = (m) => new Promise((r) => setTimeout(r, m));
552
576
  function isFunction(value) {
553
577
  return typeof value === "function" || value instanceof Function || Object.prototype.toString.call(value) === "[object Function]";
@@ -595,7 +619,6 @@ var channelHasReadEvents = (channel) => {
595
619
  return !(Array.isArray(ownCapabilities) && !ownCapabilities.includes("read-events"));
596
620
  };
597
621
  var channelTracksReadLocally = (channel) => !channelHasReadEvents(channel) && !!channel?.getClient().options.isLocalUnreadCountEnabled;
598
- var userHasReadReceipts = (client) => client.user?.privacy_settings?.read_receipts?.enabled ?? true;
599
622
  function addFileToFormData(uri, name, contentType) {
600
623
  const data = new import_form_data.default();
601
624
  if (isReadableStream(uri) || isBuffer(uri) || isFileWebAPI(uri) || isBlobWebAPI(uri)) {
@@ -759,6 +782,38 @@ function unformatMessage(message) {
759
782
  quoted_message: toMessageResponseBase(message.quoted_message)
760
783
  };
761
784
  }
785
+ var isRemoteUrl = (url) => !!url && /^https?:\/\//i.test(url.trim());
786
+ var sanitizeOutgoingAttachments = ({
787
+ client,
788
+ message
789
+ }) => {
790
+ const attachments = message.attachments;
791
+ if (!attachments?.length) return message;
792
+ const unresolved = [];
793
+ let changed = false;
794
+ const sanitized = attachments.reduce((acc, attachment) => {
795
+ const { localMetadata, ...rest } = attachment;
796
+ if (!localMetadata) {
797
+ acc.push(attachment);
798
+ return acc;
799
+ }
800
+ changed = true;
801
+ const hasRemoteSource = isRemoteUrl(rest.asset_url) || isRemoteUrl(rest.image_url) || isRemoteUrl(rest.og_scrape_url) || isRemoteUrl(rest.title_link);
802
+ if (hasRemoteSource) {
803
+ acc.push(rest);
804
+ } else {
805
+ unresolved.push(rest);
806
+ }
807
+ return acc;
808
+ }, []);
809
+ if (unresolved.length) {
810
+ const text = `Dropped ${unresolved.length} attachment(s) whose upload never completed. Composing with pending uploads requires awaiting them before sending.`;
811
+ const extraData = { attachments: unresolved, message, tags: ["attachments"] };
812
+ client.logger("warn", text, extraData);
813
+ console.warn(text, extraData);
814
+ }
815
+ return changed ? { ...message, attachments: sanitized } : message;
816
+ };
762
817
  var localMessageToNewMessagePayload = (localMessage) => {
763
818
  const {
764
819
  // Remove all timestamp fields and client-specific fields.
@@ -2531,6 +2586,12 @@ var CooldownTimer = class extends WithSubscriptions {
2531
2586
  var isScrapedContent = (attachment) => !!attachment?.og_scrape_url || !!attachment?.title_link;
2532
2587
  var isLocalAttachment = (attachment) => !!attachment?.localMetadata?.id;
2533
2588
  var isLocalUploadAttachment = (attachment) => !!attachment?.localMetadata?.uploadState;
2589
+ var PENDING_UPLOAD_STATES = [
2590
+ "pending",
2591
+ "uploading"
2592
+ ];
2593
+ var isPendingUpload = (attachment) => isLocalUploadAttachment(attachment) && PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
2594
+ var isFinishedUpload = (attachment) => isLocalUploadAttachment(attachment) && attachment.localMetadata.uploadState === "finished";
2534
2595
  var isFileAttachment = (attachment, supportedVideoFormat = []) => attachment.type === "file" || !!(attachment.mime_type && supportedVideoFormat.indexOf(attachment.mime_type) === -1 && attachment.type !== "video");
2535
2596
  var isLocalFileAttachment = (attachment) => isFileAttachment(attachment) && isLocalAttachment(attachment);
2536
2597
  var isImageAttachment = (attachment) => attachment.type === "image" && !isScrapedContent(attachment);
@@ -2606,7 +2667,7 @@ var ensureIsLocalAttachment = (attachment) => {
2606
2667
  };
2607
2668
 
2608
2669
  // src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts
2609
- var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2670
+ var createPostUploadAttachmentEnrichmentMiddleware = (composer) => ({
2610
2671
  id: "stream-io/attachment-manager-middleware/post-upload-enrichment",
2611
2672
  handlers: {
2612
2673
  postProcess: ({
@@ -2619,9 +2680,10 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2619
2680
  if (error) return forward();
2620
2681
  if (!attachment || !response) return discard();
2621
2682
  const enrichedAttachment = { ...attachment };
2622
- const previewUri = attachment.localMetadata.previewUri;
2623
- if (previewUri) {
2624
- if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
2683
+ const { id, previewUri } = attachment.localMetadata;
2684
+ const attachmentIsStillInComposer = !composer || !!composer.attachmentManager.attachmentsById[id];
2685
+ if (previewUri && attachmentIsStillInComposer) {
2686
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
2625
2687
  delete enrichedAttachment.localMetadata.previewUri;
2626
2688
  }
2627
2689
  if (isLocalImageAttachment(attachment)) {
@@ -2644,6 +2706,12 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2644
2706
  var withoutConcurrency = createRunner(wrapWithContinuationTracking);
2645
2707
  var withCancellation = createRunner(wrapWithCancellation);
2646
2708
  var pendingPromises = /* @__PURE__ */ new Map();
2709
+ function hasPending(tag) {
2710
+ return pendingPromises.has(tag);
2711
+ }
2712
+ async function settled(tag) {
2713
+ await pendingPromises.get(tag)?.promise;
2714
+ }
2647
2715
  function createRunner(wrapper) {
2648
2716
  return function run(tag, cb) {
2649
2717
  const { cb: wrapped, onContinued } = wrapper(tag, cb);
@@ -2686,6 +2754,16 @@ var MiddlewareExecutor = class {
2686
2754
  this.middleware = [];
2687
2755
  this.id = generateUUIDv4();
2688
2756
  }
2757
+ /**
2758
+ * The middleware currently installed, in execution order.
2759
+ *
2760
+ * Read-only: use `use` / `insert` / `replace` / `setOrder` / `remove` to change the chain. It
2761
+ * exists so components can ask what the chain is capable of - a middleware may declare
2762
+ * behaviour its host has to account for, and the host has no other way to find out.
2763
+ */
2764
+ get installedMiddleware() {
2765
+ return this.middleware;
2766
+ }
2689
2767
  use(middleware) {
2690
2768
  this.middleware = this.middleware.concat(middleware);
2691
2769
  return this;
@@ -2789,6 +2867,142 @@ var MiddlewareExecutor = class {
2789
2867
  }
2790
2868
  };
2791
2869
 
2870
+ // src/uploadManager.ts
2871
+ var import_axios = __toESM(require("axios"));
2872
+ var isUploadCancellation = (error) => import_axios.default.isCancel(error) || error instanceof Error && error.name === "AbortError";
2873
+ var initState = () => ({ uploads: {} });
2874
+ var upsertById = (uploads, record) => ({
2875
+ ...uploads,
2876
+ [record.id]: { ...uploads[record.id], ...record }
2877
+ });
2878
+ var updateById = (uploads, record) => {
2879
+ if (!(record.id in uploads)) return null;
2880
+ const current = uploads[record.id];
2881
+ return { ...uploads, [record.id]: { ...current, ...record } };
2882
+ };
2883
+ var UploadManager = class {
2884
+ constructor(client) {
2885
+ this.client = client;
2886
+ this.inFlightUploads = /* @__PURE__ */ new Map();
2887
+ this.getUpload = (id) => this.uploads[id];
2888
+ /**
2889
+ * Clears all upload records.
2890
+ * Invoked when the user disconnects so a later session does not inherit stale upload state.
2891
+ * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
2892
+ */
2893
+ this.reset = () => {
2894
+ for (const { abortController } of this.inFlightUploads.values()) {
2895
+ abortController.abort();
2896
+ }
2897
+ this.inFlightUploads.clear();
2898
+ this.state.next(initState());
2899
+ };
2900
+ /**
2901
+ * Removes the upload record for `id` if present.
2902
+ * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
2903
+ */
2904
+ this.deleteUploadRecord = (id) => {
2905
+ const flight = this.inFlightUploads.get(id);
2906
+ if (flight) {
2907
+ this.inFlightUploads.delete(id);
2908
+ flight.abortController.abort();
2909
+ }
2910
+ this.state.next((current) => {
2911
+ if (!(id in current.uploads)) return current;
2912
+ const uploads = { ...current.uploads };
2913
+ delete uploads[id];
2914
+ return { ...current, uploads };
2915
+ });
2916
+ };
2917
+ /**
2918
+ * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
2919
+ * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
2920
+ * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
2921
+ */
2922
+ this.upload = ({
2923
+ id,
2924
+ channelCid,
2925
+ file
2926
+ }) => {
2927
+ const existing = this.inFlightUploads.get(id);
2928
+ if (existing) return existing.promise;
2929
+ let resolvePromise;
2930
+ let rejectPromise;
2931
+ const promise = new Promise((resolve, reject) => {
2932
+ resolvePromise = resolve;
2933
+ rejectPromise = reject;
2934
+ });
2935
+ const abortController = new AbortController();
2936
+ this.inFlightUploads.set(id, { promise, abortController });
2937
+ void (async () => {
2938
+ const attachmentManager = this.resolveAttachmentManager(channelCid);
2939
+ const trackProgress = attachmentManager.config.trackUploadProgress;
2940
+ try {
2941
+ this.upsertUpload({
2942
+ uploadConfirmationPending: false,
2943
+ id,
2944
+ uploadProgress: trackProgress ? 0 : void 0
2945
+ });
2946
+ const onProgress = trackProgress ? (progress) => {
2947
+ this.updateUpload({
2948
+ id,
2949
+ uploadProgress: progress,
2950
+ // Only a number says anything about the flush, so a report without one leaves
2951
+ // the flag alone: `undefined` means the transport cannot measure this upload,
2952
+ // not that bytes were un-sent. Lowering it there would drop a UI that had
2953
+ // already gone indeterminate back to a determinate one with nothing to show.
2954
+ //
2955
+ // The record is removed in the `finally` below, so a record that still exists
2956
+ // while reporting 100% can only mean "flushed, awaiting the response".
2957
+ ...typeof progress === "number" ? { uploadConfirmationPending: progress >= 100 } : {}
2958
+ });
2959
+ } : void 0;
2960
+ const uploadRequestOptions = {
2961
+ abortSignal: abortController.signal,
2962
+ ...onProgress ? { onProgress } : {}
2963
+ };
2964
+ const response = await attachmentManager.doUploadRequest(
2965
+ file,
2966
+ uploadRequestOptions
2967
+ );
2968
+ resolvePromise(response);
2969
+ } catch (error) {
2970
+ rejectPromise(error);
2971
+ } finally {
2972
+ this.inFlightUploads.delete(id);
2973
+ this.deleteUploadRecord(id);
2974
+ }
2975
+ })();
2976
+ return promise;
2977
+ };
2978
+ this.upsertUpload = (record) => {
2979
+ this.state.partialNext({
2980
+ uploads: upsertById(this.uploads, record)
2981
+ });
2982
+ };
2983
+ this.updateUpload = (record) => {
2984
+ this.state.next((current) => {
2985
+ const nextUploads = updateById(current.uploads, record);
2986
+ if (!nextUploads) return current;
2987
+ return { ...current, uploads: nextUploads };
2988
+ });
2989
+ };
2990
+ this.state = new StateStore(initState());
2991
+ }
2992
+ resolveAttachmentManager(channelCid) {
2993
+ const colon = channelCid.indexOf(":");
2994
+ if (colon <= 0 || colon === channelCid.length - 1) {
2995
+ throw new Error(`Invalid channelCid: ${channelCid}`);
2996
+ }
2997
+ const channelType = channelCid.slice(0, colon);
2998
+ const channelId = channelCid.slice(colon + 1);
2999
+ return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
3000
+ }
3001
+ get uploads() {
3002
+ return this.state.getLatestValue().uploads;
3003
+ }
3004
+ };
3005
+
2792
3006
  // src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts
2793
3007
  var createUploadErrorHandlerMiddleware = (composer) => ({
2794
3008
  id: "stream-io/attachment-manager-middleware/upload-error",
@@ -2801,6 +3015,7 @@ var createUploadErrorHandlerMiddleware = (composer) => ({
2801
3015
  const { attachment, error } = state;
2802
3016
  if (!error) return forward();
2803
3017
  if (!attachment) return discard();
3018
+ if (isUploadCancellation(error)) return forward();
2804
3019
  const reason = error instanceof Error ? error.message : "unknown error";
2805
3020
  composer.client.notifications.addError({
2806
3021
  message: "Error uploading attachment",
@@ -2825,7 +3040,7 @@ var AttachmentPostUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2825
3040
  super();
2826
3041
  this.use([
2827
3042
  createUploadErrorHandlerMiddleware(composer),
2828
- createPostUploadAttachmentEnrichmentMiddleware()
3043
+ createPostUploadAttachmentEnrichmentMiddleware(composer)
2829
3044
  ]);
2830
3045
  }
2831
3046
  };
@@ -3273,7 +3488,7 @@ function mergeWithDiff(target, source, customizer) {
3273
3488
  }
3274
3489
 
3275
3490
  // src/messageComposer/attachmentManager.ts
3276
- var initState = ({
3491
+ var initState2 = ({
3277
3492
  message
3278
3493
  }) => ({
3279
3494
  attachments: (message?.attachments ?? [])?.filter(({ og_scrape_url }) => !og_scrape_url).map((att) => {
@@ -3301,13 +3516,14 @@ var _AttachmentManager = class _AttachmentManager {
3301
3516
  ...attachment,
3302
3517
  localMetadata: {
3303
3518
  ...attachment.localMetadata,
3519
+ uploadConfirmationPending: false,
3304
3520
  uploadProgress: void 0,
3305
3521
  uploadState: "failed"
3306
3522
  }
3307
3523
  };
3308
3524
  };
3309
3525
  this.initState = ({ message } = {}) => {
3310
- this.state.next(initState({ message }));
3526
+ this.state.next(initState2({ message }));
3311
3527
  };
3312
3528
  this.getSnapshot = () => {
3313
3529
  const state = this.state.getLatestValue();
@@ -3384,8 +3600,22 @@ var _AttachmentManager = class _AttachmentManager {
3384
3600
  this.state.partialNext({ attachments });
3385
3601
  }
3386
3602
  };
3603
+ /**
3604
+ * Releases a local preview URL the SDK minted, if it minted one.
3605
+ *
3606
+ * Only `blob:` URLs are ours to revoke: `toLocalUploadAttachment` puts a
3607
+ * `URL.createObjectURL` result in `previewUri` for a `File`, but for a React Native
3608
+ * `FileReference` it stores the caller's own native file URI, which must be left alone.
3609
+ */
3610
+ this.revokePreviewUri = (attachment) => {
3611
+ const previewUri = attachment.localMetadata?.previewUri;
3612
+ if (previewUri?.startsWith("blob:")) URL.revokeObjectURL?.(previewUri);
3613
+ };
3387
3614
  this.removeAttachments = (localAttachmentIds) => {
3388
3615
  if (!localAttachmentIds.length) return;
3616
+ const removedAttachments = this.attachments.filter(
3617
+ (attachment) => localAttachmentIds.includes(attachment.localMetadata?.id)
3618
+ );
3389
3619
  this.state.partialNext({
3390
3620
  attachments: this.attachments.filter(
3391
3621
  (attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id)
@@ -3394,6 +3624,7 @@ var _AttachmentManager = class _AttachmentManager {
3394
3624
  for (const id of localAttachmentIds) {
3395
3625
  this.client.uploadManager.deleteUploadRecord(id);
3396
3626
  }
3627
+ removedAttachments.forEach(this.revokePreviewUri);
3397
3628
  };
3398
3629
  this.getUploadConfigCheck = async (fileLike) => {
3399
3630
  const client = this.channel.getClient();
@@ -3545,21 +3776,27 @@ var _AttachmentManager = class _AttachmentManager {
3545
3776
  localMetadata: {
3546
3777
  ...attachment.localMetadata,
3547
3778
  uploadState: "failed",
3548
- uploadProgress: void 0
3779
+ uploadProgress: void 0,
3780
+ // `false`, not `undefined`: these updates go through `updateAttachment`, which merges
3781
+ // via `mergeWith` — and that skips `undefined` source values, so `undefined` would
3782
+ // leave a stale `true` in place.
3783
+ uploadConfirmationPending: false
3549
3784
  }
3550
3785
  };
3551
- this.client.notifications.addError({
3552
- message: "Error uploading attachment",
3553
- origin: {
3554
- emitter: "AttachmentManager",
3555
- context: { attachment, failedAttachment }
3556
- },
3557
- options: {
3558
- type: "api:attachment:upload:failed",
3559
- metadata: { reason },
3560
- originalError: error instanceof Error ? error : void 0
3561
- }
3562
- });
3786
+ if (!isUploadCancellation(error)) {
3787
+ this.client.notifications.addError({
3788
+ message: "Error uploading attachment",
3789
+ origin: {
3790
+ emitter: "AttachmentManager",
3791
+ context: { attachment, failedAttachment }
3792
+ },
3793
+ options: {
3794
+ type: "api:attachment:upload:failed",
3795
+ metadata: { reason },
3796
+ originalError: error instanceof Error ? error : void 0
3797
+ }
3798
+ });
3799
+ }
3563
3800
  this.updateAttachment(failedAttachment);
3564
3801
  return failedAttachment;
3565
3802
  }
@@ -3572,7 +3809,9 @@ var _AttachmentManager = class _AttachmentManager {
3572
3809
  localMetadata: {
3573
3810
  ...attachment.localMetadata,
3574
3811
  uploadState: "finished",
3575
- uploadProgress: void 0
3812
+ uploadProgress: void 0,
3813
+ // See the note in the failure branch: `undefined` cannot clear through `mergeWith`.
3814
+ uploadConfirmationPending: false
3576
3815
  }
3577
3816
  };
3578
3817
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3621,7 +3860,9 @@ var _AttachmentManager = class _AttachmentManager {
3621
3860
  localMetadata: {
3622
3861
  ...attachment.localMetadata,
3623
3862
  uploadState: error ? "failed" : "finished",
3624
- uploadProgress: void 0
3863
+ uploadProgress: void 0,
3864
+ // See the note in `uploadAttachment`: `undefined` cannot clear through `mergeWith`.
3865
+ uploadConfirmationPending: false
3625
3866
  }
3626
3867
  },
3627
3868
  error,
@@ -3646,7 +3887,7 @@ var _AttachmentManager = class _AttachmentManager {
3646
3887
  );
3647
3888
  };
3648
3889
  this.composer = composer;
3649
- this.state = new StateStore(initState({ message }));
3890
+ this.state = new StateStore(initState2({ message }));
3650
3891
  this.attachmentsByIdGetterCache = { attachmentsById: {}, attachments: [] };
3651
3892
  this.preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor({
3652
3893
  composer
@@ -3751,7 +3992,8 @@ var _AttachmentManager = class _AttachmentManager {
3751
3992
  localMetadata: {
3752
3993
  ...attachment.localMetadata,
3753
3994
  uploadState: "uploading",
3754
- uploadProgress: this.config.trackUploadProgress ? 0 : void 0
3995
+ uploadProgress: this.config.trackUploadProgress ? 0 : void 0,
3996
+ uploadConfirmationPending: false
3755
3997
  }
3756
3998
  }
3757
3999
  ]);
@@ -3764,7 +4006,8 @@ var _AttachmentManager = class _AttachmentManager {
3764
4006
  localMetadata: {
3765
4007
  ...attachment.localMetadata,
3766
4008
  uploadState: "uploading",
3767
- uploadProgress: nextUpload.uploadProgress
4009
+ uploadProgress: nextUpload.uploadProgress,
4010
+ uploadConfirmationPending: nextUpload.uploadConfirmationPending
3768
4011
  }
3769
4012
  });
3770
4013
  }
@@ -3818,6 +4061,50 @@ var localAttachmentToAttachment = (localAttachment) => {
3818
4061
  const { localMetadata, ...attachment } = localAttachment;
3819
4062
  return attachment;
3820
4063
  };
4064
+ var composeWithPendingUploads = ({
4065
+ composer,
4066
+ state
4067
+ }) => {
4068
+ const composerIsKeptAsDraft = !!composer.pollId;
4069
+ const relevantAttachments = composer.attachmentManager.attachments.filter(
4070
+ (attachment) => isFinishedUpload(attachment) || !composerIsKeptAsDraft && isPendingUpload(attachment)
4071
+ );
4072
+ const localAttachments = (state.localMessage.attachments ?? []).concat(
4073
+ relevantAttachments.map(
4074
+ (attachment) => isPendingUpload(attachment) ? attachment : localAttachmentToAttachment(attachment)
4075
+ )
4076
+ );
4077
+ const messageAttachments = (state.message.attachments ?? []).concat(
4078
+ relevantAttachments.filter(isFinishedUpload).map(localAttachmentToAttachment)
4079
+ );
4080
+ if (!localAttachments.length && !messageAttachments.length) return state;
4081
+ return {
4082
+ ...state,
4083
+ localMessage: localAttachments.length ? {
4084
+ ...state.localMessage,
4085
+ attachments: localAttachments
4086
+ } : state.localMessage,
4087
+ message: messageAttachments.length ? {
4088
+ ...state.message,
4089
+ attachments: messageAttachments
4090
+ } : state.message
4091
+ };
4092
+ };
4093
+ var createSendWithPendingUploadsAttachmentsMiddleware = (composer) => ({
4094
+ allowsPendingUploads: true,
4095
+ id: "stream-io/message-composer-middleware/attachments",
4096
+ handlers: {
4097
+ compose: ({
4098
+ state,
4099
+ next,
4100
+ forward
4101
+ }) => {
4102
+ const { attachmentManager } = composer;
4103
+ if (!attachmentManager) return forward();
4104
+ return next(composeWithPendingUploads({ composer, state }));
4105
+ }
4106
+ }
4107
+ });
3821
4108
  var createAttachmentsCompositionMiddleware = (composer) => ({
3822
4109
  id: "stream-io/message-composer-middleware/attachments",
3823
4110
  handlers: {
@@ -4034,7 +4321,7 @@ var getInitialMentions = (message) => {
4034
4321
  };
4035
4322
  var isSameMentionEntity = (first, second) => first.id === second.id && first.mentionType === second.mentionType;
4036
4323
  var getMentionsFromState = (state) => state.mentions ?? state.mentionedUsers.map(userResponseToMentionEntity);
4037
- var initState2 = ({
4324
+ var initState3 = ({
4038
4325
  composer,
4039
4326
  message
4040
4327
  }) => {
@@ -4063,7 +4350,7 @@ var initState2 = ({
4063
4350
  var TextComposer = class {
4064
4351
  constructor({ composer, message }) {
4065
4352
  this.initState = ({ message } = {}) => {
4066
- this.state.next(initState2({ composer: this.composer, message }));
4353
+ this.state.next(initState3({ composer: this.composer, message }));
4067
4354
  };
4068
4355
  this.getSnapshot = (state = this.state.getLatestValue()) => state;
4069
4356
  this.restoreSnapshot = (snapshot) => {
@@ -4256,7 +4543,7 @@ var TextComposer = class {
4256
4543
  this.commitState(output.state);
4257
4544
  };
4258
4545
  this.composer = composer;
4259
- this.state = new StateStore(initState2({ composer, message }));
4546
+ this.state = new StateStore(initState3({ composer, message }));
4260
4547
  this.middlewareExecutor = new TextComposerMiddlewareExecutor({ composer });
4261
4548
  }
4262
4549
  get channel() {
@@ -5958,6 +6245,13 @@ var MessageComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5958
6245
  createCompositionDataCleanupMiddleware(composer)
5959
6246
  ]);
5960
6247
  }
6248
+ /**
6249
+ * Narrows the base accessor to the composition middleware type, so callers can read the
6250
+ * declarations it carries (`allowsPendingUploads`) without casting.
6251
+ */
6252
+ get installedMiddleware() {
6253
+ return super.installedMiddleware;
6254
+ }
5961
6255
  };
5962
6256
  var MessageDraftComposerMiddlewareExecutor = class extends MiddlewareExecutor {
5963
6257
  constructor({ composer }) {
@@ -7072,7 +7366,7 @@ var DEFAULT_COMPOSER_CONFIG = {
7072
7366
  };
7073
7367
 
7074
7368
  // src/messageComposer/CustomDataManager.ts
7075
- var initState3 = (options) => {
7369
+ var initState4 = (options) => {
7076
7370
  if (!options)
7077
7371
  return { message: {}, custom: {} };
7078
7372
  return { message: {}, custom: {} };
@@ -7081,14 +7375,14 @@ var CustomDataManager = class {
7081
7375
  constructor({ composer, message }) {
7082
7376
  this.isMessageDataEqual = (nextState, previousState) => JSON.stringify(nextState.message) === JSON.stringify(previousState?.message);
7083
7377
  this.initState = ({ message } = {}) => {
7084
- this.state.next(initState3({ composer: this.composer, message }));
7378
+ this.state.next(initState4({ composer: this.composer, message }));
7085
7379
  };
7086
7380
  this.getSnapshot = () => this.state.getLatestValue();
7087
7381
  this.restoreSnapshot = (snapshot) => {
7088
7382
  this.state.next(snapshot);
7089
7383
  };
7090
7384
  this.composer = composer;
7091
- this.state = new StateStore(initState3({ composer, message }));
7385
+ this.state = new StateStore(initState4({ composer, message }));
7092
7386
  }
7093
7387
  get customMessageData() {
7094
7388
  return this.state.getLatestValue().message;
@@ -7124,7 +7418,7 @@ var LinkPreviewStatus = /* @__PURE__ */ ((LinkPreviewStatus2) => {
7124
7418
  return LinkPreviewStatus2;
7125
7419
  })(LinkPreviewStatus || {});
7126
7420
  var linkPreviewArrayToMap = (linkPreviews) => new Map(linkPreviews.map((linkPreview) => [linkPreview.og_scrape_url, linkPreview]));
7127
- var initState4 = ({
7421
+ var initState5 = ({
7128
7422
  message
7129
7423
  }) => message ? {
7130
7424
  previews: message.attachments?.reduce((acc, attachment) => {
@@ -7143,7 +7437,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7143
7437
  this.shouldDiscardEnrichQueries = false;
7144
7438
  this.initState = ({ message } = {}) => {
7145
7439
  this.cancelURLEnrichment();
7146
- this.state.next(initState4({ message: this.enabled ? message : void 0 }));
7440
+ this.state.next(initState5({ message: this.enabled ? message : void 0 }));
7147
7441
  };
7148
7442
  this.getSnapshot = () => this.state.getLatestValue();
7149
7443
  this.restoreSnapshot = (snapshot) => {
@@ -7246,7 +7540,7 @@ var _LinkPreviewsManager = class _LinkPreviewsManager {
7246
7540
  };
7247
7541
  this.composer = composer;
7248
7542
  this.state = new StateStore(
7249
- initState4({ message: this.enabled ? message : void 0 })
7543
+ initState5({ message: this.enabled ? message : void 0 })
7250
7544
  );
7251
7545
  this.findAndEnrichUrls = debounce(
7252
7546
  this._findAndEnrichUrls.bind(this),
@@ -7334,7 +7628,7 @@ var LinkPreviewsManager = _LinkPreviewsManager;
7334
7628
 
7335
7629
  // src/messageComposer/LocationComposer.ts
7336
7630
  var MIN_LIVE_LOCATION_SHARE_DURATION = 60 * 1e3;
7337
- var initState5 = ({
7631
+ var initState6 = ({
7338
7632
  message
7339
7633
  }) => ({
7340
7634
  location: message?.shared_location ?? null
@@ -7342,7 +7636,7 @@ var initState5 = ({
7342
7636
  var LocationComposer = class {
7343
7637
  constructor({ composer, message }) {
7344
7638
  this.initState = ({ message } = {}) => {
7345
- this.state.next(initState5({ message }));
7639
+ this.state.next(initState6({ message }));
7346
7640
  };
7347
7641
  this.getSnapshot = () => this.state.getLatestValue();
7348
7642
  this.restoreSnapshot = (snapshot) => {
@@ -7360,7 +7654,7 @@ var LocationComposer = class {
7360
7654
  });
7361
7655
  };
7362
7656
  this.composer = composer;
7363
- this.state = new StateStore(initState5({ message }));
7657
+ this.state = new StateStore(initState6({ message }));
7364
7658
  this._deviceId = this.config.getDeviceId();
7365
7659
  }
7366
7660
  get config() {
@@ -8081,7 +8375,7 @@ var initEditingAuditState = (composition) => {
8081
8375
  }
8082
8376
  };
8083
8377
  };
8084
- var initState6 = (composition) => {
8378
+ var initState7 = (composition) => {
8085
8379
  if (!composition) {
8086
8380
  return {
8087
8381
  draftId: null,
@@ -8168,7 +8462,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8168
8462
  this.textComposer.initState({ message });
8169
8463
  this.pollComposer.initState();
8170
8464
  this.customDataManager.initState({ message });
8171
- this.state.next(initState6(composition));
8465
+ this.state.next(initState7(composition));
8172
8466
  };
8173
8467
  this.initStateFromChannelResponse = (channelApiResponse) => {
8174
8468
  if (this.channel.cid !== channelApiResponse.channel.cid) {
@@ -8634,7 +8928,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8634
8928
  this.editingAuditState = new StateStore(
8635
8929
  this.initEditingAuditState(composition)
8636
8930
  );
8637
- this.state = new StateStore(initState6(composition));
8931
+ this.state = new StateStore(initState7(composition));
8638
8932
  this.compositionMiddlewareExecutor = new MessageComposerMiddlewareExecutor({
8639
8933
  composer: this
8640
8934
  });
@@ -8713,8 +9007,32 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
8713
9007
  const currentCommand = this.textComposer.command;
8714
9008
  return !currentCommand || this.validateCommandSendability(currentCommand).ready;
8715
9009
  }
9010
+ /**
9011
+ * Whether the installed composition middleware composes messages whose uploads are still in
9012
+ * flight — see `MessageCompositionMiddleware.allowsPendingUploads`.
9013
+ *
9014
+ * Derived from the chain rather than from config, so there is exactly one switch: the UI SDK
9015
+ * that implements the rest of the flow installs the middleware, and everything that has to
9016
+ * follow from that (starting with {@link hasSendableData}) follows on its own.
9017
+ *
9018
+ * **Temporary API.** In v10 sending with pending uploads becomes part of the composer
9019
+ * configuration (`MessageComposerConfig`, set through `updateConfig`), and this getter,
9020
+ * together with the middleware declaration it reads, is replaced by that config field.
9021
+ */
9022
+ get allowsPendingUploads() {
9023
+ return this.compositionMiddlewareExecutor.installedMiddleware.some(
9024
+ (middleware) => middleware.allowsPendingUploads
9025
+ );
9026
+ }
8716
9027
  get hasSendableData() {
8717
- return this.isCommandSendable && !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
9028
+ if (!this.isCommandSendable) return false;
9029
+ if (this.allowsPendingUploads) {
9030
+ const hasSendableAttachment = this.attachmentManager.attachments.some(
9031
+ (attachment) => isFinishedUpload(attachment) || isPendingUpload(attachment)
9032
+ );
9033
+ return !!(!this.textComposer.textIsEmpty || hasSendableAttachment || this.pollId || this.locationComposer.validLocation);
9034
+ }
9035
+ return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
8718
9036
  }
8719
9037
  get compositionIsEmpty() {
8720
9038
  return !this.quotedMessage && this.contentIsEmpty;
@@ -8853,7 +9171,6 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
8853
9171
  * @param options
8854
9172
  */
8855
9173
  this.markRead = async (collection, options) => {
8856
- if (!userHasReadReceipts(this.client)) return null;
8857
9174
  let result = null;
8858
9175
  if (isChannel(collection)) {
8859
9176
  result = await collection.markAsReadRequest(options);
@@ -8863,7 +9180,7 @@ var MessageDeliveryReporter = class _MessageDeliveryReporter {
8863
9180
  thread_id: collection.id
8864
9181
  });
8865
9182
  }
8866
- this.removeCandidateFor(collection);
9183
+ if (result) this.removeCandidateFor(collection);
8867
9184
  return result;
8868
9185
  };
8869
9186
  /**
@@ -9248,18 +9565,14 @@ var Channel = class {
9248
9565
  return await this.query(defaultOptions, "latest");
9249
9566
  };
9250
9567
  this._callChannelListeners = (event) => {
9251
- const channel = this;
9252
- const listeners = [];
9253
- if (channel.listeners.all) {
9254
- listeners.push(...channel.listeners.all);
9255
- }
9256
- if (channel.listeners[event.type]) {
9257
- listeners.push(...channel.listeners[event.type]);
9258
- }
9568
+ const listeners = [
9569
+ ...this.listeners.all ?? [],
9570
+ ...this.listeners[event.type] ?? []
9571
+ ];
9572
+ const { logger } = this._client;
9259
9573
  for (const listener of listeners) {
9260
- if (typeof listener !== "string") {
9261
- listener(event);
9262
- }
9574
+ if (typeof listener === "string") continue;
9575
+ invokeEventListener(listener, event, logger);
9263
9576
  }
9264
9577
  };
9265
9578
  /**
@@ -9341,14 +9654,16 @@ var Channel = class {
9341
9654
  *
9342
9655
  * @return {Promise<SendMessageAPIResponse>} The Server Response
9343
9656
  */
9344
- async _sendMessage(message, options) {
9345
- return await this.getClient().post(
9346
- this._channelURL() + "/message",
9347
- {
9348
- message,
9349
- ...options
9350
- }
9351
- );
9657
+ async _sendMessage(rawMessage, options) {
9658
+ const client = this.getClient();
9659
+ const message = sanitizeOutgoingAttachments({
9660
+ client,
9661
+ message: rawMessage
9662
+ });
9663
+ return await client.post(this._channelURL() + "/message", {
9664
+ message,
9665
+ ...options
9666
+ });
9352
9667
  }
9353
9668
  async sendMessage(message, options) {
9354
9669
  try {
@@ -10171,14 +10486,15 @@ var Channel = class {
10171
10486
  return await this.getClient().messageDeliveryReporter.markRead(this, data);
10172
10487
  }
10173
10488
  /**
10174
- * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
10489
+ * markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
10490
+ * enabled and the user holds the `read-events` capability on this channel
10175
10491
  *
10176
10492
  * @param {MarkReadOptions} data
10177
10493
  * @return {Promise<EventAPIResponse | null>} Description
10178
10494
  */
10179
10495
  async markAsReadRequest(data = {}) {
10180
10496
  this._checkInitialized();
10181
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10497
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10182
10498
  return null;
10183
10499
  }
10184
10500
  return await this.getClient().post(this._channelURL() + "/read", {
@@ -10186,14 +10502,15 @@ var Channel = class {
10186
10502
  });
10187
10503
  }
10188
10504
  /**
10189
- * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is enabled
10505
+ * markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
10506
+ * enabled and the user holds the `read-events` capability on this channel
10190
10507
  *
10191
10508
  * @param {MarkUnreadOptions} data
10192
10509
  * @return {APIResponse} An API response
10193
10510
  */
10194
10511
  async markUnread(data) {
10195
10512
  this._checkInitialized();
10196
- if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
10513
+ if (!this.getClient()._isUsingServerAuth() && (!this.getConfig()?.read_events || !channelHasReadEvents(this))) {
10197
10514
  return Promise.resolve(null);
10198
10515
  }
10199
10516
  return await this.getClient().post(this._channelURL() + "/unread", {
@@ -10799,6 +11116,7 @@ var Channel = class {
10799
11116
  // delivery-report network sync below is skipped for it.
10800
11117
  case "message.read_locally":
10801
11118
  case "message.read":
11119
+ if (event.thread) break;
10802
11120
  if (event.user?.id && event.created_at) {
10803
11121
  const previousReadState = channelState.read[event.user.id];
10804
11122
  channelState.read[event.user.id] = {
@@ -11248,7 +11566,7 @@ var ClientState = class {
11248
11566
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
11249
11567
 
11250
11568
  // src/insights.ts
11251
- var import_axios = __toESM(require("axios"));
11569
+ var import_axios2 = __toESM(require("axios"));
11252
11570
  var InsightMetrics = class {
11253
11571
  constructor() {
11254
11572
  this.connectionStartTimestamp = null;
@@ -11261,7 +11579,7 @@ var postInsights = async (insightType, insights) => {
11261
11579
  const maxAttempts = 3;
11262
11580
  for (let i = 0; i < maxAttempts; i++) {
11263
11581
  try {
11264
- await import_axios.default.post(
11582
+ await import_axios2.default.post(
11265
11583
  `https://chat-insights.getstream.io/insights/${insightType}`,
11266
11584
  insights
11267
11585
  );
@@ -11769,131 +12087,6 @@ var StableWSConnection = class {
11769
12087
  }
11770
12088
  };
11771
12089
 
11772
- // src/uploadManager.ts
11773
- var initState7 = () => ({ uploads: {} });
11774
- var upsertById = (uploads, record) => ({
11775
- ...uploads,
11776
- [record.id]: { ...uploads[record.id], ...record }
11777
- });
11778
- var updateById = (uploads, record) => {
11779
- if (!(record.id in uploads)) return null;
11780
- const current = uploads[record.id];
11781
- return { ...uploads, [record.id]: { ...current, ...record } };
11782
- };
11783
- var UploadManager = class {
11784
- constructor(client) {
11785
- this.client = client;
11786
- this.inFlightUploads = /* @__PURE__ */ new Map();
11787
- this.getUpload = (id) => this.uploads[id];
11788
- /**
11789
- * Clears all upload records.
11790
- * Invoked when the user disconnects so a later session does not inherit stale upload state.
11791
- * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
11792
- */
11793
- this.reset = () => {
11794
- for (const { abortController } of this.inFlightUploads.values()) {
11795
- abortController.abort();
11796
- }
11797
- this.inFlightUploads.clear();
11798
- this.state.next(initState7());
11799
- };
11800
- /**
11801
- * Removes the upload record for `id` if present.
11802
- * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
11803
- */
11804
- this.deleteUploadRecord = (id) => {
11805
- const flight = this.inFlightUploads.get(id);
11806
- if (flight) {
11807
- this.inFlightUploads.delete(id);
11808
- flight.abortController.abort();
11809
- }
11810
- this.state.next((current) => {
11811
- if (!(id in current.uploads)) return current;
11812
- const uploads = { ...current.uploads };
11813
- delete uploads[id];
11814
- return { ...current, uploads };
11815
- });
11816
- };
11817
- /**
11818
- * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
11819
- * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
11820
- * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
11821
- */
11822
- this.upload = ({
11823
- id,
11824
- channelCid,
11825
- file
11826
- }) => {
11827
- const existing = this.inFlightUploads.get(id);
11828
- if (existing) return existing.promise;
11829
- let resolvePromise;
11830
- let rejectPromise;
11831
- const promise = new Promise((resolve, reject) => {
11832
- resolvePromise = resolve;
11833
- rejectPromise = reject;
11834
- });
11835
- const abortController = new AbortController();
11836
- this.inFlightUploads.set(id, { promise, abortController });
11837
- void (async () => {
11838
- const attachmentManager = this.resolveAttachmentManager(channelCid);
11839
- const trackProgress = attachmentManager.config.trackUploadProgress;
11840
- try {
11841
- this.upsertUpload({
11842
- id,
11843
- uploadProgress: trackProgress ? 0 : void 0
11844
- });
11845
- const onProgress = trackProgress ? (progress) => {
11846
- this.updateUpload({
11847
- id,
11848
- uploadProgress: progress
11849
- });
11850
- } : void 0;
11851
- const uploadRequestOptions = {
11852
- abortSignal: abortController.signal,
11853
- ...onProgress ? { onProgress } : {}
11854
- };
11855
- const response = await attachmentManager.doUploadRequest(
11856
- file,
11857
- uploadRequestOptions
11858
- );
11859
- resolvePromise(response);
11860
- } catch (error) {
11861
- rejectPromise(error);
11862
- } finally {
11863
- this.inFlightUploads.delete(id);
11864
- this.deleteUploadRecord(id);
11865
- }
11866
- })();
11867
- return promise;
11868
- };
11869
- this.upsertUpload = (record) => {
11870
- this.state.partialNext({
11871
- uploads: upsertById(this.uploads, record)
11872
- });
11873
- };
11874
- this.updateUpload = (record) => {
11875
- this.state.next((current) => {
11876
- const nextUploads = updateById(current.uploads, record);
11877
- if (!nextUploads) return current;
11878
- return { ...current, uploads: nextUploads };
11879
- });
11880
- };
11881
- this.state = new StateStore(initState7());
11882
- }
11883
- resolveAttachmentManager(channelCid) {
11884
- const colon = channelCid.indexOf(":");
11885
- if (colon <= 0 || colon === channelCid.length - 1) {
11886
- throw new Error(`Invalid channelCid: ${channelCid}`);
11887
- }
11888
- const channelType = channelCid.slice(0, colon);
11889
- const channelId = channelCid.slice(colon + 1);
11890
- return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
11891
- }
11892
- get uploads() {
11893
- return this.state.getLatestValue().uploads;
11894
- }
11895
- };
11896
-
11897
12090
  // src/signing.ts
11898
12091
  var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
11899
12092
  var import_crypto = __toESM(require("crypto"));
@@ -12152,7 +12345,7 @@ var TokenManager = class {
12152
12345
  };
12153
12346
 
12154
12347
  // src/connection_fallback.ts
12155
- var import_axios2 = __toESM(require("axios"));
12348
+ var import_axios3 = __toESM(require("axios"));
12156
12349
  var WSConnectionFallback = class {
12157
12350
  constructor({ client }) {
12158
12351
  /** @private */
@@ -12171,7 +12364,7 @@ var WSConnectionFallback = class {
12171
12364
  /** @private */
12172
12365
  this._req = async (params, config, retry) => {
12173
12366
  if (!this.cancelToken && !params.close) {
12174
- this.cancelToken = import_axios2.default.CancelToken.source();
12367
+ this.cancelToken = import_axios3.default.CancelToken.source();
12175
12368
  }
12176
12369
  try {
12177
12370
  const res = await this.client.doAxiosRequest(
@@ -12207,7 +12400,7 @@ var WSConnectionFallback = class {
12207
12400
  }
12208
12401
  }
12209
12402
  } catch (error) {
12210
- if (import_axios2.default.isCancel(error)) {
12403
+ if (import_axios3.default.isCancel(error)) {
12211
12404
  this._log(`_poll() - axios canceled request`);
12212
12405
  return;
12213
12406
  }
@@ -14630,7 +14823,7 @@ var StreamChat = class _StreamChat {
14630
14823
  return this.handleResponse(response);
14631
14824
  } catch (e) {
14632
14825
  e.client_request_id = requestConfig.headers?.["x-client-request-id"];
14633
- if (import_axios3.default.isCancel(e)) throw e;
14826
+ if (import_axios4.default.isCancel(e)) throw e;
14634
14827
  this._logApiError(type, url, e);
14635
14828
  this.consecutiveFailures += 1;
14636
14829
  if (e.response) {
@@ -14779,16 +14972,12 @@ var StreamChat = class _StreamChat {
14779
14972
  }
14780
14973
  };
14781
14974
  this._callClientListeners = (event) => {
14782
- const client = this;
14783
- const listeners = [];
14784
- if (client.listeners.all) {
14785
- listeners.push(...client.listeners.all);
14786
- }
14787
- if (client.listeners[event.type]) {
14788
- listeners.push(...client.listeners[event.type]);
14789
- }
14975
+ const listeners = [
14976
+ ...this.listeners.all ?? [],
14977
+ ...this.listeners[event.type] ?? []
14978
+ ];
14790
14979
  for (const listener of listeners) {
14791
- listener(event);
14980
+ invokeEventListener(listener, event, this.logger);
14792
14981
  }
14793
14982
  };
14794
14983
  this.recoverState = async () => {
@@ -14942,7 +15131,7 @@ var StreamChat = class _StreamChat {
14942
15131
  return this.userAgent;
14943
15132
  }
14944
15133
  if (!this.cachedUserAgent) {
14945
- const version = "9.51.0";
15134
+ const version = "9.52.1";
14946
15135
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14947
15136
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14948
15137
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -15010,7 +15199,7 @@ var StreamChat = class _StreamChat {
15010
15199
  keepAliveMsecs: 3e3
15011
15200
  });
15012
15201
  }
15013
- this.axiosInstance = import_axios3.default.create(this.options);
15202
+ this.axiosInstance = import_axios4.default.create(this.options);
15014
15203
  this.setBaseURL(this.options.baseURL || "https://chat.stream-io-api.com");
15015
15204
  if (typeof process !== "undefined" && "env" in process && process.env.STREAM_LOCAL_TEST_RUN) {
15016
15205
  this.setBaseURL("http://localhost:3030");
@@ -15031,7 +15220,7 @@ var StreamChat = class _StreamChat {
15031
15220
  this.defaultWSTimeoutWithFallback = 6 * 1e3;
15032
15221
  this.defaultWSTimeout = 15 * 1e3;
15033
15222
  this.axiosInstance.defaults.paramsSerializer = axiosParamsSerializer;
15034
- this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : () => null;
15223
+ this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : noopLogger;
15035
15224
  this.recoverStateOnReconnect = this.options.recoverStateOnReconnect;
15036
15225
  this.threads = new ThreadManager({ client: this });
15037
15226
  this.polls = new PollManager({ client: this });
@@ -16362,6 +16551,11 @@ var StreamChat = class _StreamChat {
16362
16551
  }
16363
16552
  /**
16364
16553
  * unflagMessage - unflag a message
16554
+ *
16555
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16556
+ * the backend - calling this resolves successfully but does not remove the flag.
16557
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16558
+ *
16365
16559
  * @param {string} targetMessageID
16366
16560
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16367
16561
  * @returns {Promise<APIResponse>}
@@ -16374,6 +16568,11 @@ var StreamChat = class _StreamChat {
16374
16568
  }
16375
16569
  /**
16376
16570
  * unflagUser - unflag a user
16571
+ *
16572
+ * @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
16573
+ * the backend - calling this resolves successfully but does not remove the flag.
16574
+ * Use `client.moderation.submitAction()` against the review queue item instead.
16575
+ *
16377
16576
  * @param {string} targetID
16378
16577
  * @param {string} [options.user_id] currentUserID, only used with serverside auth
16379
16578
  * @returns {Promise<APIResponse>}
@@ -16658,7 +16857,10 @@ var StreamChat = class _StreamChat {
16658
16857
  if (!message.id) {
16659
16858
  throw Error("Please specify the message.id when calling updateMessage");
16660
16859
  }
16661
- const payload = toUpdatedMessagePayload(message);
16860
+ const payload = sanitizeOutgoingAttachments({
16861
+ client: this,
16862
+ message: toUpdatedMessagePayload(message)
16863
+ });
16662
16864
  if (typeof partialUserOrUserId === "string") {
16663
16865
  payload.user_id = partialUserOrUserId;
16664
16866
  } else if (typeof partialUserOrUserId?.id === "string") {
@@ -18474,7 +18676,7 @@ var OfflineError = class extends Error {
18474
18676
  };
18475
18677
 
18476
18678
  // src/offline-support/offline_sync_manager.ts
18477
- var import_axios4 = require("axios");
18679
+ var import_axios5 = require("axios");
18478
18680
  var OfflineDBSyncManager = class {
18479
18681
  constructor({
18480
18682
  client,
@@ -18622,7 +18824,7 @@ var OfflineDBSyncManager = class {
18622
18824
  });
18623
18825
  } catch (e) {
18624
18826
  console.log("An error has occurred while syncing the DB.", e);
18625
- if ((0, import_axios4.isAxiosError)(e) && e.code === "ECONNABORTED") {
18827
+ if ((0, import_axios5.isAxiosError)(e) && e.code === "ECONNABORTED") {
18626
18828
  return;
18627
18829
  }
18628
18830
  const error = e;
@@ -19131,6 +19333,7 @@ var AbstractOfflineDB = class {
19131
19333
  return this.handleMessageUpdatedEvent({ event, execute });
19132
19334
  }
19133
19335
  if (type === "message.read" || type === "notification.mark_read") {
19336
+ if (event.thread) return [];
19134
19337
  return this.handleRead({ event, unreadMessages: 0, execute });
19135
19338
  }
19136
19339
  if (type === "message.read_locally") {
@@ -19698,6 +19901,7 @@ var LiveLocationManager = _LiveLocationManager;
19698
19901
  createMessageComposerStateCompositionMiddleware,
19699
19902
  createPollComposerStateMiddleware,
19700
19903
  createPostUploadAttachmentEnrichmentMiddleware,
19904
+ createSendWithPendingUploadsAttachmentsMiddleware,
19701
19905
  createSharedLocationCompositionMiddleware,
19702
19906
  createTextComposerCompositionMiddleware,
19703
19907
  createTextComposerPreValidationMiddleware,
@@ -19726,6 +19930,7 @@ var LiveLocationManager = _LiveLocationManager;
19726
19930
  getTokenizedSuggestionDisplayName,
19727
19931
  getTriggerCharWithToken,
19728
19932
  gunzipPayload,
19933
+ hasPending,
19729
19934
  insertItemWithTrigger,
19730
19935
  isAudioAttachment,
19731
19936
  isBlobButNotFile,
@@ -19733,6 +19938,7 @@ var LiveLocationManager = _LiveLocationManager;
19733
19938
  isFileAttachment,
19734
19939
  isFileList,
19735
19940
  isFileReference,
19941
+ isFinishedUpload,
19736
19942
  isGiphyAttachment,
19737
19943
  isImageAttachment,
19738
19944
  isImageFile,
@@ -19745,9 +19951,11 @@ var LiveLocationManager = _LiveLocationManager;
19745
19951
  isLocalVoiceRecordingAttachment,
19746
19952
  isOwnUser,
19747
19953
  isPatch,
19954
+ isPendingUpload,
19748
19955
  isScrapedContent,
19749
19956
  isSharedLocationResponse,
19750
19957
  isTargetedOptionTextUpdate,
19958
+ isUploadCancellation,
19751
19959
  isUploadedAttachment,
19752
19960
  isVideoAttachment,
19753
19961
  isVoiceRecordingAttachment,
@@ -19767,11 +19975,14 @@ var LiveLocationManager = _LiveLocationManager;
19767
19975
  readFileAsArrayBuffer,
19768
19976
  removeDiacritics,
19769
19977
  replaceWordWithEntity,
19978
+ settled,
19770
19979
  stripCommandFromText,
19771
19980
  stripMentionTokens,
19772
19981
  textIsEmpty,
19773
19982
  timeLeftMs,
19774
19983
  verifyAndParseWebhook,
19775
- verifySignature
19984
+ verifySignature,
19985
+ withCancellation,
19986
+ withoutConcurrency
19776
19987
  });
19777
19988
  //# sourceMappingURL=index.node.js.map