stream-chat 9.41.1 → 9.42.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.
@@ -138,6 +138,7 @@ __export(index_exports, {
138
138
  ThreadManager: () => ThreadManager,
139
139
  TokenManager: () => TokenManager,
140
140
  UPDATE_LIVE_LOCATION_REQUEST_MIN_THROTTLE_TIMEOUT: () => UPDATE_LIVE_LOCATION_REQUEST_MIN_THROTTLE_TIMEOUT,
141
+ UploadManager: () => UploadManager,
141
142
  UserFromToken: () => UserFromToken,
142
143
  UserSearchSource: () => UserSearchSource,
143
144
  VALID_MAX_VOTES_VALUE_REGEX: () => VALID_MAX_VOTES_VALUE_REGEX,
@@ -3299,11 +3300,11 @@ var _AttachmentManager = class _AttachmentManager {
3299
3300
  return attachments;
3300
3301
  }
3301
3302
  }
3302
- return null;
3303
+ return stateAttachments;
3303
3304
  };
3304
3305
  this.updateAttachment = (attachmentToUpdate) => {
3305
3306
  const updatedAttachments = this.prepareAttachmentUpdate(attachmentToUpdate);
3306
- if (updatedAttachments) {
3307
+ if (updatedAttachments && updatedAttachments !== this.attachments) {
3307
3308
  this.state.partialNext({ attachments: updatedAttachments });
3308
3309
  }
3309
3310
  };
@@ -3313,15 +3314,15 @@ var _AttachmentManager = class _AttachmentManager {
3313
3314
  let hasUpdates = false;
3314
3315
  attachmentsToUpsert.forEach((attachment) => {
3315
3316
  const updatedAttachments = this.prepareAttachmentUpdate(attachment);
3316
- if (updatedAttachments) {
3317
- attachments = updatedAttachments;
3318
- hasUpdates = true;
3319
- } else {
3317
+ if (updatedAttachments === null) {
3320
3318
  const localAttachment = ensureIsLocalAttachment(attachment);
3321
3319
  if (localAttachment) {
3322
3320
  attachments.push(localAttachment);
3323
3321
  hasUpdates = true;
3324
3322
  }
3323
+ } else if (updatedAttachments !== this.attachments) {
3324
+ attachments = updatedAttachments;
3325
+ hasUpdates = true;
3325
3326
  }
3326
3327
  });
3327
3328
  if (hasUpdates) {
@@ -3329,11 +3330,15 @@ var _AttachmentManager = class _AttachmentManager {
3329
3330
  }
3330
3331
  };
3331
3332
  this.removeAttachments = (localAttachmentIds) => {
3333
+ if (!localAttachmentIds.length) return;
3332
3334
  this.state.partialNext({
3333
3335
  attachments: this.attachments.filter(
3334
3336
  (attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id)
3335
3337
  )
3336
3338
  });
3339
+ for (const id of localAttachmentIds) {
3340
+ this.client.uploadManager.deleteUploadRecord(id);
3341
+ }
3337
3342
  };
3338
3343
  this.getUploadConfigCheck = async (fileLike) => {
3339
3344
  const client = this.channel.getClient();
@@ -3422,13 +3427,17 @@ var _AttachmentManager = class _AttachmentManager {
3422
3427
  const percent = progressEvent.lengthComputable && progressEvent.total ? Math.round(progressEvent.loaded * 100 / progressEvent.total) : void 0;
3423
3428
  options.onProgress?.(percent);
3424
3429
  } : void 0;
3430
+ const axiosUploadConfig = progressHandler || options?.abortSignal ? {
3431
+ ...progressHandler ? { onUploadProgress: progressHandler } : {},
3432
+ ...options?.abortSignal ? { signal: options.abortSignal } : {}
3433
+ } : void 0;
3425
3434
  if (isFileReference(fileLike)) {
3426
3435
  return this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3427
3436
  fileLike.uri,
3428
3437
  fileLike.name,
3429
3438
  fileLike.type,
3430
3439
  void 0,
3431
- progressHandler ? { onUploadProgress: progressHandler } : void 0
3440
+ axiosUploadConfig
3432
3441
  );
3433
3442
  }
3434
3443
  const file = isFile(fileLike) ? fileLike : createFileFromBlobs({
@@ -3436,13 +3445,7 @@ var _AttachmentManager = class _AttachmentManager {
3436
3445
  fileName: generateFileName(fileLike.type),
3437
3446
  mimeType: fileLike.type
3438
3447
  });
3439
- const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3440
- file,
3441
- void 0,
3442
- void 0,
3443
- void 0,
3444
- progressHandler ? { onUploadProgress: progressHandler } : void 0
3445
- );
3448
+ const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](file, void 0, void 0, void 0, axiosUploadConfig);
3446
3449
  return result;
3447
3450
  };
3448
3451
  /**
@@ -3477,33 +3480,9 @@ var _AttachmentManager = class _AttachmentManager {
3477
3480
  });
3478
3481
  return localAttachment;
3479
3482
  }
3480
- const shouldTrackProgress = this.config.trackUploadProgress;
3481
- const uploadingAttachment = {
3482
- ...attachment,
3483
- localMetadata: {
3484
- ...attachment.localMetadata,
3485
- uploadState: "uploading",
3486
- ...shouldTrackProgress && { uploadProgress: 0 }
3487
- }
3488
- };
3489
- this.upsertAttachments([uploadingAttachment]);
3490
- const uploadOptions = shouldTrackProgress ? {
3491
- onProgress: (percent) => {
3492
- this.updateAttachment({
3493
- ...uploadingAttachment,
3494
- localMetadata: {
3495
- ...uploadingAttachment.localMetadata,
3496
- uploadProgress: percent
3497
- }
3498
- });
3499
- }
3500
- } : void 0;
3501
3483
  let response;
3502
3484
  try {
3503
- response = await this.doUploadRequest(
3504
- localAttachment.localMetadata.file,
3505
- uploadOptions
3506
- );
3485
+ response = await this.upload(attachment);
3507
3486
  } catch (error) {
3508
3487
  const reason = error instanceof Error ? error.message : "unknown error";
3509
3488
  const failedAttachment = {
@@ -3572,31 +3551,10 @@ var _AttachmentManager = class _AttachmentManager {
3572
3551
  this.upsertAttachments([attachment]);
3573
3552
  return preUpload.state.attachment;
3574
3553
  }
3575
- const shouldTrackProgress = this.config.trackUploadProgress;
3576
- attachment = {
3577
- ...attachment,
3578
- localMetadata: {
3579
- ...attachment.localMetadata,
3580
- uploadState: "uploading",
3581
- ...shouldTrackProgress && { uploadProgress: 0 }
3582
- }
3583
- };
3584
- this.upsertAttachments([attachment]);
3585
- const uploadOptions = shouldTrackProgress ? {
3586
- onProgress: (percent) => {
3587
- this.updateAttachment({
3588
- ...attachment,
3589
- localMetadata: {
3590
- ...attachment.localMetadata,
3591
- uploadProgress: percent
3592
- }
3593
- });
3594
- }
3595
- } : void 0;
3596
3554
  let response;
3597
3555
  let error;
3598
3556
  try {
3599
- response = await this.doUploadRequest(file, uploadOptions);
3557
+ response = await this.upload(attachment);
3600
3558
  } catch (err) {
3601
3559
  error = err instanceof Error ? err : void 0;
3602
3560
  }
@@ -3723,6 +3681,40 @@ var _AttachmentManager = class _AttachmentManager {
3723
3681
  ({ localMetadata }) => localMetadata.uploadState === state
3724
3682
  );
3725
3683
  }
3684
+ upload(attachment) {
3685
+ const localId = attachment.localMetadata.id;
3686
+ this.upsertAttachments([
3687
+ {
3688
+ ...attachment,
3689
+ localMetadata: {
3690
+ ...attachment.localMetadata,
3691
+ uploadState: "uploading",
3692
+ uploadProgress: this.config.trackUploadProgress ? 0 : void 0
3693
+ }
3694
+ }
3695
+ ]);
3696
+ const unsubscribe = this.client.uploadManager.state.subscribeWithSelector(
3697
+ (s) => ({ upload: s.uploads[localId] }),
3698
+ ({ upload: nextUpload }) => {
3699
+ if (!nextUpload) return;
3700
+ this.updateAttachment({
3701
+ ...attachment,
3702
+ localMetadata: {
3703
+ ...attachment.localMetadata,
3704
+ uploadState: "uploading",
3705
+ uploadProgress: nextUpload.uploadProgress
3706
+ }
3707
+ });
3708
+ }
3709
+ );
3710
+ return this.client.uploadManager.upload({
3711
+ id: localId,
3712
+ channelCid: this.channel.cid,
3713
+ file: attachment.localMetadata.file
3714
+ }).finally(() => {
3715
+ unsubscribe();
3716
+ });
3717
+ }
3726
3718
  };
3727
3719
  _AttachmentManager.toLocalUploadAttachment = (fileLike) => {
3728
3720
  const file = isFileReference(fileLike) || isFile(fileLike) ? fileLike : createFileFromBlobs({
@@ -7575,9 +7567,6 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7575
7567
  return this.state.getLatestValue().showReplyInChannel;
7576
7568
  }
7577
7569
  get hasSendableData() {
7578
- if (this.client.offlineDb) {
7579
- return !this.textComposer.textIsEmpty || !!this.attachmentManager.attachments.length || !!this.pollId || !!this.locationComposer.validLocation;
7580
- }
7581
7570
  return !!(!this.attachmentManager.uploadsInProgressCount && (!this.textComposer.textIsEmpty || this.attachmentManager.successfulUploadsCount > 0) || this.pollId || !!this.locationComposer.validLocation);
7582
7571
  }
7583
7572
  get compositionIsEmpty() {
@@ -10599,6 +10588,131 @@ var StableWSConnection = class {
10599
10588
  }
10600
10589
  };
10601
10590
 
10591
+ // src/uploadManager.ts
10592
+ var initState7 = () => ({ uploads: {} });
10593
+ var upsertById = (uploads, record) => ({
10594
+ ...uploads,
10595
+ [record.id]: { ...uploads[record.id], ...record }
10596
+ });
10597
+ var updateById = (uploads, record) => {
10598
+ if (!(record.id in uploads)) return null;
10599
+ const current = uploads[record.id];
10600
+ return { ...uploads, [record.id]: { ...current, ...record } };
10601
+ };
10602
+ var UploadManager = class {
10603
+ constructor(client) {
10604
+ this.client = client;
10605
+ this.inFlightUploads = /* @__PURE__ */ new Map();
10606
+ this.getUpload = (id) => this.uploads[id];
10607
+ /**
10608
+ * Clears all upload records.
10609
+ * Invoked when the user disconnects so a later session does not inherit stale upload state.
10610
+ * Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
10611
+ */
10612
+ this.reset = () => {
10613
+ for (const { abortController } of this.inFlightUploads.values()) {
10614
+ abortController.abort();
10615
+ }
10616
+ this.inFlightUploads.clear();
10617
+ this.state.next(initState7());
10618
+ };
10619
+ /**
10620
+ * Removes the upload record for `id` if present.
10621
+ * If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
10622
+ */
10623
+ this.deleteUploadRecord = (id) => {
10624
+ const flight = this.inFlightUploads.get(id);
10625
+ if (flight) {
10626
+ this.inFlightUploads.delete(id);
10627
+ flight.abortController.abort();
10628
+ }
10629
+ this.state.next((current) => {
10630
+ if (!(id in current.uploads)) return current;
10631
+ const uploads = { ...current.uploads };
10632
+ delete uploads[id];
10633
+ return { ...current, uploads };
10634
+ });
10635
+ };
10636
+ /**
10637
+ * Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
10638
+ * Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
10639
+ * Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
10640
+ */
10641
+ this.upload = ({
10642
+ id,
10643
+ channelCid,
10644
+ file
10645
+ }) => {
10646
+ const existing = this.inFlightUploads.get(id);
10647
+ if (existing) return existing.promise;
10648
+ let resolvePromise;
10649
+ let rejectPromise;
10650
+ const promise = new Promise((resolve, reject) => {
10651
+ resolvePromise = resolve;
10652
+ rejectPromise = reject;
10653
+ });
10654
+ const abortController = new AbortController();
10655
+ this.inFlightUploads.set(id, { promise, abortController });
10656
+ void (async () => {
10657
+ const attachmentManager = this.resolveAttachmentManager(channelCid);
10658
+ const trackProgress = attachmentManager.config.trackUploadProgress;
10659
+ try {
10660
+ this.upsertUpload({
10661
+ id,
10662
+ uploadProgress: trackProgress ? 0 : void 0
10663
+ });
10664
+ const onProgress = trackProgress ? (progress) => {
10665
+ this.updateUpload({
10666
+ id,
10667
+ uploadProgress: progress
10668
+ });
10669
+ } : void 0;
10670
+ const uploadRequestOptions = {
10671
+ abortSignal: abortController.signal,
10672
+ ...onProgress ? { onProgress } : {}
10673
+ };
10674
+ const response = await attachmentManager.doUploadRequest(
10675
+ file,
10676
+ uploadRequestOptions
10677
+ );
10678
+ resolvePromise(response);
10679
+ } catch (error) {
10680
+ rejectPromise(error);
10681
+ } finally {
10682
+ this.inFlightUploads.delete(id);
10683
+ this.deleteUploadRecord(id);
10684
+ }
10685
+ })();
10686
+ return promise;
10687
+ };
10688
+ this.upsertUpload = (record) => {
10689
+ this.state.partialNext({
10690
+ uploads: upsertById(this.uploads, record)
10691
+ });
10692
+ };
10693
+ this.updateUpload = (record) => {
10694
+ this.state.next((current) => {
10695
+ const nextUploads = updateById(current.uploads, record);
10696
+ if (!nextUploads) return current;
10697
+ return { ...current, uploads: nextUploads };
10698
+ });
10699
+ };
10700
+ this.state = new StateStore(initState7());
10701
+ }
10702
+ resolveAttachmentManager(channelCid) {
10703
+ const colon = channelCid.indexOf(":");
10704
+ if (colon <= 0 || colon === channelCid.length - 1) {
10705
+ throw new Error(`Invalid channelCid: ${channelCid}`);
10706
+ }
10707
+ const channelType = channelCid.slice(0, colon);
10708
+ const channelId = channelCid.slice(colon + 1);
10709
+ return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
10710
+ }
10711
+ get uploads() {
10712
+ return this.state.getLatestValue().uploads;
10713
+ }
10714
+ };
10715
+
10602
10716
  // src/signing.ts
10603
10717
  var import_jsonwebtoken = __toESM(require_jsonwebtoken());
10604
10718
  var import_crypto = __toESM(require_crypto());
@@ -13071,6 +13185,7 @@ var StreamChat = class _StreamChat {
13071
13185
  this.activeChannels = {};
13072
13186
  this.state = new ClientState({ client: this });
13073
13187
  this.threads.resetState();
13188
+ this.uploadManager.reset();
13074
13189
  closePromise.finally(() => {
13075
13190
  this.tokenManager.reset();
13076
13191
  }).catch((err) => console.error(err));
@@ -13477,6 +13592,7 @@ var StreamChat = class _StreamChat {
13477
13592
  this.blockedUsers = new StateStore({ userIds: [] });
13478
13593
  this.moderation = new Moderation(this);
13479
13594
  this.notifications = options?.notifications ?? new NotificationManager();
13595
+ this.uploadManager = new UploadManager(this);
13480
13596
  if (secretOrOptions && isString2(secretOrOptions)) {
13481
13597
  this.secret = secretOrOptions;
13482
13598
  }
@@ -15266,7 +15382,7 @@ var StreamChat = class _StreamChat {
15266
15382
  if (this.userAgent) {
15267
15383
  return this.userAgent;
15268
15384
  }
15269
- const version = "9.41.1";
15385
+ const version = "9.42.1";
15270
15386
  const clientBundle = "browser-cjs";
15271
15387
  let userAgentString = "";
15272
15388
  if (this.sdkIdentifier) {