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.
@@ -3213,12 +3213,18 @@ var _AttachmentManager = class _AttachmentManager {
3213
3213
  * Method to perform the default upload behavior without checking for custom upload functions
3214
3214
  * to prevent recursive calls
3215
3215
  */
3216
- this.doDefaultUploadRequest = async (fileLike) => {
3216
+ this.doDefaultUploadRequest = async (fileLike, options) => {
3217
+ const progressHandler = options?.onProgress ? (progressEvent) => {
3218
+ const percent = progressEvent.lengthComputable && progressEvent.total ? Math.round(progressEvent.loaded * 100 / progressEvent.total) : void 0;
3219
+ options.onProgress?.(percent);
3220
+ } : void 0;
3217
3221
  if (isFileReference(fileLike)) {
3218
3222
  return this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3219
3223
  fileLike.uri,
3220
3224
  fileLike.name,
3221
- fileLike.type
3225
+ fileLike.type,
3226
+ void 0,
3227
+ progressHandler ? { onUploadProgress: progressHandler } : void 0
3222
3228
  );
3223
3229
  }
3224
3230
  const file = isFile(fileLike) ? fileLike : createFileFromBlobs({
@@ -3226,18 +3232,24 @@ var _AttachmentManager = class _AttachmentManager {
3226
3232
  fileName: generateFileName(fileLike.type),
3227
3233
  mimeType: fileLike.type
3228
3234
  });
3229
- const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](file);
3235
+ const { duration, ...result } = await this.channel[isImageFile(fileLike) ? "sendImage" : "sendFile"](
3236
+ file,
3237
+ void 0,
3238
+ void 0,
3239
+ void 0,
3240
+ progressHandler ? { onUploadProgress: progressHandler } : void 0
3241
+ );
3230
3242
  return result;
3231
3243
  };
3232
3244
  /**
3233
3245
  * todo: docs how to customize the image and file upload by overriding do
3234
3246
  */
3235
- this.doUploadRequest = async (fileLike) => {
3247
+ this.doUploadRequest = async (fileLike, options) => {
3236
3248
  const customUploadFn = this.config.doUploadRequest;
3237
3249
  if (customUploadFn) {
3238
- return await customUploadFn(fileLike);
3250
+ return await customUploadFn(fileLike, options);
3239
3251
  }
3240
- return this.doDefaultUploadRequest(fileLike);
3252
+ return this.doDefaultUploadRequest(fileLike, options);
3241
3253
  };
3242
3254
  // @deprecated use attachmentManager.uploadFile(file)
3243
3255
  this.uploadAttachment = async (attachment) => {
@@ -3261,25 +3273,41 @@ var _AttachmentManager = class _AttachmentManager {
3261
3273
  });
3262
3274
  return localAttachment;
3263
3275
  }
3264
- this.upsertAttachments([
3265
- {
3266
- ...attachment,
3267
- localMetadata: {
3268
- ...attachment.localMetadata,
3269
- uploadState: "uploading"
3270
- }
3276
+ const shouldTrackProgress = this.config.trackUploadProgress;
3277
+ const uploadingAttachment = {
3278
+ ...attachment,
3279
+ localMetadata: {
3280
+ ...attachment.localMetadata,
3281
+ uploadState: "uploading",
3282
+ ...shouldTrackProgress && { uploadProgress: 0 }
3271
3283
  }
3272
- ]);
3284
+ };
3285
+ this.upsertAttachments([uploadingAttachment]);
3286
+ const uploadOptions = shouldTrackProgress ? {
3287
+ onProgress: (percent) => {
3288
+ this.updateAttachment({
3289
+ ...uploadingAttachment,
3290
+ localMetadata: {
3291
+ ...uploadingAttachment.localMetadata,
3292
+ uploadProgress: percent
3293
+ }
3294
+ });
3295
+ }
3296
+ } : void 0;
3273
3297
  let response;
3274
3298
  try {
3275
- response = await this.doUploadRequest(localAttachment.localMetadata.file);
3299
+ response = await this.doUploadRequest(
3300
+ localAttachment.localMetadata.file,
3301
+ uploadOptions
3302
+ );
3276
3303
  } catch (error) {
3277
3304
  const reason = error instanceof Error ? error.message : "unknown error";
3278
3305
  const failedAttachment = {
3279
3306
  ...attachment,
3280
3307
  localMetadata: {
3281
3308
  ...attachment.localMetadata,
3282
- uploadState: "failed"
3309
+ uploadState: "failed",
3310
+ uploadProgress: void 0
3283
3311
  }
3284
3312
  };
3285
3313
  this.client.notifications.addError({
@@ -3305,7 +3333,8 @@ var _AttachmentManager = class _AttachmentManager {
3305
3333
  ...attachment,
3306
3334
  localMetadata: {
3307
3335
  ...attachment.localMetadata,
3308
- uploadState: "finished"
3336
+ uploadState: "finished",
3337
+ uploadProgress: void 0
3309
3338
  }
3310
3339
  };
3311
3340
  const previewUri = uploadedAttachment.localMetadata.previewUri;
@@ -3339,18 +3368,31 @@ var _AttachmentManager = class _AttachmentManager {
3339
3368
  this.upsertAttachments([attachment]);
3340
3369
  return preUpload.state.attachment;
3341
3370
  }
3371
+ const shouldTrackProgress = this.config.trackUploadProgress;
3342
3372
  attachment = {
3343
3373
  ...attachment,
3344
3374
  localMetadata: {
3345
3375
  ...attachment.localMetadata,
3346
- uploadState: "uploading"
3376
+ uploadState: "uploading",
3377
+ ...shouldTrackProgress && { uploadProgress: 0 }
3347
3378
  }
3348
3379
  };
3349
3380
  this.upsertAttachments([attachment]);
3381
+ const uploadOptions = shouldTrackProgress ? {
3382
+ onProgress: (percent) => {
3383
+ this.updateAttachment({
3384
+ ...attachment,
3385
+ localMetadata: {
3386
+ ...attachment.localMetadata,
3387
+ uploadProgress: percent
3388
+ }
3389
+ });
3390
+ }
3391
+ } : void 0;
3350
3392
  let response;
3351
3393
  let error;
3352
3394
  try {
3353
- response = await this.doUploadRequest(file);
3395
+ response = await this.doUploadRequest(file, uploadOptions);
3354
3396
  } catch (err) {
3355
3397
  error = err instanceof Error ? err : void 0;
3356
3398
  }
@@ -3361,7 +3403,8 @@ var _AttachmentManager = class _AttachmentManager {
3361
3403
  ...attachment,
3362
3404
  localMetadata: {
3363
3405
  ...attachment.localMetadata,
3364
- uploadState: error ? "failed" : "finished"
3406
+ uploadState: error ? "failed" : "finished",
3407
+ uploadProgress: void 0
3365
3408
  }
3366
3409
  },
3367
3410
  error,
@@ -3529,7 +3572,8 @@ var DEFAULT_ATTACHMENT_MANAGER_CONFIG = {
3529
3572
  acceptedFiles: [],
3530
3573
  // an empty array means all files are accepted
3531
3574
  fileUploadFilter: () => true,
3532
- maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE
3575
+ maxNumberOfFilesPerMessage: API_MAX_FILES_ALLOWED_PER_MESSAGE,
3576
+ trackUploadProgress: true
3533
3577
  };
3534
3578
  var DEFAULT_TEXT_COMPOSER_CONFIG = {
3535
3579
  enabled: true,
@@ -7988,22 +8032,44 @@ var Channel = class {
7988
8032
  }
7989
8033
  return await this._sendMessage(message, options);
7990
8034
  }
7991
- sendFile(uri, name, contentType, user) {
8035
+ /**
8036
+ * Upload a file to this channel’s file endpoint (multipart). Forwards to the client’s `sendFile` implementation.
8037
+ *
8038
+ * @param uri File source: URL string, `File`, `Buffer`, or readable stream (Node).
8039
+ * @param name File name sent in the multipart body.
8040
+ * @param contentType MIME type; defaults are applied when omitted.
8041
+ * @param user Optional user payload appended to the form as JSON.
8042
+ * @param axiosRequestConfig Optional Axios per-request config, merged after upload defaults (e.g. `onUploadProgress`, `signal` from `AbortController`).
8043
+ * @return Promise resolving to `{ file: string, ... }` with the CDN URL.
8044
+ */
8045
+ sendFile(uri, name, contentType, user, axiosRequestConfig) {
7992
8046
  return this.getClient().sendFile(
7993
8047
  `${this._channelURL()}/file`,
7994
8048
  uri,
7995
8049
  name,
7996
8050
  contentType,
7997
- user
8051
+ user,
8052
+ axiosRequestConfig
7998
8053
  );
7999
8054
  }
8000
- sendImage(uri, name, contentType, user) {
8055
+ /**
8056
+ * Upload an image to this channel’s image endpoint (multipart). Uses the same transport as `sendFile`.
8057
+ *
8058
+ * @param uri Image source: URL string, `File`, or readable stream (Node). For `Buffer` uploads, use `sendFile` toward the channel file endpoint instead.
8059
+ * @param name File name sent in the multipart body.
8060
+ * @param contentType MIME type.
8061
+ * @param user Optional user payload appended to the form as JSON.
8062
+ * @param axiosRequestConfig Optional Axios per-request config, merged after upload defaults (e.g. `onUploadProgress`, `signal`).
8063
+ * @return Promise resolving to `{ file: string, ... }` with the CDN URL.
8064
+ */
8065
+ sendImage(uri, name, contentType, user, axiosRequestConfig) {
8001
8066
  return this.getClient().sendFile(
8002
8067
  `${this._channelURL()}/image`,
8003
8068
  uri,
8004
8069
  name,
8005
8070
  contentType,
8006
- user
8071
+ user,
8072
+ axiosRequestConfig
8007
8073
  );
8008
8074
  }
8009
8075
  deleteFile(url) {
@@ -12566,6 +12632,29 @@ var _ReminderManager = class _ReminderManager extends WithSubscriptions {
12566
12632
  _ReminderManager.isReminderWsEventPayload = (event) => !!event.reminder && (event.type.startsWith("reminder.") || event.type === "notification.reminder_due");
12567
12633
  var ReminderManager = _ReminderManager;
12568
12634
 
12635
+ // src/offline-support/util.ts
12636
+ var isLocalUrl = (value) => !!value && !value.startsWith("http");
12637
+ var isAttachmentReplayable = (attachment) => {
12638
+ if (!attachment || typeof attachment !== "object") {
12639
+ return true;
12640
+ }
12641
+ return !isLocalUrl(attachment.asset_url) && !isLocalUrl(attachment.image_url);
12642
+ };
12643
+ var isMessageUpdateReplayable = (message) => !message.attachments?.some((attachment) => !isAttachmentReplayable(attachment));
12644
+ var getPendingTaskChannelData = (cid) => {
12645
+ if (!cid) {
12646
+ return {};
12647
+ }
12648
+ const separatorIndex = cid.indexOf(":");
12649
+ if (separatorIndex <= 0 || separatorIndex === cid.length - 1) {
12650
+ return {};
12651
+ }
12652
+ return {
12653
+ channelId: cid.slice(separatorIndex + 1),
12654
+ channelType: cid.slice(0, separatorIndex)
12655
+ };
12656
+ };
12657
+
12569
12658
  // src/client.ts
12570
12659
  function isString2(x) {
12571
12660
  return typeof x === "string" || x instanceof String;
@@ -13521,7 +13610,7 @@ var StreamChat = class _StreamChat {
13521
13610
  delete(url, params) {
13522
13611
  return this.doAxiosRequest("delete", url, null, { params });
13523
13612
  }
13524
- sendFile(url, uri, name, contentType, user) {
13613
+ sendFile(url, uri, name, contentType, user, axiosRequestConfig) {
13525
13614
  const data = addFileToFormData(uri, name, contentType || "multipart/form-data");
13526
13615
  if (user != null) data.append("user", JSON.stringify(user));
13527
13616
  return this.doAxiosRequest("postForm", url, data, {
@@ -13530,7 +13619,8 @@ var StreamChat = class _StreamChat {
13530
13619
  config: {
13531
13620
  timeout: 0,
13532
13621
  maxContentLength: Infinity,
13533
- maxBodyLength: Infinity
13622
+ maxBodyLength: Infinity,
13623
+ ...axiosRequestConfig
13534
13624
  }
13535
13625
  });
13536
13626
  }
@@ -14658,6 +14748,30 @@ var StreamChat = class _StreamChat {
14658
14748
  * @return {{ message: LocalMessage | MessageResponse }} Response that includes the message
14659
14749
  */
14660
14750
  async updateMessage(message, partialUserOrUserId, options) {
14751
+ if (!message.id) {
14752
+ throw Error("Please specify the message.id when calling updateMessage");
14753
+ }
14754
+ const messageId = message.id;
14755
+ try {
14756
+ if (this.offlineDb) {
14757
+ return await this.offlineDb.queueTask({
14758
+ task: {
14759
+ ...getPendingTaskChannelData(message.cid),
14760
+ messageId,
14761
+ payload: [message, partialUserOrUserId, options],
14762
+ type: "update-message"
14763
+ }
14764
+ });
14765
+ }
14766
+ } catch (error) {
14767
+ this.logger("error", `offlineDb:updateMessage`, {
14768
+ tags: ["channel", "offlineDb"],
14769
+ error
14770
+ });
14771
+ }
14772
+ return await this._updateMessage(message, partialUserOrUserId, options);
14773
+ }
14774
+ async _updateMessage(message, partialUserOrUserId, options) {
14661
14775
  if (!message.id) {
14662
14776
  throw Error("Please specify the message.id when calling updateMessage");
14663
14777
  }
@@ -14935,7 +15049,7 @@ var StreamChat = class _StreamChat {
14935
15049
  if (this.userAgent) {
14936
15050
  return this.userAgent;
14937
15051
  }
14938
- const version = "9.39.0";
15052
+ const version = "9.41.0";
14939
15053
  const clientBundle = "browser-esm";
14940
15054
  let userAgentString = "";
14941
15055
  if (this.sdkIdentifier) {
@@ -16073,11 +16187,19 @@ var StreamChat = class _StreamChat {
16073
16187
  * @param {string} [name] The name of the file
16074
16188
  * @param {string} [contentType] The content type of the file
16075
16189
  * @param {UserResponse} [user] Optional user information
16190
+ * @param {AxiosRequestConfig} [axiosRequestConfig] Optional axios config (e.g. onUploadProgress for progress tracking)
16076
16191
  *
16077
16192
  * @return {Promise<SendFileAPIResponse>} Response containing the file URL
16078
16193
  */
16079
- uploadFile(uri, name, contentType, user) {
16080
- return this.sendFile(`${this.baseURL}/uploads/file`, uri, name, contentType, user);
16194
+ uploadFile(uri, name, contentType, user, axiosRequestConfig) {
16195
+ return this.sendFile(
16196
+ `${this.baseURL}/uploads/file`,
16197
+ uri,
16198
+ name,
16199
+ contentType,
16200
+ user,
16201
+ axiosRequestConfig
16202
+ );
16081
16203
  }
16082
16204
  /**
16083
16205
  * uploadImage - Uploads an image to the configured storage (defaults to Stream CDN)
@@ -16086,11 +16208,19 @@ var StreamChat = class _StreamChat {
16086
16208
  * @param {string} [name] The name of the image
16087
16209
  * @param {string} [contentType] The content type of the image
16088
16210
  * @param {UserResponse} [user] Optional user information
16211
+ * @param {AxiosRequestConfig} [axiosRequestConfig] Optional axios config (e.g. onUploadProgress for progress tracking)
16089
16212
  *
16090
16213
  * @return {Promise<SendFileAPIResponse>} Response containing the image URL
16091
16214
  */
16092
- uploadImage(uri, name, contentType, user) {
16093
- return this.sendFile(`${this.baseURL}/uploads/image`, uri, name, contentType, user);
16215
+ uploadImage(uri, name, contentType, user, axiosRequestConfig) {
16216
+ return this.sendFile(
16217
+ `${this.baseURL}/uploads/image`,
16218
+ uri,
16219
+ name,
16220
+ contentType,
16221
+ user,
16222
+ axiosRequestConfig
16223
+ );
16094
16224
  }
16095
16225
  /**
16096
16226
  * deleteFile - Deletes a file from the configured storage
@@ -17091,7 +17221,7 @@ var AbstractOfflineDB = class {
17091
17221
  return await attemptTaskExecution();
17092
17222
  } catch (e) {
17093
17223
  if (!this.shouldSkipQueueingTask(e)) {
17094
- await this.addPendingTask(task);
17224
+ await this.handleAddPendingTask({ task });
17095
17225
  }
17096
17226
  throw e;
17097
17227
  }
@@ -17104,19 +17234,92 @@ var AbstractOfflineDB = class {
17104
17234
  * @param error
17105
17235
  */
17106
17236
  this.shouldSkipQueueingTask = (error) => error?.response?.data?.code === 4 || error?.response?.data?.code === 17;
17237
+ this.mergeFailedMessageUpdateIntoPendingSendMessage = ({
17238
+ editedMessage,
17239
+ pendingMessage
17240
+ }) => {
17241
+ const normalizedEditedMessageSource = {
17242
+ ...editedMessage
17243
+ };
17244
+ if (editedMessage.status === "failed") {
17245
+ delete normalizedEditedMessageSource.message_text_updated_at;
17246
+ }
17247
+ const normalizedEditedMessage = localMessageToNewMessagePayload(
17248
+ normalizedEditedMessageSource
17249
+ );
17250
+ const pendingMessageStatus = pendingMessage.status;
17251
+ return {
17252
+ ...pendingMessage,
17253
+ ...normalizedEditedMessage,
17254
+ ...typeof pendingMessageStatus !== "undefined" ? { status: pendingMessageStatus } : {}
17255
+ };
17256
+ };
17257
+ this.isPendingSendMessageTask = (task) => task.type === "send-message";
17258
+ this.handleOfflineFailedUpdateMessagePendingTask = async (task) => {
17259
+ const [message] = task.payload;
17260
+ if (!message.id) {
17261
+ return;
17262
+ }
17263
+ const pendingTasks = await this.getPendingTasks({ messageId: message.id });
17264
+ const pendingSendMessageTask = pendingTasks.find(this.isPendingSendMessageTask);
17265
+ if (!pendingSendMessageTask) {
17266
+ return;
17267
+ }
17268
+ const updatedPendingSendMessage = this.mergeFailedMessageUpdateIntoPendingSendMessage(
17269
+ {
17270
+ editedMessage: message,
17271
+ pendingMessage: pendingSendMessageTask.payload[0]
17272
+ }
17273
+ );
17274
+ const updatedPendingTask = {
17275
+ ...pendingSendMessageTask,
17276
+ payload: [updatedPendingSendMessage, pendingSendMessageTask.payload[1]]
17277
+ };
17278
+ if (pendingSendMessageTask.id) {
17279
+ await this.updatePendingTask({
17280
+ id: pendingSendMessageTask.id,
17281
+ task: updatedPendingTask
17282
+ });
17283
+ return;
17284
+ }
17285
+ await this.addPendingTask({
17286
+ ...updatedPendingTask,
17287
+ id: void 0
17288
+ });
17289
+ };
17290
+ /**
17291
+ * Central ingress for persisting pending tasks. It either stores the task as-is
17292
+ * or rewrites an existing pending `send-message` task for offline edits of failed messages.
17293
+ */
17294
+ this.handleAddPendingTask = async ({ task }) => {
17295
+ if (task.type === "update-message" && !isMessageUpdateReplayable(task.payload[0])) {
17296
+ return;
17297
+ }
17298
+ if (task.type === "update-message" && !this.client.wsConnection?.isHealthy && task.payload[0].status === "failed") {
17299
+ await this.handleOfflineFailedUpdateMessagePendingTask(task);
17300
+ return;
17301
+ }
17302
+ await this.addPendingTask(task);
17303
+ };
17107
17304
  /**
17108
17305
  * Executes a task from the list of supported pending tasks. Currently supported pending tasks
17109
17306
  * are:
17307
+ * - Updating a message
17110
17308
  * - Deleting a message
17111
17309
  * - Sending a reaction
17112
17310
  * - Removing a reaction
17113
17311
  * - Sending a message
17312
+ * - Creating a draft
17313
+ * - Deleting a draft
17114
17314
  * It will throw if we try to execute a pending task that is not supported.
17115
17315
  * @param task - The task we want to execute
17116
17316
  * @param isPendingTask - a control value telling us if it's an actual pending task being executed
17117
17317
  * or delayed execution
17118
17318
  */
17119
17319
  this.executeTask = async ({ task }, isPendingTask = false) => {
17320
+ if (task.type === "update-message") {
17321
+ return await this.client._updateMessage(...task.payload);
17322
+ }
17120
17323
  if (task.type === "delete-message") {
17121
17324
  return await this.client._deleteMessage(...task.payload);
17122
17325
  }