stream-chat 9.2.0-offline-support-beta.3 → 9.2.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.
@@ -2550,7 +2550,7 @@ var RESERVED_UPDATED_MESSAGE_FIELDS = {
2550
2550
  };
2551
2551
  var LOCAL_MESSAGE_FIELDS = { error: true };
2552
2552
  var DEFAULT_QUERY_CHANNELS_RETRY_COUNT = 3;
2553
- var DEFAULT_QUERY_CHANNELS_SECONDS_BETWEEN_RETRIES = 1;
2553
+ var DEFAULT_QUERY_CHANNELS_MS_BETWEEN_RETRIES = 1e3;
2554
2554
 
2555
2555
  // src/utils.ts
2556
2556
  function logChatPromiseExecution(promise, name) {
@@ -3254,16 +3254,17 @@ var promoteChannel = ({
3254
3254
  };
3255
3255
  var isDate2 = (value) => !!value.getTime;
3256
3256
  var isLocalMessage = (message) => isDate2(message.created_at);
3257
- var waitSeconds = (seconds) => new Promise((resolve) => {
3258
- setTimeout(resolve, seconds * 1e3);
3259
- });
3260
3257
  var runDetached = (callback, options) => {
3261
3258
  const { context, onSuccessCallback = () => void 0, onErrorCallback } = options ?? {};
3262
3259
  const defaultOnError = (error) => {
3263
3260
  console.log(`An error has occurred in context ${context}: ${error}`);
3264
3261
  };
3265
3262
  const onError = onErrorCallback ?? defaultOnError;
3266
- callback.then(onSuccessCallback).catch(onError);
3263
+ let promise = callback;
3264
+ if (onSuccessCallback) {
3265
+ promise = promise.then(onSuccessCallback);
3266
+ }
3267
+ promise.catch(onError);
3267
3268
  };
3268
3269
 
3269
3270
  // src/channel_state.ts
@@ -4548,7 +4549,7 @@ var AttachmentManager = class {
4548
4549
  } = uploadConfig;
4549
4550
  const sizeLimit = size_limit || DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;
4550
4551
  const mimeType = fileLike.type;
4551
- if (isFile2(fileLike)) {
4552
+ if (isFile2(fileLike) || isFileReference(fileLike)) {
4552
4553
  if (allowed_file_extensions?.length && !allowed_file_extensions.some(
4553
4554
  (ext) => fileLike.name.toLowerCase().endsWith(ext.toLowerCase())
4554
4555
  )) {
@@ -8394,10 +8395,12 @@ var Channel = class {
8394
8395
  type: reactionType,
8395
8396
  user_id: this.getClient().userID ?? user_id
8396
8397
  };
8397
- await offlineDb.deleteReaction({
8398
- message,
8399
- reaction
8400
- });
8398
+ if (message) {
8399
+ await offlineDb.deleteReaction({
8400
+ message,
8401
+ reaction
8402
+ });
8403
+ }
8401
8404
  return await offlineDb.queueTask({
8402
8405
  task: {
8403
8406
  channelId: this.id,
@@ -11748,7 +11751,7 @@ var ChannelManager = class extends WithSubscriptions {
11748
11751
  this.setOptions = (options = {}) => {
11749
11752
  this.options = { ...DEFAULT_CHANNEL_MANAGER_OPTIONS, ...options };
11750
11753
  };
11751
- this.queryChannelsRequest = async (payload, retryCount = 0) => {
11754
+ this.executeChannelsQuery = async (payload, retryCount = 0) => {
11752
11755
  const { filters, sort, options, stateOptions } = payload;
11753
11756
  const { offset, limit } = {
11754
11757
  ...DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS,
@@ -11792,8 +11795,8 @@ var ChannelManager = class extends WithSubscriptions {
11792
11795
  this.state.partialNext({ error: wrappedError });
11793
11796
  return;
11794
11797
  }
11795
- await waitSeconds(DEFAULT_QUERY_CHANNELS_SECONDS_BETWEEN_RETRIES);
11796
- return this.queryChannelsRequest(payload, retryCount + 1);
11798
+ await sleep(DEFAULT_QUERY_CHANNELS_MS_BETWEEN_RETRIES);
11799
+ return this.executeChannelsQuery(payload, retryCount + 1);
11797
11800
  }
11798
11801
  };
11799
11802
  this.queryChannels = async (filters, sort = [], options = {}, stateOptions = {}) => {
@@ -11801,10 +11804,12 @@ var ChannelManager = class extends WithSubscriptions {
11801
11804
  pagination: { isLoading, filters: filtersFromState },
11802
11805
  initialized
11803
11806
  } = this.state.getLatestValue();
11804
- if (isLoading && !this.options.abortInFlightQuery && JSON.stringify(filtersFromState) === JSON.stringify(filters)) {
11807
+ if (isLoading && !this.options.abortInFlightQuery && // TODO: Figure a proper way to either deeply compare these or
11808
+ // create hashes from each.
11809
+ JSON.stringify(filtersFromState) === JSON.stringify(filters)) {
11805
11810
  return;
11806
11811
  }
11807
- const queryChannelsRequestPayload = { filters, sort, options, stateOptions };
11812
+ const executeChannelsQueryPayload = { filters, sort, options, stateOptions };
11808
11813
  try {
11809
11814
  this.stateOptions = stateOptions;
11810
11815
  this.state.next((currentState) => ({
@@ -11839,13 +11844,13 @@ var ChannelManager = class extends WithSubscriptions {
11839
11844
  this.client.offlineDb.syncManager.scheduleSyncStatusChangeCallback(
11840
11845
  this.id,
11841
11846
  async () => {
11842
- await this.queryChannelsRequest(queryChannelsRequestPayload);
11847
+ await this.executeChannelsQuery(executeChannelsQueryPayload);
11843
11848
  }
11844
11849
  );
11845
11850
  return;
11846
11851
  }
11847
11852
  }
11848
- await this.queryChannelsRequest(queryChannelsRequestPayload);
11853
+ await this.executeChannelsQuery(executeChannelsQueryPayload);
11849
11854
  } catch (error) {
11850
11855
  this.client.logger("error", error.message);
11851
11856
  this.state.next((currentState) => ({
@@ -13188,13 +13193,11 @@ var StreamChat = class _StreamChat {
13188
13193
  if (event.type === "notification.mutes_updated" && event.me?.mutes) {
13189
13194
  this.mutedUsers = event.me.mutes;
13190
13195
  }
13191
- if (event.type === "notification.mark_read") {
13192
- if (event.unread_channels === 0) {
13193
- const activeChannelKeys = Object.keys(this.activeChannels);
13194
- activeChannelKeys.forEach(
13195
- (activeChannelKey) => this.activeChannels[activeChannelKey].state.unreadCount = 0
13196
- );
13197
- }
13196
+ if (event.type === "notification.mark_read" && event.unread_channels === 0) {
13197
+ const activeChannelKeys = Object.keys(this.activeChannels);
13198
+ activeChannelKeys.forEach(
13199
+ (activeChannelKey) => this.activeChannels[activeChannelKey].state.unreadCount = 0
13200
+ );
13198
13201
  }
13199
13202
  if ((event.type === "channel.deleted" || event.type === "notification.channel_deleted") && event.cid) {
13200
13203
  const { cid } = event;
@@ -14072,7 +14075,7 @@ var StreamChat = class _StreamChat {
14072
14075
  data
14073
14076
  );
14074
14077
  }
14075
- deleteChannelType(channelType) {
14078
+ DBDeleteChannelType(channelType) {
14076
14079
  return this.delete(
14077
14080
  this.baseURL + `/channeltypes/${encodeURIComponent(channelType)}`
14078
14081
  );
@@ -14413,7 +14416,7 @@ var StreamChat = class _StreamChat {
14413
14416
  if (this.userAgent) {
14414
14417
  return this.userAgent;
14415
14418
  }
14416
- const version = "9.2.0-offline-support-beta.3";
14419
+ const version = "9.2.0";
14417
14420
  const clientBundle = "browser-cjs";
14418
14421
  let userAgentString = "";
14419
14422
  if (this.sdkIdentifier) {
@@ -15589,7 +15592,147 @@ var BuiltinPermissions = {
15589
15592
  UseFrozenChannel: "Send messages and reactions to frozen channels"
15590
15593
  };
15591
15594
 
15592
- // src/offline_support_api.ts
15595
+ // src/offline-support/offline_sync_manager.ts
15596
+ var OfflineDBSyncManager = class {
15597
+ constructor({
15598
+ client,
15599
+ offlineDb
15600
+ }) {
15601
+ this.syncStatus = false;
15602
+ this.connectionChangedListener = null;
15603
+ this.syncStatusListeners = [];
15604
+ this.scheduledSyncStatusCallbacks = /* @__PURE__ */ new Map();
15605
+ /**
15606
+ * Initializes the sync manager. Should only be called once per session.
15607
+ *
15608
+ * Cleans up old listeners if re-initialized to avoid memory leaks.
15609
+ * Starts syncing immediately if already connected, otherwise waits for reconnection.
15610
+ */
15611
+ this.init = async () => {
15612
+ try {
15613
+ if (this.client.user?.id && this.client.wsConnection?.isHealthy) {
15614
+ await this.syncAndExecutePendingTasks();
15615
+ await this.invokeSyncStatusListeners(true);
15616
+ }
15617
+ if (this.connectionChangedListener) {
15618
+ this.connectionChangedListener.unsubscribe();
15619
+ }
15620
+ this.connectionChangedListener = this.client.on(
15621
+ "connection.changed",
15622
+ async (event) => {
15623
+ if (event.online) {
15624
+ await this.syncAndExecutePendingTasks();
15625
+ await this.invokeSyncStatusListeners(true);
15626
+ } else {
15627
+ await this.invokeSyncStatusListeners(false);
15628
+ }
15629
+ }
15630
+ );
15631
+ } catch (error) {
15632
+ console.log("Error in DBSyncManager.init: ", error);
15633
+ }
15634
+ };
15635
+ /**
15636
+ * Registers a listener that is called whenever the sync status changes.
15637
+ *
15638
+ * @param listener - A callback invoked with the new sync status (`true` or `false`).
15639
+ * @returns An object with an `unsubscribe` function to remove the listener.
15640
+ */
15641
+ this.onSyncStatusChange = (listener) => {
15642
+ this.syncStatusListeners.push(listener);
15643
+ return {
15644
+ unsubscribe: () => {
15645
+ this.syncStatusListeners = this.syncStatusListeners.filter(
15646
+ (el) => el !== listener
15647
+ );
15648
+ }
15649
+ };
15650
+ };
15651
+ /**
15652
+ * Schedules a one-time callback to be invoked after the next successful sync.
15653
+ *
15654
+ * @param tag - A unique key to identify and manage the callback.
15655
+ * @param callback - An async function to run after sync.
15656
+ */
15657
+ this.scheduleSyncStatusChangeCallback = (tag, callback) => {
15658
+ this.scheduledSyncStatusCallbacks.set(tag, callback);
15659
+ };
15660
+ /**
15661
+ * Invokes all registered sync status listeners and executes any scheduled sync callbacks.
15662
+ *
15663
+ * @param status - The new sync status (`true` or `false`).
15664
+ */
15665
+ this.invokeSyncStatusListeners = async (status) => {
15666
+ this.syncStatus = status;
15667
+ this.syncStatusListeners.forEach((l) => l(status));
15668
+ if (status) {
15669
+ const promises = Array.from(this.scheduledSyncStatusCallbacks.values()).map(
15670
+ (cb) => cb()
15671
+ );
15672
+ await Promise.all(promises);
15673
+ this.scheduledSyncStatusCallbacks.clear();
15674
+ }
15675
+ };
15676
+ /**
15677
+ * Performs synchronization with the Stream backend.
15678
+ *
15679
+ * This includes downloading events since the last sync, updating the local DB,
15680
+ * and handling sync failures (e.g., if syncing beyond the allowed retention window).
15681
+ */
15682
+ this.sync = async () => {
15683
+ if (!this.client?.user) {
15684
+ return;
15685
+ }
15686
+ try {
15687
+ const cids = await this.offlineDb.getAllChannelCids();
15688
+ if (cids.length === 0) {
15689
+ return;
15690
+ }
15691
+ const lastSyncedAt = await this.offlineDb.getLastSyncedAt({
15692
+ userId: this.client.user.id
15693
+ });
15694
+ if (lastSyncedAt) {
15695
+ const lastSyncedAtDate = new Date(lastSyncedAt);
15696
+ const nowDate = /* @__PURE__ */ new Date();
15697
+ const diff = Math.floor(
15698
+ (nowDate.getTime() - lastSyncedAtDate.getTime()) / (1e3 * 60 * 60 * 24)
15699
+ );
15700
+ if (diff > 30) {
15701
+ await this.offlineDb.resetDB();
15702
+ } else {
15703
+ const result = await this.client.sync(cids, lastSyncedAtDate.toISOString());
15704
+ const queryPromises = result.events.map(
15705
+ (event) => this.offlineDb.handleEvent({ event, execute: false })
15706
+ );
15707
+ const queriesArray = await Promise.all(queryPromises);
15708
+ const queries = queriesArray.flat();
15709
+ if (queries.length) {
15710
+ await this.offlineDb.executeSqlBatch(queries);
15711
+ }
15712
+ }
15713
+ }
15714
+ await this.offlineDb.upsertUserSyncStatus({
15715
+ userId: this.client.user.id,
15716
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toString()
15717
+ });
15718
+ } catch (e) {
15719
+ console.log("An error has occurred while syncing the DB.", e);
15720
+ await this.offlineDb.resetDB();
15721
+ }
15722
+ };
15723
+ /**
15724
+ * Executes any tasks that were queued while offline and then performs a sync.
15725
+ */
15726
+ this.syncAndExecutePendingTasks = async () => {
15727
+ await this.offlineDb.executePendingTasks();
15728
+ await this.sync();
15729
+ };
15730
+ this.client = client;
15731
+ this.offlineDb = offlineDb;
15732
+ }
15733
+ };
15734
+
15735
+ // src/offline-support/offline_support_api.ts
15593
15736
  var AbstractOfflineDB = class {
15594
15737
  constructor({ client }) {
15595
15738
  /**
@@ -16167,144 +16310,6 @@ var AbstractOfflineDB = class {
16167
16310
  return userId === userIdFromState && initialized;
16168
16311
  }
16169
16312
  };
16170
- var OfflineDBSyncManager = class {
16171
- constructor({
16172
- client,
16173
- offlineDb
16174
- }) {
16175
- this.syncStatus = false;
16176
- this.connectionChangedListener = null;
16177
- this.syncStatusListeners = [];
16178
- this.scheduledSyncStatusCallbacks = /* @__PURE__ */ new Map();
16179
- /**
16180
- * Initializes the sync manager. Should only be called once per session.
16181
- *
16182
- * Cleans up old listeners if re-initialized to avoid memory leaks.
16183
- * Starts syncing immediately if already connected, otherwise waits for reconnection.
16184
- */
16185
- this.init = async () => {
16186
- try {
16187
- if (this.client.user?.id && this.client.wsConnection?.isHealthy) {
16188
- await this.syncAndExecutePendingTasks();
16189
- await this.invokeSyncStatusListeners(true);
16190
- }
16191
- if (this.connectionChangedListener) {
16192
- this.connectionChangedListener.unsubscribe();
16193
- }
16194
- this.connectionChangedListener = this.client.on(
16195
- "connection.changed",
16196
- async (event) => {
16197
- if (event.online) {
16198
- await this.syncAndExecutePendingTasks();
16199
- await this.invokeSyncStatusListeners(true);
16200
- } else {
16201
- await this.invokeSyncStatusListeners(false);
16202
- }
16203
- }
16204
- );
16205
- } catch (error) {
16206
- console.log("Error in DBSyncManager.init: ", error);
16207
- }
16208
- };
16209
- /**
16210
- * Registers a listener that is called whenever the sync status changes.
16211
- *
16212
- * @param listener - A callback invoked with the new sync status (`true` or `false`).
16213
- * @returns An object with an `unsubscribe` function to remove the listener.
16214
- */
16215
- this.onSyncStatusChange = (listener) => {
16216
- this.syncStatusListeners.push(listener);
16217
- return {
16218
- unsubscribe: () => {
16219
- this.syncStatusListeners = this.syncStatusListeners.filter(
16220
- (el) => el !== listener
16221
- );
16222
- }
16223
- };
16224
- };
16225
- /**
16226
- * Schedules a one-time callback to be invoked after the next successful sync.
16227
- *
16228
- * @param tag - A unique key to identify and manage the callback.
16229
- * @param callback - An async function to run after sync.
16230
- */
16231
- this.scheduleSyncStatusChangeCallback = (tag, callback) => {
16232
- this.scheduledSyncStatusCallbacks.set(tag, callback);
16233
- };
16234
- /**
16235
- * Invokes all registered sync status listeners and executes any scheduled sync callbacks.
16236
- *
16237
- * @param status - The new sync status (`true` or `false`).
16238
- */
16239
- this.invokeSyncStatusListeners = async (status) => {
16240
- this.syncStatus = status;
16241
- this.syncStatusListeners.forEach((l) => l(status));
16242
- if (status) {
16243
- const promises = Array.from(this.scheduledSyncStatusCallbacks.values()).map(
16244
- (cb) => cb()
16245
- );
16246
- await Promise.all(promises);
16247
- this.scheduledSyncStatusCallbacks.clear();
16248
- }
16249
- };
16250
- /**
16251
- * Performs synchronization with the Stream backend.
16252
- *
16253
- * This includes downloading events since the last sync, updating the local DB,
16254
- * and handling sync failures (e.g., if syncing beyond the allowed retention window).
16255
- */
16256
- this.sync = async () => {
16257
- if (!this.client?.user) {
16258
- return;
16259
- }
16260
- const cids = await this.offlineDb.getAllChannelCids();
16261
- if (cids.length === 0) {
16262
- return;
16263
- }
16264
- const lastSyncedAt = await this.offlineDb.getLastSyncedAt({
16265
- userId: this.client.user.id
16266
- });
16267
- if (lastSyncedAt) {
16268
- const lastSyncedAtDate = new Date(lastSyncedAt);
16269
- const nowDate = /* @__PURE__ */ new Date();
16270
- const diff = Math.floor(
16271
- (nowDate.getTime() - lastSyncedAtDate.getTime()) / (1e3 * 60 * 60 * 24)
16272
- );
16273
- if (diff > 30) {
16274
- await this.offlineDb.resetDB();
16275
- } else {
16276
- try {
16277
- const result = await this.client.sync(cids, lastSyncedAtDate.toISOString());
16278
- const queryPromises = result.events.map(
16279
- (event) => this.offlineDb.handleEvent({ event, execute: false })
16280
- );
16281
- const queriesArray = await Promise.all(queryPromises);
16282
- const queries = queriesArray.flat();
16283
- if (queries.length) {
16284
- await this.offlineDb.executeSqlBatch(queries);
16285
- }
16286
- } catch (e) {
16287
- console.log("An error has occurred while syncing the DB.", e);
16288
- await this.offlineDb.resetDB();
16289
- }
16290
- }
16291
- }
16292
- await this.offlineDb.upsertUserSyncStatus({
16293
- userId: this.client.user.id,
16294
- lastSyncedAt: (/* @__PURE__ */ new Date()).toString()
16295
- });
16296
- };
16297
- /**
16298
- * Executes any tasks that were queued while offline and then performs a sync.
16299
- */
16300
- this.syncAndExecutePendingTasks = async () => {
16301
- await this.offlineDb.executePendingTasks();
16302
- await this.sync();
16303
- };
16304
- this.client = client;
16305
- this.offlineDb = offlineDb;
16306
- }
16307
- };
16308
16313
 
16309
16314
  // src/utils/FixedSizeQueueCache.ts
16310
16315
  var FixedSizeQueueCache = class {