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.
@@ -3381,12 +3381,18 @@ var _AttachmentManager = class _AttachmentManager {
3381
3381
  * Method to perform the default upload behavior without checking for custom upload functions
3382
3382
  * to prevent recursive calls
3383
3383
  */
3384
- this.doDefaultUploadRequest = async (fileLike) => {
3384
+ this.doDefaultUploadRequest = async (fileLike, options) => {
3385
+ const progressHandler = options?.onProgress ? (progressEvent) => {
3386
+ const percent = progressEvent.lengthComputable && progressEvent.total ? Math.round(progressEvent.loaded * 100 / progressEvent.total) : void 0;
3387
+ options.onProgress?.(percent);
3388
+ } : void 0;
3385
3389
  if (isFileReference(fileLike)) {
3386
3390
  return this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3387
3391
  fileLike.uri,
3388
3392
  fileLike.name,
3389
- fileLike.type
3393
+ fileLike.type,
3394
+ void 0,
3395
+ progressHandler ? { onUploadProgress: progressHandler } : void 0
3390
3396
  );
3391
3397
  }
3392
3398
  const file = isFile(fileLike) ? fileLike : createFileFromBlobs({
@@ -3394,18 +3400,24 @@ var _AttachmentManager = class _AttachmentManager {
3394
3400
  fileName: generateFileName(fileLike.type),
3395
3401
  mimeType: fileLike.type
3396
3402
  });
3397
- const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](file);
3403
+ const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3404
+ file,
3405
+ void 0,
3406
+ void 0,
3407
+ void 0,
3408
+ progressHandler ? { onUploadProgress: progressHandler } : void 0
3409
+ );
3398
3410
  return result;
3399
3411
  };
3400
3412
  /**
3401
3413
  * todo: docs how to customize the image and file upload by overriding do
3402
3414
  */
3403
- this.doUploadRequest = async (fileLike) => {
3415
+ this.doUploadRequest = async (fileLike, options) => {
3404
3416
  const customUploadFn = this.config.doUploadRequest;
3405
3417
  if (customUploadFn) {
3406
- return await customUploadFn(fileLike);
3418
+ return await customUploadFn(fileLike, options);
3407
3419
  }
3408
- return this.doDefaultUploadRequest(fileLike);
3420
+ return this.doDefaultUploadRequest(fileLike, options);
3409
3421
  };
3410
3422
  // @deprecated use attachmentManager.uploadFile(file)
3411
3423
  this.uploadAttachment = async (attachment) => {
@@ -3429,25 +3441,41 @@ var _AttachmentManager = class _AttachmentManager {
3429
3441
  });
3430
3442
  return localAttachment;
3431
3443
  }
3432
- this.upsertAttachments([
3433
- {
3434
- ...attachment,
3435
- localMetadata: {
3436
- ...attachment.localMetadata,
3437
- uploadState: "uploading"
3438
- }
3444
+ const shouldTrackProgress = this.config.trackUploadProgress;
3445
+ const uploadingAttachment = {
3446
+ ...attachment,
3447
+ localMetadata: {
3448
+ ...attachment.localMetadata,
3449
+ uploadState: "uploading",
3450
+ ...shouldTrackProgress && { uploadProgress: 0 }
3439
3451
  }
3440
- ]);
3452
+ };
3453
+ this.upsertAttachments([uploadingAttachment]);
3454
+ const uploadOptions = shouldTrackProgress ? {
3455
+ onProgress: (percent) => {
3456
+ this.updateAttachment({
3457
+ ...uploadingAttachment,
3458
+ localMetadata: {
3459
+ ...uploadingAttachment.localMetadata,
3460
+ uploadProgress: percent
3461
+ }
3462
+ });
3463
+ }
3464
+ } : void 0;
3441
3465
  let response;
3442
3466
  try {
3443
- response = await this.doUploadRequest(localAttachment.localMetadata.file);
3467
+ response = await this.doUploadRequest(
3468
+ localAttachment.localMetadata.file,
3469
+ uploadOptions
3470
+ );
3444
3471
  } catch (error) {
3445
3472
  const reason = error instanceof Error ? error.message : "unknown error";
3446
3473
  const failedAttachment = {
3447
3474
  ...attachment,
3448
3475
  localMetadata: {
3449
3476
  ...attachment.localMetadata,
3450
- uploadState: "failed"
3477
+ uploadState: "failed",
3478
+ uploadProgress: void 0
3451
3479
  }
3452
3480
  };
3453
3481
  this.client.notifications.addError({
@@ -3473,7 +3501,8 @@ var _AttachmentManager = class _AttachmentManager {
3473
3501
  ...attachment,
3474
3502
  localMetadata: {
3475
3503
  ...attachment.localMetadata,
3476
- uploadState: "finished"
3504
+ uploadState: "finished",
3505
+ uploadProgress: void 0
3477
3506
  }
3478
3507
  };
3479
3508
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3507,18 +3536,31 @@ var _AttachmentManager = class _AttachmentManager {
3507
3536
  this.upsertAttachments([attachment]);
3508
3537
  return preUpload.state.attachment;
3509
3538
  }
3539
+ const shouldTrackProgress = this.config.trackUploadProgress;
3510
3540
  attachment = {
3511
3541
  ...attachment,
3512
3542
  localMetadata: {
3513
3543
  ...attachment.localMetadata,
3514
- uploadState: "uploading"
3544
+ uploadState: "uploading",
3545
+ ...shouldTrackProgress && { uploadProgress: 0 }
3515
3546
  }
3516
3547
  };
3517
3548
  this.upsertAttachments([attachment]);
3549
+ const uploadOptions = shouldTrackProgress ? {
3550
+ onProgress: (percent) => {
3551
+ this.updateAttachment({
3552
+ ...attachment,
3553
+ localMetadata: {
3554
+ ...attachment.localMetadata,
3555
+ uploadProgress: percent
3556
+ }
3557
+ });
3558
+ }
3559
+ } : void 0;
3518
3560
  let response;
3519
3561
  let error;
3520
3562
  try {
3521
- response = await this.doUploadRequest(file);
3563
+ response = await this.doUploadRequest(file, uploadOptions);
3522
3564
  } catch (err) {
3523
3565
  error = err instanceof Error ? err : void 0;
3524
3566
  }
@@ -3529,7 +3571,8 @@ var _AttachmentManager = class _AttachmentManager {
3529
3571
  ...attachment,
3530
3572
  localMetadata: {
3531
3573
  ...attachment.localMetadata,
3532
- uploadState: error ? "failed" : "finished"
3574
+ uploadState: error ? "failed" : "finished",
3575
+ uploadProgress: void 0
3533
3576
  }
3534
3577
  },
3535
3578
  error,
@@ -3697,7 +3740,8 @@ var DEFAULT_ATTACHMENT_MANAGER_CONFIG = {
3697
3740
  acceptedFiles: [],
3698
3741
  // an empty array means all files are accepted
3699
3742
  fileUploadFilter: () => true,
3700
- maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE
3743
+ maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE,
3744
+ trackUploadProgress: true
3701
3745
  };
3702
3746
  var DEFAULT_TEXT_COMPOSER_CONFIG = {
3703
3747
  enabled: true,
@@ -8156,22 +8200,44 @@ var Channel = class {
8156
8200
  }
8157
8201
  return await this._sendMessage(message, options);
8158
8202
  }
8159
- sendFile(uri, name, contentType, user) {
8203
+ /**
8204
+ * Upload a file to this channel’s file endpoint (multipart). Forwards to the client’s `sendFile` implementation.
8205
+ *
8206
+ * @param uri File source: URL string, `File`, `Buffer`, or readable stream (Node).
8207
+ * @param name File name sent in the multipart body.
8208
+ * @param contentType MIME type; defaults are applied when omitted.
8209
+ * @param user Optional user payload appended to the form as JSON.
8210
+ * @param axiosRequestConfig Optional Axios per-request config, merged after upload defaults (e.g. `onUploadProgress`, `signal` from `AbortController`).
8211
+ * @return Promise resolving to `{ file: string, ... }` with the CDN URL.
8212
+ */
8213
+ sendFile(uri, name, contentType, user, axiosRequestConfig) {
8160
8214
  return this.getClient().sendFile(
8161
8215
  `${this._channelURL()}/file`,
8162
8216
  uri,
8163
8217
  name,
8164
8218
  contentType,
8165
- user
8219
+ user,
8220
+ axiosRequestConfig
8166
8221
  );
8167
8222
  }
8168
- sendImage(uri, name, contentType, user) {
8223
+ /**
8224
+ * Upload an image to this channel’s image endpoint (multipart). Uses the same transport as `sendFile`.
8225
+ *
8226
+ * @param uri Image source: URL string, `File`, or readable stream (Node). For `Buffer` uploads, use `sendFile` toward the channel file endpoint instead.
8227
+ * @param name File name sent in the multipart body.
8228
+ * @param contentType MIME type.
8229
+ * @param user Optional user payload appended to the form as JSON.
8230
+ * @param axiosRequestConfig Optional Axios per-request config, merged after upload defaults (e.g. `onUploadProgress`, `signal`).
8231
+ * @return Promise resolving to `{ file: string, ... }` with the CDN URL.
8232
+ */
8233
+ sendImage(uri, name, contentType, user, axiosRequestConfig) {
8169
8234
  return this.getClient().sendFile(
8170
8235
  `${this._channelURL()}/image`,
8171
8236
  uri,
8172
8237
  name,
8173
8238
  contentType,
8174
- user
8239
+ user,
8240
+ axiosRequestConfig
8175
8241
  );
8176
8242
  }
8177
8243
  deleteFile(url) {
@@ -12734,6 +12800,29 @@ var _ReminderManager = class _ReminderManager extends WithSubscriptions {
12734
12800
  _ReminderManager.isReminderWsEventPayload = (event) => !!event.reminder && (event.type.startsWith("reminder.") || event.type === "notification.reminder_due");
12735
12801
  var ReminderManager = _ReminderManager;
12736
12802
 
12803
+ // src/offline-support/util.ts
12804
+ var isLocalUrl = (value) => !!value && !value.startsWith("http");
12805
+ var isAttachmentReplayable = (attachment) => {
12806
+ if (!attachment || typeof attachment !== "object") {
12807
+ return true;
12808
+ }
12809
+ return !isLocalUrl(attachment.asset_url) && !isLocalUrl(attachment.image_url);
12810
+ };
12811
+ var isMessageUpdateReplayable = (message) => !message.attachments?.some((attachment) => !isAttachmentReplayable(attachment));
12812
+ var getPendingTaskChannelData = (cid) => {
12813
+ if (!cid) {
12814
+ return {};
12815
+ }
12816
+ const separatorIndex = cid.indexOf(":");
12817
+ if (separatorIndex <= 0 || separatorIndex === cid.length - 1) {
12818
+ return {};
12819
+ }
12820
+ return {
12821
+ channelId: cid.slice(separatorIndex + 1),
12822
+ channelType: cid.slice(0, separatorIndex)
12823
+ };
12824
+ };
12825
+
12737
12826
  // src/client.ts
12738
12827
  function isString2(x) {
12739
12828
  return typeof x === "string" || x instanceof String;
@@ -13689,7 +13778,7 @@ var StreamChat = class _StreamChat {
13689
13778
  delete(url, params) {
13690
13779
  return this.doAxiosRequest("delete", url, null, { params });
13691
13780
  }
13692
- sendFile(url, uri, name, contentType, user) {
13781
+ sendFile(url, uri, name, contentType, user, axiosRequestConfig) {
13693
13782
  const data = addFileToFormData(uri, name, contentType || "multipart/form-data");
13694
13783
  if (user != null) data.append("user", JSON.stringify(user));
13695
13784
  return this.doAxiosRequest("postForm", url, data, {
@@ -13698,7 +13787,8 @@ var StreamChat = class _StreamChat {
13698
13787
  config: {
13699
13788
  timeout: 0,
13700
13789
  maxContentLength: Infinity,
13701
- maxBodyLength: Infinity
13790
+ maxBodyLength: Infinity,
13791
+ ...axiosRequestConfig
13702
13792
  }
13703
13793
  });
13704
13794
  }
@@ -14826,6 +14916,30 @@ var StreamChat = class _StreamChat {
14826
14916
  * @return {{ message: LocalMessage | MessageResponse }} Response that includes the message
14827
14917
  */
14828
14918
  async updateMessage(message, partialUserOrUserId, options) {
14919
+ if (!message.id) {
14920
+ throw Error("Please specify the message.id when calling updateMessage");
14921
+ }
14922
+ const messageId = message.id;
14923
+ try {
14924
+ if (this.offlineDb) {
14925
+ return await this.offlineDb.queueTask({
14926
+ task: {
14927
+ ...getPendingTaskChannelData(message.cid),
14928
+ messageId,
14929
+ payload: [message, partialUserOrUserId, options],
14930
+ type: "update-message"
14931
+ }
14932
+ });
14933
+ }
14934
+ } catch (error) {
14935
+ this.logger("error", `offlineDb:updateMessage`, {
14936
+ tags: ["channel", "offlineDb"],
14937
+ error
14938
+ });
14939
+ }
14940
+ return await this._updateMessage(message, partialUserOrUserId, options);
14941
+ }
14942
+ async _updateMessage(message, partialUserOrUserId, options) {
14829
14943
  if (!message.id) {
14830
14944
  throw Error("Please specify the message.id when calling updateMessage");
14831
14945
  }
@@ -15103,7 +15217,7 @@ var StreamChat = class _StreamChat {
15103
15217
  if (this.userAgent) {
15104
15218
  return this.userAgent;
15105
15219
  }
15106
- const version = "9.39.0";
15220
+ const version = "9.41.0";
15107
15221
  const clientBundle = "node-cjs";
15108
15222
  let userAgentString = "";
15109
15223
  if (this.sdkIdentifier) {
@@ -16241,11 +16355,19 @@ var StreamChat = class _StreamChat {
16241
16355
  * @param {string} [name] The name of the file
16242
16356
  * @param {string} [contentType] The content type of the file
16243
16357
  * @param {UserResponse} [user] Optional user information
16358
+ * @param {AxiosRequestConfig} [axiosRequestConfig] Optional axios config (e.g. onUploadProgress for progress tracking)
16244
16359
  *
16245
16360
  * @return {Promise<SendFileAPIResponse>} Response containing the file URL
16246
16361
  */
16247
- uploadFile(uri, name, contentType, user) {
16248
- return this.sendFile(`${this.baseURL}/uploads/file`, uri, name, contentType, user);
16362
+ uploadFile(uri, name, contentType, user, axiosRequestConfig) {
16363
+ return this.sendFile(
16364
+ `${this.baseURL}/uploads/file`,
16365
+ uri,
16366
+ name,
16367
+ contentType,
16368
+ user,
16369
+ axiosRequestConfig
16370
+ );
16249
16371
  }
16250
16372
  /**
16251
16373
  * uploadImage - Uploads an image to the configured storage (defaults to Stream CDN)
@@ -16254,11 +16376,19 @@ var StreamChat = class _StreamChat {
16254
16376
  * @param {string} [name] The name of the image
16255
16377
  * @param {string} [contentType] The content type of the image
16256
16378
  * @param {UserResponse} [user] Optional user information
16379
+ * @param {AxiosRequestConfig} [axiosRequestConfig] Optional axios config (e.g. onUploadProgress for progress tracking)
16257
16380
  *
16258
16381
  * @return {Promise<SendFileAPIResponse>} Response containing the image URL
16259
16382
  */
16260
- uploadImage(uri, name, contentType, user) {
16261
- return this.sendFile(`${this.baseURL}/uploads/image`, uri, name, contentType, user);
16383
+ uploadImage(uri, name, contentType, user, axiosRequestConfig) {
16384
+ return this.sendFile(
16385
+ `${this.baseURL}/uploads/image`,
16386
+ uri,
16387
+ name,
16388
+ contentType,
16389
+ user,
16390
+ axiosRequestConfig
16391
+ );
16262
16392
  }
16263
16393
  /**
16264
16394
  * deleteFile - Deletes a file from the configured storage
@@ -17259,7 +17389,7 @@ var AbstractOfflineDB = class {
17259
17389
  return await attemptTaskExecution();
17260
17390
  } catch (e) {
17261
17391
  if (!this.shouldSkipQueueingTask(e)) {
17262
- await this.addPendingTask(task);
17392
+ await this.handleAddPendingTask({ task });
17263
17393
  }
17264
17394
  throw e;
17265
17395
  }
@@ -17272,19 +17402,92 @@ var AbstractOfflineDB = class {
17272
17402
  * @param error
17273
17403
  */
17274
17404
  this.shouldSkipQueueingTask = (error) => error?.response?.data?.code === 4 || error?.response?.data?.code === 17;
17405
+ this.mergeFailedMessageUpdateIntoPendingSendMessage = ({
17406
+ editedMessage,
17407
+ pendingMessage
17408
+ }) => {
17409
+ const normalizedEditedMessageSource = {
17410
+ ...editedMessage
17411
+ };
17412
+ if (editedMessage.status === "failed") {
17413
+ delete normalizedEditedMessageSource.message_text_updated_at;
17414
+ }
17415
+ const normalizedEditedMessage = localMessageToNewMessagePayload(
17416
+ normalizedEditedMessageSource
17417
+ );
17418
+ const pendingMessageStatus = pendingMessage.status;
17419
+ return {
17420
+ ...pendingMessage,
17421
+ ...normalizedEditedMessage,
17422
+ ...typeof pendingMessageStatus !== "undefined" ? { status: pendingMessageStatus } : {}
17423
+ };
17424
+ };
17425
+ this.isPendingSendMessageTask = (task) => task.type === "send-message";
17426
+ this.handleOfflineFailedUpdateMessagePendingTask = async (task) => {
17427
+ const [message] = task.payload;
17428
+ if (!message.id) {
17429
+ return;
17430
+ }
17431
+ const pendingTasks = await this.getPendingTasks({ messageId: message.id });
17432
+ const pendingSendMessageTask = pendingTasks.find(this.isPendingSendMessageTask);
17433
+ if (!pendingSendMessageTask) {
17434
+ return;
17435
+ }
17436
+ const updatedPendingSendMessage = this.mergeFailedMessageUpdateIntoPendingSendMessage(
17437
+ {
17438
+ editedMessage: message,
17439
+ pendingMessage: pendingSendMessageTask.payload[0]
17440
+ }
17441
+ );
17442
+ const updatedPendingTask = {
17443
+ ...pendingSendMessageTask,
17444
+ payload: [updatedPendingSendMessage, pendingSendMessageTask.payload[1]]
17445
+ };
17446
+ if (pendingSendMessageTask.id) {
17447
+ await this.updatePendingTask({
17448
+ id: pendingSendMessageTask.id,
17449
+ task: updatedPendingTask
17450
+ });
17451
+ return;
17452
+ }
17453
+ await this.addPendingTask({
17454
+ ...updatedPendingTask,
17455
+ id: void 0
17456
+ });
17457
+ };
17458
+ /**
17459
+ * Central ingress for persisting pending tasks. It either stores the task as-is
17460
+ * or rewrites an existing pending `send-message` task for offline edits of failed messages.
17461
+ */
17462
+ this.handleAddPendingTask = async ({ task }) => {
17463
+ if (task.type === "update-message" && !isMessageUpdateReplayable(task.payload[0])) {
17464
+ return;
17465
+ }
17466
+ if (task.type === "update-message" && !this.client.wsConnection?.isHealthy && task.payload[0].status === "failed") {
17467
+ await this.handleOfflineFailedUpdateMessagePendingTask(task);
17468
+ return;
17469
+ }
17470
+ await this.addPendingTask(task);
17471
+ };
17275
17472
  /**
17276
17473
  * Executes a task from the list of supported pending tasks. Currently supported pending tasks
17277
17474
  * are:
17475
+ * - Updating a message
17278
17476
  * - Deleting a message
17279
17477
  * - Sending a reaction
17280
17478
  * - Removing a reaction
17281
17479
  * - Sending a message
17480
+ * - Creating a draft
17481
+ * - Deleting a draft
17282
17482
  * It will throw if we try to execute a pending task that is not supported.
17283
17483
  * @param task - The task we want to execute
17284
17484
  * @param isPendingTask - a control value telling us if it's an actual pending task being executed
17285
17485
  * or delayed execution
17286
17486
  */
17287
17487
  this.executeTask = async ({ task }, isPendingTask = false) => {
17488
+ if (task.type === "update-message") {
17489
+ return await this.client._updateMessage(...task.payload);
17490
+ }
17288
17491
  if (task.type === "delete-message") {
17289
17492
  return await this.client._deleteMessage(...task.payload);
17290
17493
  }