stream-chat 9.39.0 → 9.41.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.
@@ -3402,12 +3402,18 @@ var _AttachmentManager = class _AttachmentManager {
3402
3402
  * Method to perform the default upload behavior without checking for custom upload functions
3403
3403
  * to prevent recursive calls
3404
3404
  */
3405
- this.doDefaultUploadRequest = async (fileLike) => {
3405
+ this.doDefaultUploadRequest = async (fileLike, options) => {
3406
+ const progressHandler = options?.onProgress ? (progressEvent) => {
3407
+ const percent = progressEvent.lengthComputable && progressEvent.total ? Math.round(progressEvent.loaded * 100 / progressEvent.total) : void 0;
3408
+ options.onProgress?.(percent);
3409
+ } : void 0;
3406
3410
  if (isFileReference(fileLike)) {
3407
3411
  return this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3408
3412
  fileLike.uri,
3409
3413
  fileLike.name,
3410
- fileLike.type
3414
+ fileLike.type,
3415
+ void 0,
3416
+ progressHandler ? { onUploadProgress: progressHandler } : void 0
3411
3417
  );
3412
3418
  }
3413
3419
  const file = isFile(fileLike) ? fileLike : createFileFromBlobs({
@@ -3415,18 +3421,24 @@ var _AttachmentManager = class _AttachmentManager {
3415
3421
  fileName: generateFileName(fileLike.type),
3416
3422
  mimeType: fileLike.type
3417
3423
  });
3418
- const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](file);
3424
+ const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3425
+ file,
3426
+ void 0,
3427
+ void 0,
3428
+ void 0,
3429
+ progressHandler ? { onUploadProgress: progressHandler } : void 0
3430
+ );
3419
3431
  return result;
3420
3432
  };
3421
3433
  /**
3422
3434
  * todo: docs how to customize the image and file upload by overriding do
3423
3435
  */
3424
- this.doUploadRequest = async (fileLike) => {
3436
+ this.doUploadRequest = async (fileLike, options) => {
3425
3437
  const customUploadFn = this.config.doUploadRequest;
3426
3438
  if (customUploadFn) {
3427
- return await customUploadFn(fileLike);
3439
+ return await customUploadFn(fileLike, options);
3428
3440
  }
3429
- return this.doDefaultUploadRequest(fileLike);
3441
+ return this.doDefaultUploadRequest(fileLike, options);
3430
3442
  };
3431
3443
  // @deprecated use attachmentManager.uploadFile(file)
3432
3444
  this.uploadAttachment = async (attachment) => {
@@ -3450,25 +3462,41 @@ var _AttachmentManager = class _AttachmentManager {
3450
3462
  });
3451
3463
  return localAttachment;
3452
3464
  }
3453
- this.upsertAttachments([
3454
- {
3455
- ...attachment,
3456
- localMetadata: {
3457
- ...attachment.localMetadata,
3458
- uploadState: "uploading"
3459
- }
3465
+ const shouldTrackProgress = this.config.trackUploadProgress;
3466
+ const uploadingAttachment = {
3467
+ ...attachment,
3468
+ localMetadata: {
3469
+ ...attachment.localMetadata,
3470
+ uploadState: "uploading",
3471
+ ...shouldTrackProgress && { uploadProgress: 0 }
3460
3472
  }
3461
- ]);
3473
+ };
3474
+ this.upsertAttachments([uploadingAttachment]);
3475
+ const uploadOptions = shouldTrackProgress ? {
3476
+ onProgress: (percent) => {
3477
+ this.updateAttachment({
3478
+ ...uploadingAttachment,
3479
+ localMetadata: {
3480
+ ...uploadingAttachment.localMetadata,
3481
+ uploadProgress: percent
3482
+ }
3483
+ });
3484
+ }
3485
+ } : void 0;
3462
3486
  let response;
3463
3487
  try {
3464
- response = await this.doUploadRequest(localAttachment.localMetadata.file);
3488
+ response = await this.doUploadRequest(
3489
+ localAttachment.localMetadata.file,
3490
+ uploadOptions
3491
+ );
3465
3492
  } catch (error) {
3466
3493
  const reason = error instanceof Error ? error.message : "unknown error";
3467
3494
  const failedAttachment = {
3468
3495
  ...attachment,
3469
3496
  localMetadata: {
3470
3497
  ...attachment.localMetadata,
3471
- uploadState: "failed"
3498
+ uploadState: "failed",
3499
+ uploadProgress: void 0
3472
3500
  }
3473
3501
  };
3474
3502
  this.client.notifications.addError({
@@ -3494,7 +3522,8 @@ var _AttachmentManager = class _AttachmentManager {
3494
3522
  ...attachment,
3495
3523
  localMetadata: {
3496
3524
  ...attachment.localMetadata,
3497
- uploadState: "finished"
3525
+ uploadState: "finished",
3526
+ uploadProgress: void 0
3498
3527
  }
3499
3528
  };
3500
3529
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3528,18 +3557,31 @@ var _AttachmentManager = class _AttachmentManager {
3528
3557
  this.upsertAttachments([attachment]);
3529
3558
  return preUpload.state.attachment;
3530
3559
  }
3560
+ const shouldTrackProgress = this.config.trackUploadProgress;
3531
3561
  attachment = {
3532
3562
  ...attachment,
3533
3563
  localMetadata: {
3534
3564
  ...attachment.localMetadata,
3535
- uploadState: "uploading"
3565
+ uploadState: "uploading",
3566
+ ...shouldTrackProgress && { uploadProgress: 0 }
3536
3567
  }
3537
3568
  };
3538
3569
  this.upsertAttachments([attachment]);
3570
+ const uploadOptions = shouldTrackProgress ? {
3571
+ onProgress: (percent) => {
3572
+ this.updateAttachment({
3573
+ ...attachment,
3574
+ localMetadata: {
3575
+ ...attachment.localMetadata,
3576
+ uploadProgress: percent
3577
+ }
3578
+ });
3579
+ }
3580
+ } : void 0;
3539
3581
  let response;
3540
3582
  let error;
3541
3583
  try {
3542
- response = await this.doUploadRequest(file);
3584
+ response = await this.doUploadRequest(file, uploadOptions);
3543
3585
  } catch (err) {
3544
3586
  error = err instanceof Error ? err : void 0;
3545
3587
  }
@@ -3550,7 +3592,8 @@ var _AttachmentManager = class _AttachmentManager {
3550
3592
  ...attachment,
3551
3593
  localMetadata: {
3552
3594
  ...attachment.localMetadata,
3553
- uploadState: error ? "failed" : "finished"
3595
+ uploadState: error ? "failed" : "finished",
3596
+ uploadProgress: void 0
3554
3597
  }
3555
3598
  },
3556
3599
  error,
@@ -3718,7 +3761,8 @@ var DEFAULT_ATTACHMENT_MANAGER_CONFIG = {
3718
3761
  acceptedFiles: [],
3719
3762
  // an empty array means all files are accepted
3720
3763
  fileUploadFilter: () => true,
3721
- maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE
3764
+ maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE,
3765
+ trackUploadProgress: true
3722
3766
  };
3723
3767
  var DEFAULT_TEXT_COMPOSER_CONFIG = {
3724
3768
  enabled: true,
@@ -8177,22 +8221,44 @@ var Channel = class {
8177
8221
  }
8178
8222
  return await this._sendMessage(message, options);
8179
8223
  }
8180
- sendFile(uri, name, contentType, user) {
8224
+ /**
8225
+ * Upload a file to this channel’s file endpoint (multipart). Forwards to the client’s `sendFile` implementation.
8226
+ *
8227
+ * @param uri File source: URL string, `File`, `Buffer`, or readable stream (Node).
8228
+ * @param name File name sent in the multipart body.
8229
+ * @param contentType MIME type; defaults are applied when omitted.
8230
+ * @param user Optional user payload appended to the form as JSON.
8231
+ * @param axiosRequestConfig Optional Axios per-request config, merged after upload defaults (e.g. `onUploadProgress`, `signal` from `AbortController`).
8232
+ * @return Promise resolving to `{ file: string, ... }` with the CDN URL.
8233
+ */
8234
+ sendFile(uri, name, contentType, user, axiosRequestConfig) {
8181
8235
  return this.getClient().sendFile(
8182
8236
  `${this._channelURL()}/file`,
8183
8237
  uri,
8184
8238
  name,
8185
8239
  contentType,
8186
- user
8240
+ user,
8241
+ axiosRequestConfig
8187
8242
  );
8188
8243
  }
8189
- sendImage(uri, name, contentType, user) {
8244
+ /**
8245
+ * Upload an image to this channel’s image endpoint (multipart). Uses the same transport as `sendFile`.
8246
+ *
8247
+ * @param uri Image source: URL string, `File`, or readable stream (Node). For `Buffer` uploads, use `sendFile` toward the channel file endpoint instead.
8248
+ * @param name File name sent in the multipart body.
8249
+ * @param contentType MIME type.
8250
+ * @param user Optional user payload appended to the form as JSON.
8251
+ * @param axiosRequestConfig Optional Axios per-request config, merged after upload defaults (e.g. `onUploadProgress`, `signal`).
8252
+ * @return Promise resolving to `{ file: string, ... }` with the CDN URL.
8253
+ */
8254
+ sendImage(uri, name, contentType, user, axiosRequestConfig) {
8190
8255
  return this.getClient().sendFile(
8191
8256
  `${this._channelURL()}/image`,
8192
8257
  uri,
8193
8258
  name,
8194
8259
  contentType,
8195
- user
8260
+ user,
8261
+ axiosRequestConfig
8196
8262
  );
8197
8263
  }
8198
8264
  deleteFile(url) {
@@ -12755,6 +12821,29 @@ var _ReminderManager = class _ReminderManager extends WithSubscriptions {
12755
12821
  _ReminderManager.isReminderWsEventPayload = (event) => !!event.reminder && (event.type.startsWith("reminder.") || event.type === "notification.reminder_due");
12756
12822
  var ReminderManager = _ReminderManager;
12757
12823
 
12824
+ // src/offline-support/util.ts
12825
+ var isLocalUrl = (value) => !!value && !value.startsWith("http");
12826
+ var isAttachmentReplayable = (attachment) => {
12827
+ if (!attachment || typeof attachment !== "object") {
12828
+ return true;
12829
+ }
12830
+ return !isLocalUrl(attachment.asset_url) && !isLocalUrl(attachment.image_url);
12831
+ };
12832
+ var isMessageUpdateReplayable = (message) => !message.attachments?.some((attachment) => !isAttachmentReplayable(attachment));
12833
+ var getPendingTaskChannelData = (cid) => {
12834
+ if (!cid) {
12835
+ return {};
12836
+ }
12837
+ const separatorIndex = cid.indexOf(":");
12838
+ if (separatorIndex <= 0 || separatorIndex === cid.length - 1) {
12839
+ return {};
12840
+ }
12841
+ return {
12842
+ channelId: cid.slice(separatorIndex + 1),
12843
+ channelType: cid.slice(0, separatorIndex)
12844
+ };
12845
+ };
12846
+
12758
12847
  // src/client.ts
12759
12848
  function isString2(x) {
12760
12849
  return typeof x === "string" || x instanceof String;
@@ -13710,7 +13799,7 @@ var StreamChat = class _StreamChat {
13710
13799
  delete(url, params) {
13711
13800
  return this.doAxiosRequest("delete", url, null, { params });
13712
13801
  }
13713
- sendFile(url, uri, name, contentType, user) {
13802
+ sendFile(url, uri, name, contentType, user, axiosRequestConfig) {
13714
13803
  const data = addFileToFormData(uri, name, contentType || "multipart/form-data");
13715
13804
  if (user != null) data.append("user", JSON.stringify(user));
13716
13805
  return this.doAxiosRequest("postForm", url, data, {
@@ -13719,7 +13808,8 @@ var StreamChat = class _StreamChat {
13719
13808
  config: {
13720
13809
  timeout: 0,
13721
13810
  maxContentLength: Infinity,
13722
- maxBodyLength: Infinity
13811
+ maxBodyLength: Infinity,
13812
+ ...axiosRequestConfig
13723
13813
  }
13724
13814
  });
13725
13815
  }
@@ -14847,6 +14937,30 @@ var StreamChat = class _StreamChat {
14847
14937
  * @return {{ message: LocalMessage | MessageResponse }} Response that includes the message
14848
14938
  */
14849
14939
  async updateMessage(message, partialUserOrUserId, options) {
14940
+ if (!message.id) {
14941
+ throw Error("Please specify the message.id when calling updateMessage");
14942
+ }
14943
+ const messageId = message.id;
14944
+ try {
14945
+ if (this.offlineDb) {
14946
+ return await this.offlineDb.queueTask({
14947
+ task: {
14948
+ ...getPendingTaskChannelData(message.cid),
14949
+ messageId,
14950
+ payload: [message, partialUserOrUserId, options],
14951
+ type: "update-message"
14952
+ }
14953
+ });
14954
+ }
14955
+ } catch (error) {
14956
+ this.logger("error", `offlineDb:updateMessage`, {
14957
+ tags: ["channel", "offlineDb"],
14958
+ error
14959
+ });
14960
+ }
14961
+ return await this._updateMessage(message, partialUserOrUserId, options);
14962
+ }
14963
+ async _updateMessage(message, partialUserOrUserId, options) {
14850
14964
  if (!message.id) {
14851
14965
  throw Error("Please specify the message.id when calling updateMessage");
14852
14966
  }
@@ -15124,7 +15238,7 @@ var StreamChat = class _StreamChat {
15124
15238
  if (this.userAgent) {
15125
15239
  return this.userAgent;
15126
15240
  }
15127
- const version = "9.39.0";
15241
+ const version = "9.41.0";
15128
15242
  const clientBundle = "browser-cjs";
15129
15243
  let userAgentString = "";
15130
15244
  if (this.sdkIdentifier) {
@@ -16262,11 +16376,19 @@ var StreamChat = class _StreamChat {
16262
16376
  * @param {string} [name] The name of the file
16263
16377
  * @param {string} [contentType] The content type of the file
16264
16378
  * @param {UserResponse} [user] Optional user information
16379
+ * @param {AxiosRequestConfig} [axiosRequestConfig] Optional axios config (e.g. onUploadProgress for progress tracking)
16265
16380
  *
16266
16381
  * @return {Promise<SendFileAPIResponse>} Response containing the file URL
16267
16382
  */
16268
- uploadFile(uri, name, contentType, user) {
16269
- return this.sendFile(`${this.baseURL}/uploads/file`, uri, name, contentType, user);
16383
+ uploadFile(uri, name, contentType, user, axiosRequestConfig) {
16384
+ return this.sendFile(
16385
+ `${this.baseURL}/uploads/file`,
16386
+ uri,
16387
+ name,
16388
+ contentType,
16389
+ user,
16390
+ axiosRequestConfig
16391
+ );
16270
16392
  }
16271
16393
  /**
16272
16394
  * uploadImage - Uploads an image to the configured storage (defaults to Stream CDN)
@@ -16275,11 +16397,19 @@ var StreamChat = class _StreamChat {
16275
16397
  * @param {string} [name] The name of the image
16276
16398
  * @param {string} [contentType] The content type of the image
16277
16399
  * @param {UserResponse} [user] Optional user information
16400
+ * @param {AxiosRequestConfig} [axiosRequestConfig] Optional axios config (e.g. onUploadProgress for progress tracking)
16278
16401
  *
16279
16402
  * @return {Promise<SendFileAPIResponse>} Response containing the image URL
16280
16403
  */
16281
- uploadImage(uri, name, contentType, user) {
16282
- return this.sendFile(`${this.baseURL}/uploads/image`, uri, name, contentType, user);
16404
+ uploadImage(uri, name, contentType, user, axiosRequestConfig) {
16405
+ return this.sendFile(
16406
+ `${this.baseURL}/uploads/image`,
16407
+ uri,
16408
+ name,
16409
+ contentType,
16410
+ user,
16411
+ axiosRequestConfig
16412
+ );
16283
16413
  }
16284
16414
  /**
16285
16415
  * deleteFile - Deletes a file from the configured storage
@@ -17280,7 +17410,7 @@ var AbstractOfflineDB = class {
17280
17410
  return await attemptTaskExecution();
17281
17411
  } catch (e) {
17282
17412
  if (!this.shouldSkipQueueingTask(e)) {
17283
- await this.addPendingTask(task);
17413
+ await this.handleAddPendingTask({ task });
17284
17414
  }
17285
17415
  throw e;
17286
17416
  }
@@ -17293,19 +17423,92 @@ var AbstractOfflineDB = class {
17293
17423
  * @param error
17294
17424
  */
17295
17425
  this.shouldSkipQueueingTask = (error) => error?.response?.data?.code === 4 || error?.response?.data?.code === 17;
17426
+ this.mergeFailedMessageUpdateIntoPendingSendMessage = ({
17427
+ editedMessage,
17428
+ pendingMessage
17429
+ }) => {
17430
+ const normalizedEditedMessageSource = {
17431
+ ...editedMessage
17432
+ };
17433
+ if (editedMessage.status === "failed") {
17434
+ delete normalizedEditedMessageSource.message_text_updated_at;
17435
+ }
17436
+ const normalizedEditedMessage = localMessageToNewMessagePayload(
17437
+ normalizedEditedMessageSource
17438
+ );
17439
+ const pendingMessageStatus = pendingMessage.status;
17440
+ return {
17441
+ ...pendingMessage,
17442
+ ...normalizedEditedMessage,
17443
+ ...typeof pendingMessageStatus !== "undefined" ? { status: pendingMessageStatus } : {}
17444
+ };
17445
+ };
17446
+ this.isPendingSendMessageTask = (task) => task.type === "send-message";
17447
+ this.handleOfflineFailedUpdateMessagePendingTask = async (task) => {
17448
+ const [message] = task.payload;
17449
+ if (!message.id) {
17450
+ return;
17451
+ }
17452
+ const pendingTasks = await this.getPendingTasks({ messageId: message.id });
17453
+ const pendingSendMessageTask = pendingTasks.find(this.isPendingSendMessageTask);
17454
+ if (!pendingSendMessageTask) {
17455
+ return;
17456
+ }
17457
+ const updatedPendingSendMessage = this.mergeFailedMessageUpdateIntoPendingSendMessage(
17458
+ {
17459
+ editedMessage: message,
17460
+ pendingMessage: pendingSendMessageTask.payload[0]
17461
+ }
17462
+ );
17463
+ const updatedPendingTask = {
17464
+ ...pendingSendMessageTask,
17465
+ payload: [updatedPendingSendMessage, pendingSendMessageTask.payload[1]]
17466
+ };
17467
+ if (pendingSendMessageTask.id) {
17468
+ await this.updatePendingTask({
17469
+ id: pendingSendMessageTask.id,
17470
+ task: updatedPendingTask
17471
+ });
17472
+ return;
17473
+ }
17474
+ await this.addPendingTask({
17475
+ ...updatedPendingTask,
17476
+ id: void 0
17477
+ });
17478
+ };
17479
+ /**
17480
+ * Central ingress for persisting pending tasks. It either stores the task as-is
17481
+ * or rewrites an existing pending `send-message` task for offline edits of failed messages.
17482
+ */
17483
+ this.handleAddPendingTask = async ({ task }) => {
17484
+ if (task.type === "update-message" && !isMessageUpdateReplayable(task.payload[0])) {
17485
+ return;
17486
+ }
17487
+ if (task.type === "update-message" && !this.client.wsConnection?.isHealthy && task.payload[0].status === "failed") {
17488
+ await this.handleOfflineFailedUpdateMessagePendingTask(task);
17489
+ return;
17490
+ }
17491
+ await this.addPendingTask(task);
17492
+ };
17296
17493
  /**
17297
17494
  * Executes a task from the list of supported pending tasks. Currently supported pending tasks
17298
17495
  * are:
17496
+ * - Updating a message
17299
17497
  * - Deleting a message
17300
17498
  * - Sending a reaction
17301
17499
  * - Removing a reaction
17302
17500
  * - Sending a message
17501
+ * - Creating a draft
17502
+ * - Deleting a draft
17303
17503
  * It will throw if we try to execute a pending task that is not supported.
17304
17504
  * @param task - The task we want to execute
17305
17505
  * @param isPendingTask - a control value telling us if it's an actual pending task being executed
17306
17506
  * or delayed execution
17307
17507
  */
17308
17508
  this.executeTask = async ({ task }, isPendingTask = false) => {
17509
+ if (task.type === "update-message") {
17510
+ return await this.client._updateMessage(...task.payload);
17511
+ }
17309
17512
  if (task.type === "delete-message") {
17310
17513
  return await this.client._deleteMessage(...task.payload);
17311
17514
  }