stream-chat 9.41.1 → 9.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -117,6 +117,7 @@ __export(index_exports, {
117
117
  ThreadManager: () => ThreadManager,
118
118
  TokenManager: () => TokenManager,
119
119
  UPDATE_LIVE_LOCATION_REQUEST_MIN_THROTTLE_TIMEOUT: () => UPDATE_LIVE_LOCATION_REQUEST_MIN_THROTTLE_TIMEOUT,
120
+ UploadManager: () => UploadManager,
120
121
  UserFromToken: () => UserFromToken,
121
122
  UserSearchSource: () => UserSearchSource,
122
123
  VALID_MAX_VOTES_VALUE_REGEX: () => VALID_MAX_VOTES_VALUE_REGEX,
@@ -3278,11 +3279,11 @@ var _AttachmentManager = class _AttachmentManager {
3278
3279
  return attachments;
3279
3280
  }
3280
3281
  }
3281
- return null;
3282
+ return stateAttachments;
3282
3283
  };
3283
3284
  this.updateAttachment = (attachmentToUpdate) => {
3284
3285
  const updatedAttachments = this.prepareAttachmentUpdate(attachmentToUpdate);
3285
- if (updatedAttachments) {
3286
+ if (updatedAttachments && updatedAttachments !== this.attachments) {
3286
3287
  this.state.partialNext({ attachments: updatedAttachments });
3287
3288
  }
3288
3289
  };
@@ -3292,15 +3293,15 @@ var _AttachmentManager = class _AttachmentManager {
3292
3293
  let hasUpdates = false;
3293
3294
  attachmentsToUpsert.forEach((attachment) => {
3294
3295
  const updatedAttachments = this.prepareAttachmentUpdate(attachment);
3295
- if (updatedAttachments) {
3296
- attachments = updatedAttachments;
3297
- hasUpdates = true;
3298
- } else {
3296
+ if (updatedAttachments === null) {
3299
3297
  const localAttachment = ensureIsLocalAttachment(attachment);
3300
3298
  if (localAttachment) {
3301
3299
  attachments.push(localAttachment);
3302
3300
  hasUpdates = true;
3303
3301
  }
3302
+ } else if (updatedAttachments !== this.attachments) {
3303
+ attachments = updatedAttachments;
3304
+ hasUpdates = true;
3304
3305
  }
3305
3306
  });
3306
3307
  if (hasUpdates) {
@@ -3308,11 +3309,15 @@ var _AttachmentManager = class _AttachmentManager {
3308
3309
  }
3309
3310
  };
3310
3311
  this.removeAttachments = (localAttachmentIds) => {
3312
+ if (!localAttachmentIds.length) return;
3311
3313
  this.state.partialNext({
3312
3314
  attachments: this.attachments.filter(
3313
3315
  (attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id)
3314
3316
  )
3315
3317
  });
3318
+ for (const id of localAttachmentIds) {
3319
+ this.client.uploadManager.deleteUploadRecord(id);
3320
+ }
3316
3321
  };
3317
3322
  this.getUploadConfigCheck = async (fileLike) => {
3318
3323
  const client = this.channel.getClient();
@@ -3401,13 +3406,17 @@ var _AttachmentManager = class _AttachmentManager {
3401
3406
  const percent = progressEvent.lengthComputable && progressEvent.total ? Math.round(progressEvent.loaded * 100 / progressEvent.total) : void 0;
3402
3407
  options.onProgress?.(percent);
3403
3408
  } : void 0;
3409
+ const axiosUploadConfig = progressHandler || options?.abortSignal ? {
3410
+ ...progressHandler ? { onUploadProgress: progressHandler } : {},
3411
+ ...options?.abortSignal ? { signal: options.abortSignal } : {}
3412
+ } : void 0;
3404
3413
  if (isFileReference(fileLike)) {
3405
3414
  return this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3406
3415
  fileLike.uri,
3407
3416
  fileLike.name,
3408
3417
  fileLike.type,
3409
3418
  void 0,
3410
- progressHandler ? { onUploadProgress: progressHandler } : void 0
3419
+ axiosUploadConfig
3411
3420
  );
3412
3421
  }
3413
3422
  const file = isFile(fileLike) ? fileLike : createFileFromBlobs({
@@ -3415,13 +3424,7 @@ var _AttachmentManager = class _AttachmentManager {
3415
3424
  fileName: generateFileName(fileLike.type),
3416
3425
  mimeType: fileLike.type
3417
3426
  });
3418
- const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3419
- file,
3420
- void 0,
3421
- void 0,
3422
- void 0,
3423
- progressHandler ? { onUploadProgress: progressHandler } : void 0
3424
- );
3427
+ const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](file, void 0, void 0, void 0, axiosUploadConfig);
3425
3428
  return result;
3426
3429
  };
3427
3430
  /**
@@ -3456,33 +3459,9 @@ var _AttachmentManager = class _AttachmentManager {
3456
3459
  });
3457
3460
  return localAttachment;
3458
3461
  }
3459
- const shouldTrackProgress = this.config.trackUploadProgress;
3460
- const uploadingAttachment = {
3461
- ...attachment,
3462
- localMetadata: {
3463
- ...attachment.localMetadata,
3464
- uploadState: "uploading",
3465
- ...shouldTrackProgress && { uploadProgress: 0 }
3466
- }
3467
- };
3468
- this.upsertAttachments([uploadingAttachment]);
3469
- const uploadOptions = shouldTrackProgress ? {
3470
- onProgress: (percent) => {
3471
- this.updateAttachment({
3472
- ...uploadingAttachment,
3473
- localMetadata: {
3474
- ...uploadingAttachment.localMetadata,
3475
- uploadProgress: percent
3476
- }
3477
- });
3478
- }
3479
- } : void 0;
3480
3462
  let response;
3481
3463
  try {
3482
- response = await this.doUploadRequest(
3483
- localAttachment.localMetadata.file,
3484
- uploadOptions
3485
- );
3464
+ response = await this.upload(attachment);
3486
3465
  } catch (error) {
3487
3466
  const reason = error instanceof Error ? error.message : "unknown error";
3488
3467
  const failedAttachment = {
@@ -3551,31 +3530,10 @@ var _AttachmentManager = class _AttachmentManager {
3551
3530
  this.upsertAttachments([attachment]);
3552
3531
  return preUpload.state.attachment;
3553
3532
  }
3554
- const shouldTrackProgress = this.config.trackUploadProgress;
3555
- attachment = {
3556
- ...attachment,
3557
- localMetadata: {
3558
- ...attachment.localMetadata,
3559
- uploadState: "uploading",
3560
- ...shouldTrackProgress && { uploadProgress: 0 }
3561
- }
3562
- };
3563
- this.upsertAttachments([attachment]);
3564
- const uploadOptions = shouldTrackProgress ? {
3565
- onProgress: (percent) => {
3566
- this.updateAttachment({
3567
- ...attachment,
3568
- localMetadata: {
3569
- ...attachment.localMetadata,
3570
- uploadProgress: percent
3571
- }
3572
- });
3573
- }
3574
- } : void 0;
3575
3533
  let response;
3576
3534
  let error;
3577
3535
  try {
3578
- response = await this.doUploadRequest(file, uploadOptions);
3536
+ response = await this.upload(attachment);
3579
3537
  } catch (err) {
3580
3538
  error = err instanceof Error ? err : void 0;
3581
3539
  }
@@ -3702,6 +3660,40 @@ var _AttachmentManager = class _AttachmentManager {
3702
3660
  ({ localMetadata }) => localMetadata.uploadState === state
3703
3661
  );
3704
3662
  }
3663
+ upload(attachment) {
3664
+ const localId = attachment.localMetadata.id;
3665
+ this.upsertAttachments([
3666
+ {
3667
+ ...attachment,
3668
+ localMetadata: {
3669
+ ...attachment.localMetadata,
3670
+ uploadState: "uploading",
3671
+ uploadProgress: this.config.trackUploadProgress ? 0 : void 0
3672
+ }
3673
+ }
3674
+ ]);
3675
+ const unsubscribe = this.client.uploadManager.state.subscribeWithSelector(
3676
+ (s) => ({ upload: s.uploads[localId] }),
3677
+ ({ upload: nextUpload }) => {
3678
+ if (!nextUpload) return;
3679
+ this.updateAttachment({
3680
+ ...attachment,
3681
+ localMetadata: {
3682
+ ...attachment.localMetadata,
3683
+ uploadState: "uploading",
3684
+ uploadProgress: nextUpload.uploadProgress
3685
+ }
3686
+ });
3687
+ }
3688
+ );
3689
+ return this.client.uploadManager.upload({
3690
+ id: localId,
3691
+ channelCid: this.channel.cid,
3692
+ file: attachment.localMetadata.file
3693
+ }).finally(() => {
3694
+ unsubscribe();
3695
+ });
3696
+ }
3705
3697
  };
3706
3698
  _AttachmentManager.toLocalUploadAttachment = (fileLike) => {
3707
3699
  const file = isFileReference(fileLike) || isFile(fileLike) ? fileLike : createFileFromBlobs({
@@ -10578,6 +10570,131 @@ var StableWSConnection = class {
10578
10570
  }
10579
10571
  };
10580
10572
 
10573
+ // src/uploadManager.ts
10574
+ var initState7 = () => ({ uploads: {} });
10575
+ var upsertById = (uploads, record) => ({
10576
+ ...uploads,
10577
+ [record.id]: { ...uploads[record.id], ...record }
10578
+ });
10579
+ var updateById = (uploads, record) => {
10580
+ if (!(record.id in uploads)) return null;
10581
+ const current = uploads[record.id];
10582
+ return { ...uploads, [record.id]: { ...current, ...record } };
10583
+ };
10584
+ var UploadManager = class {
10585
+ constructor(client) {
10586
+ this.client = client;
10587
+ this.inFlightUploads = /* @__PURE__ */ new Map();
10588
+ this.getUpload = (id) => this.uploads[id];
10589
+ /**
10590
+ * Clears all upload records.
10591
+ * Invoked when the user disconnects so a later session does not inherit stale upload state.
10592
+ * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
10593
+ */
10594
+ this.reset = () => {
10595
+ for (const { abortController } of this.inFlightUploads.values()) {
10596
+ abortController.abort();
10597
+ }
10598
+ this.inFlightUploads.clear();
10599
+ this.state.next(initState7());
10600
+ };
10601
+ /**
10602
+ * Removes the upload record for `id` if present.
10603
+ * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
10604
+ */
10605
+ this.deleteUploadRecord = (id) => {
10606
+ const flight = this.inFlightUploads.get(id);
10607
+ if (flight) {
10608
+ this.inFlightUploads.delete(id);
10609
+ flight.abortController.abort();
10610
+ }
10611
+ this.state.next((current) => {
10612
+ if (!(id in current.uploads)) return current;
10613
+ const uploads = { ...current.uploads };
10614
+ delete uploads[id];
10615
+ return { ...current, uploads };
10616
+ });
10617
+ };
10618
+ /**
10619
+ * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
10620
+ * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
10621
+ * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
10622
+ */
10623
+ this.upload = ({
10624
+ id,
10625
+ channelCid,
10626
+ file
10627
+ }) => {
10628
+ const existing = this.inFlightUploads.get(id);
10629
+ if (existing) return existing.promise;
10630
+ let resolvePromise;
10631
+ let rejectPromise;
10632
+ const promise = new Promise((resolve, reject) => {
10633
+ resolvePromise = resolve;
10634
+ rejectPromise = reject;
10635
+ });
10636
+ const abortController = new AbortController();
10637
+ this.inFlightUploads.set(id, { promise, abortController });
10638
+ void (async () => {
10639
+ const attachmentManager = this.resolveAttachmentManager(channelCid);
10640
+ const trackProgress = attachmentManager.config.trackUploadProgress;
10641
+ try {
10642
+ this.upsertUpload({
10643
+ id,
10644
+ uploadProgress: trackProgress ? 0 : void 0
10645
+ });
10646
+ const onProgress = trackProgress ? (progress) => {
10647
+ this.updateUpload({
10648
+ id,
10649
+ uploadProgress: progress
10650
+ });
10651
+ } : void 0;
10652
+ const uploadRequestOptions = {
10653
+ abortSignal: abortController.signal,
10654
+ ...onProgress ? { onProgress } : {}
10655
+ };
10656
+ const response = await attachmentManager.doUploadRequest(
10657
+ file,
10658
+ uploadRequestOptions
10659
+ );
10660
+ resolvePromise(response);
10661
+ } catch (error) {
10662
+ rejectPromise(error);
10663
+ } finally {
10664
+ this.inFlightUploads.delete(id);
10665
+ this.deleteUploadRecord(id);
10666
+ }
10667
+ })();
10668
+ return promise;
10669
+ };
10670
+ this.upsertUpload = (record) => {
10671
+ this.state.partialNext({
10672
+ uploads: upsertById(this.uploads, record)
10673
+ });
10674
+ };
10675
+ this.updateUpload = (record) => {
10676
+ this.state.next((current) => {
10677
+ const nextUploads = updateById(current.uploads, record);
10678
+ if (!nextUploads) return current;
10679
+ return { ...current, uploads: nextUploads };
10680
+ });
10681
+ };
10682
+ this.state = new StateStore(initState7());
10683
+ }
10684
+ resolveAttachmentManager(channelCid) {
10685
+ const colon = channelCid.indexOf(":");
10686
+ if (colon <= 0 || colon === channelCid.length - 1) {
10687
+ throw new Error(`Invalid channelCid: ${channelCid}`);
10688
+ }
10689
+ const channelType = channelCid.slice(0, colon);
10690
+ const channelId = channelCid.slice(colon + 1);
10691
+ return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
10692
+ }
10693
+ get uploads() {
10694
+ return this.state.getLatestValue().uploads;
10695
+ }
10696
+ };
10697
+
10581
10698
  // src/signing.ts
10582
10699
  var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
10583
10700
  var import_crypto = __toESM(require("crypto"));
@@ -13050,6 +13167,7 @@ var StreamChat = class _StreamChat {
13050
13167
  this.activeChannels = {};
13051
13168
  this.state = new ClientState({ client: this });
13052
13169
  this.threads.resetState();
13170
+ this.uploadManager.reset();
13053
13171
  closePromise.finally(() => {
13054
13172
  this.tokenManager.reset();
13055
13173
  }).catch((err) => console.error(err));
@@ -13456,6 +13574,7 @@ var StreamChat = class _StreamChat {
13456
13574
  this.blockedUsers = new StateStore({ userIds: [] });
13457
13575
  this.moderation = new Moderation(this);
13458
13576
  this.notifications = options?.notifications ?? new NotificationManager();
13577
+ this.uploadManager = new UploadManager(this);
13459
13578
  if (secretOrOptions && isString2(secretOrOptions)) {
13460
13579
  this.secret = secretOrOptions;
13461
13580
  }
@@ -15245,7 +15364,7 @@ var StreamChat = class _StreamChat {
15245
15364
  if (this.userAgent) {
15246
15365
  return this.userAgent;
15247
15366
  }
15248
- const version = "9.41.1";
15367
+ const version = "9.42.0";
15249
15368
  const clientBundle = "node-cjs";
15250
15369
  let userAgentString = "";
15251
15370
  if (this.sdkIdentifier) {
@@ -17935,6 +18054,7 @@ var FixedSizeQueueCache = class {
17935
18054
  ThreadManager,
17936
18055
  TokenManager,
17937
18056
  UPDATE_LIVE_LOCATION_REQUEST_MIN_THROTTLE_TIMEOUT,
18057
+ UploadManager,
17938
18058
  UserFromToken,
17939
18059
  UserSearchSource,
17940
18060
  VALID_MAX_VOTES_VALUE_REGEX,