@liveblocks/core 1.10.0-beta3 → 1.10.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.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "1.10.0-beta3";
9
+ var PKG_VERSION = "1.10.0";
10
10
  var PKG_FORMAT = "cjs";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -362,23 +362,29 @@ var FSM = class {
362
362
  }
363
363
  onEnterAsync(nameOrPattern, promiseFn, onOK, onError) {
364
364
  return this.onEnter(nameOrPattern, () => {
365
- let cancelled = false;
366
- void promiseFn(this.currentContext.current).then(
365
+ const abortController = new AbortController();
366
+ const signal = abortController.signal;
367
+ let done = false;
368
+ void promiseFn(this.currentContext.current, signal).then(
367
369
  // On OK
368
370
  (data) => {
369
- if (!cancelled) {
371
+ if (!signal.aborted) {
372
+ done = true;
370
373
  this.transition({ type: "ASYNC_OK", data }, onOK);
371
374
  }
372
375
  },
373
376
  // On Error
374
377
  (reason) => {
375
- if (!cancelled) {
378
+ if (!signal.aborted) {
379
+ done = true;
376
380
  this.transition({ type: "ASYNC_ERROR", reason }, onError);
377
381
  }
378
382
  }
379
383
  );
380
384
  return () => {
381
- cancelled = true;
385
+ if (!done) {
386
+ abortController.abort();
387
+ }
382
388
  };
383
389
  });
384
390
  }
@@ -652,6 +658,7 @@ var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
652
658
 
653
659
  // src/types/IWebSocket.ts
654
660
  var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
661
+ WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_NORMAL"] = 1e3] = "CLOSE_NORMAL";
655
662
  WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
656
663
  WebsocketCloseCodes2[WebsocketCloseCodes2["UNEXPECTED_CONDITION"] = 1011] = "UNEXPECTED_CONDITION";
657
664
  WebsocketCloseCodes2[WebsocketCloseCodes2["TRY_AGAIN_LATER"] = 1013] = "TRY_AGAIN_LATER";
@@ -661,6 +668,7 @@ var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
661
668
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
662
669
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
663
670
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
671
+ WebsocketCloseCodes2[WebsocketCloseCodes2["KICKED"] = 4100] = "KICKED";
664
672
  WebsocketCloseCodes2[WebsocketCloseCodes2["TOKEN_EXPIRED"] = 4109] = "TOKEN_EXPIRED";
665
673
  WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
666
674
  return WebsocketCloseCodes2;
@@ -728,6 +736,7 @@ var StopRetrying = class extends Error {
728
736
  }
729
737
  };
730
738
  var LiveblocksError = class extends Error {
739
+ /** @internal */
731
740
  constructor(message, code) {
732
741
  super(message);
733
742
  this.code = code;
@@ -942,14 +951,16 @@ function createConnectionStateMachine(delegates, options) {
942
951
  // When the "open" event happens, we're ready to transition to the
943
952
  // OK state. This is done by resolving the Promise.
944
953
  //
945
- async (ctx) => {
954
+ async (ctx, signal) => {
946
955
  let capturedPrematureEvent = null;
956
+ let unconfirmedSocket = null;
947
957
  const connect$ = new Promise(
948
958
  (resolve, rej) => {
949
959
  if (ctx.authValue === null) {
950
960
  throw new Error("No auth authValue");
951
961
  }
952
962
  const socket = delegates.createSocket(ctx.authValue);
963
+ unconfirmedSocket = socket;
953
964
  function reject(event) {
954
965
  capturedPrematureEvent = event;
955
966
  socket.removeEventListener("message", onSocketMessage);
@@ -1005,12 +1016,18 @@ function createConnectionStateMachine(delegates, options) {
1005
1016
  //
1006
1017
  ([socket, unsub]) => {
1007
1018
  unsub();
1019
+ if (signal.aborted) {
1020
+ throw new Error("Aborted");
1021
+ }
1008
1022
  if (capturedPrematureEvent) {
1009
1023
  throw capturedPrematureEvent;
1010
1024
  }
1011
1025
  return socket;
1012
1026
  }
1013
- );
1027
+ ).catch((e) => {
1028
+ teardownSocket(unconfirmedSocket);
1029
+ throw e;
1030
+ });
1014
1031
  },
1015
1032
  // Only transition to OK state after a successfully opened WebSocket connection
1016
1033
  (okEvent) => ({
@@ -1338,6 +1355,9 @@ function createAuthManager(authOptions) {
1338
1355
  if (token.parsed.k === "id" /* ID_TOKEN */) {
1339
1356
  return token;
1340
1357
  } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
1358
+ if (!requestOptions.roomId && Object.entries(token.parsed.perms).length === 0) {
1359
+ return token;
1360
+ }
1341
1361
  for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
1342
1362
  if (!requestOptions.roomId) {
1343
1363
  if (resource.includes("*") && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
@@ -1397,6 +1417,9 @@ function createAuthManager(authOptions) {
1397
1417
  }
1398
1418
  function verifyTokenPermissions(parsedToken, options) {
1399
1419
  if (!options.roomId && parsedToken.parsed.k === "acc" /* ACCESS_TOKEN */) {
1420
+ if (Object.entries(parsedToken.parsed.perms).length === 0) {
1421
+ return;
1422
+ }
1400
1423
  for (const [resource, scopes] of Object.entries(
1401
1424
  parsedToken.parsed.perms
1402
1425
  )) {
@@ -1405,7 +1428,7 @@ function createAuthManager(authOptions) {
1405
1428
  }
1406
1429
  }
1407
1430
  throw new StopRetrying(
1408
- "The issued Access Token doesn't grant enough permissions. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/access-tokens-not-enough-permissions"
1431
+ "The issued access token doesn't grant enough permissions. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/access-tokens-not-enough-permissions"
1409
1432
  );
1410
1433
  }
1411
1434
  }
@@ -2049,7 +2072,7 @@ function urljoin(baseUrl, path, params) {
2049
2072
 
2050
2073
  // src/notifications.ts
2051
2074
  var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY = 50;
2052
- function createInboxNotificationsApi({
2075
+ function createNotificationsApi({
2053
2076
  baseUrl,
2054
2077
  authManager,
2055
2078
  currentUserIdStore,
@@ -5187,7 +5210,7 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5187
5210
  );
5188
5211
  return convertToCommentData(comment);
5189
5212
  }
5190
- async function deleteComment({
5213
+ async function deleteComment2({
5191
5214
  threadId,
5192
5215
  commentId
5193
5216
  }) {
@@ -5200,7 +5223,7 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5200
5223
  }
5201
5224
  );
5202
5225
  }
5203
- async function addReaction({
5226
+ async function addReaction2({
5204
5227
  threadId,
5205
5228
  commentId,
5206
5229
  emoji
@@ -5219,7 +5242,7 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5219
5242
  );
5220
5243
  return convertToCommentUserReaction(reaction);
5221
5244
  }
5222
- async function removeReaction({
5245
+ async function removeReaction2({
5223
5246
  threadId,
5224
5247
  commentId,
5225
5248
  emoji
@@ -5240,11 +5263,12 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5240
5263
  editThreadMetadata,
5241
5264
  createComment,
5242
5265
  editComment,
5243
- deleteComment,
5244
- addReaction,
5245
- removeReaction
5266
+ deleteComment: deleteComment2,
5267
+ addReaction: addReaction2,
5268
+ removeReaction: removeReaction2
5246
5269
  };
5247
5270
  }
5271
+ var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY2 = 50;
5248
5272
  function createRoom(options, config) {
5249
5273
  const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
5250
5274
  const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
@@ -5292,9 +5316,6 @@ function createRoom(options, config) {
5292
5316
  opClock: 0,
5293
5317
  nodes: /* @__PURE__ */ new Map(),
5294
5318
  root: void 0,
5295
- comments: {
5296
- lastRequestedAt: null
5297
- },
5298
5319
  undoStack: [],
5299
5320
  redoStack: [],
5300
5321
  pausedHistory: null,
@@ -6299,7 +6320,7 @@ ${Array.from(traces).join("\n\n")}`
6299
6320
  delegates.authenticate,
6300
6321
  fetchClientApi
6301
6322
  );
6302
- async function fetchJson(endpoint, options2) {
6323
+ async function fetchNotificationsJson(endpoint, options2) {
6303
6324
  const authValue = await delegates.authenticate();
6304
6325
  const response = await fetchClientApi(
6305
6326
  config.roomId,
@@ -6309,41 +6330,70 @@ ${Array.from(traces).join("\n\n")}`
6309
6330
  );
6310
6331
  if (!response.ok) {
6311
6332
  if (response.status >= 400 && response.status < 600) {
6312
- let errorMessage = "";
6333
+ let error3;
6313
6334
  try {
6314
6335
  const errorBody = await response.json();
6315
- errorMessage = errorBody.message;
6316
- } catch (error3) {
6317
- errorMessage = response.statusText;
6336
+ error3 = new NotificationsApiError(
6337
+ errorBody.message,
6338
+ response.status,
6339
+ errorBody
6340
+ );
6341
+ } catch (e7) {
6342
+ error3 = new NotificationsApiError(
6343
+ response.statusText,
6344
+ response.status
6345
+ );
6318
6346
  }
6319
- throw new Error(
6320
- `Request failed with status ${response.status}: ${errorMessage}`
6321
- );
6347
+ throw error3;
6322
6348
  }
6323
6349
  }
6324
6350
  let body;
6325
6351
  try {
6326
6352
  body = await response.json();
6327
- } catch (e7) {
6353
+ } catch (e8) {
6328
6354
  body = {};
6329
6355
  }
6330
6356
  return body;
6331
6357
  }
6332
6358
  function getRoomNotificationSettings() {
6333
- return fetchJson("/notification-settings");
6359
+ return fetchNotificationsJson(
6360
+ "/notification-settings"
6361
+ );
6334
6362
  }
6335
6363
  function updateRoomNotificationSettings(settings) {
6336
- return fetchJson("/notification-settings", {
6364
+ return fetchNotificationsJson(
6365
+ "/notification-settings",
6366
+ {
6367
+ method: "POST",
6368
+ body: JSON.stringify(settings),
6369
+ headers: {
6370
+ "Content-Type": "application/json"
6371
+ }
6372
+ }
6373
+ );
6374
+ }
6375
+ async function markInboxNotificationsAsRead(inboxNotificationIds) {
6376
+ await fetchNotificationsJson("/inbox-notifications/read", {
6337
6377
  method: "POST",
6338
- body: JSON.stringify(settings),
6339
6378
  headers: {
6340
6379
  "Content-Type": "application/json"
6341
- }
6380
+ },
6381
+ body: JSON.stringify({ inboxNotificationIds })
6342
6382
  });
6343
6383
  }
6384
+ const batchedMarkInboxNotificationsAsRead = new Batch(
6385
+ async (batchedInboxNotificationIds) => {
6386
+ const inboxNotificationIds = batchedInboxNotificationIds.flat();
6387
+ await markInboxNotificationsAsRead(inboxNotificationIds);
6388
+ return inboxNotificationIds;
6389
+ },
6390
+ { delay: MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY2 }
6391
+ );
6392
+ async function markInboxNotificationAsRead(inboxNotificationId) {
6393
+ await batchedMarkInboxNotificationsAsRead.get(inboxNotificationId);
6394
+ }
6344
6395
  return Object.defineProperty(
6345
6396
  {
6346
- /* NOTE: Exposing internals here only to allow testing implementation details in unit tests */
6347
6397
  [kInternal]: {
6348
6398
  get presenceBuffer() {
6349
6399
  return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _145 => _145.buffer, 'access', _146 => _146.presenceUpdates, 'optionalAccess', _147 => _147.data]), () => ( null)));
@@ -6367,12 +6417,12 @@ ${Array.from(traces).join("\n\n")}`
6367
6417
  rawSend: (data) => managedSocket.send(data)
6368
6418
  },
6369
6419
  comments: {
6370
- get lastRequestedAt() {
6371
- return context.comments.lastRequestedAt;
6372
- },
6373
- set lastRequestedAt(value) {
6374
- context.comments.lastRequestedAt = value;
6375
- }
6420
+ ...commentsApi
6421
+ },
6422
+ notifications: {
6423
+ getRoomNotificationSettings,
6424
+ updateRoomNotificationSettings,
6425
+ markInboxNotificationAsRead
6376
6426
  }
6377
6427
  },
6378
6428
  id: config.roomId,
@@ -6410,12 +6460,7 @@ ${Array.from(traces).join("\n\n")}`
6410
6460
  getSelf: () => self.current,
6411
6461
  // Presence
6412
6462
  getPresence: () => context.myPresence.current,
6413
- getOthers: () => context.others.current,
6414
- // Comments
6415
- ...commentsApi,
6416
- // Notifications
6417
- getRoomNotificationSettings,
6418
- updateRoomNotificationSettings
6463
+ getOthers: () => context.others.current
6419
6464
  },
6420
6465
  // Explictly make the internal field non-enumerable, to avoid aggressive
6421
6466
  // freezing when used with Immer
@@ -6680,8 +6725,15 @@ function applyOptimisticUpdates(state) {
6680
6725
  if (thread === void 0) {
6681
6726
  break;
6682
6727
  }
6728
+ if (thread.deletedAt !== void 0) {
6729
+ break;
6730
+ }
6731
+ if (thread.updatedAt !== void 0 && thread.updatedAt > optimisticUpdate.updatedAt) {
6732
+ break;
6733
+ }
6683
6734
  result.threads[thread.id] = {
6684
6735
  ...thread,
6736
+ updatedAt: optimisticUpdate.updatedAt,
6685
6737
  metadata: {
6686
6738
  ...thread.metadata,
6687
6739
  ...optimisticUpdate.metadata
@@ -6694,16 +6746,17 @@ function applyOptimisticUpdates(state) {
6694
6746
  if (thread === void 0) {
6695
6747
  break;
6696
6748
  }
6697
- result.threads[thread.id] = {
6698
- ...thread,
6699
- comments: [...thread.comments, optimisticUpdate.comment]
6700
- // TODO: Handle replace comment
6701
- };
6702
- if (!optimisticUpdate.inboxNotificationId) {
6749
+ result.threads[thread.id] = upsertComment(
6750
+ thread,
6751
+ optimisticUpdate.comment
6752
+ );
6753
+ const inboxNotification = Object.values(result.inboxNotifications).find(
6754
+ (notification) => notification.threadId === thread.id
6755
+ );
6756
+ if (inboxNotification === void 0) {
6703
6757
  break;
6704
6758
  }
6705
- const inboxNotification = result.inboxNotifications[optimisticUpdate.inboxNotificationId];
6706
- result.inboxNotifications[optimisticUpdate.inboxNotificationId] = {
6759
+ result.inboxNotifications[inboxNotification.id] = {
6707
6760
  ...inboxNotification,
6708
6761
  notifiedAt: optimisticUpdate.comment.createdAt,
6709
6762
  readAt: optimisticUpdate.comment.createdAt
@@ -6711,20 +6764,14 @@ function applyOptimisticUpdates(state) {
6711
6764
  break;
6712
6765
  }
6713
6766
  case "edit-comment": {
6714
- const thread = result.threads[optimisticUpdate.threadId];
6767
+ const thread = result.threads[optimisticUpdate.comment.threadId];
6715
6768
  if (thread === void 0) {
6716
6769
  break;
6717
6770
  }
6718
- result.threads[thread.id] = {
6719
- ...thread,
6720
- comments: thread.comments.map(
6721
- (comment) => comment.id === optimisticUpdate.commentId ? {
6722
- ...comment,
6723
- editedAt: optimisticUpdate.editedAt,
6724
- body: optimisticUpdate.body
6725
- } : comment
6726
- )
6727
- };
6771
+ result.threads[thread.id] = upsertComment(
6772
+ thread,
6773
+ optimisticUpdate.comment
6774
+ );
6728
6775
  break;
6729
6776
  }
6730
6777
  case "delete-comment": {
@@ -6732,21 +6779,11 @@ function applyOptimisticUpdates(state) {
6732
6779
  if (thread === void 0) {
6733
6780
  break;
6734
6781
  }
6735
- result.threads[thread.id] = {
6736
- ...thread,
6737
- comments: thread.comments.map(
6738
- (comment) => comment.id === optimisticUpdate.commentId ? {
6739
- ...comment,
6740
- deletedAt: optimisticUpdate.deletedAt,
6741
- body: void 0
6742
- } : comment
6743
- )
6744
- };
6745
- if (!result.threads[thread.id].comments.some(
6746
- (comment) => comment.deletedAt === void 0
6747
- )) {
6748
- delete result.threads[thread.id];
6749
- }
6782
+ result.threads[thread.id] = deleteComment(
6783
+ thread,
6784
+ optimisticUpdate.commentId,
6785
+ optimisticUpdate.deletedAt
6786
+ );
6750
6787
  break;
6751
6788
  }
6752
6789
  case "add-reaction": {
@@ -6754,43 +6791,11 @@ function applyOptimisticUpdates(state) {
6754
6791
  if (thread === void 0) {
6755
6792
  break;
6756
6793
  }
6757
- result.threads[thread.id] = {
6758
- ...thread,
6759
- comments: thread.comments.map((comment) => {
6760
- if (comment.id === optimisticUpdate.commentId) {
6761
- if (comment.reactions.some(
6762
- (reaction) => reaction.emoji === optimisticUpdate.emoji
6763
- )) {
6764
- return {
6765
- ...comment,
6766
- reactions: comment.reactions.map(
6767
- (reaction) => reaction.emoji === optimisticUpdate.emoji ? {
6768
- ...reaction,
6769
- users: [
6770
- ...reaction.users,
6771
- { id: optimisticUpdate.userId }
6772
- ]
6773
- } : reaction
6774
- )
6775
- };
6776
- } else {
6777
- return {
6778
- ...comment,
6779
- reactions: [
6780
- ...comment.reactions,
6781
- {
6782
- emoji: optimisticUpdate.emoji,
6783
- createdAt: optimisticUpdate.createdAt,
6784
- users: [{ id: optimisticUpdate.userId }]
6785
- }
6786
- ]
6787
- };
6788
- }
6789
- } else {
6790
- return comment;
6791
- }
6792
- })
6793
- };
6794
+ result.threads[thread.id] = addReaction(
6795
+ thread,
6796
+ optimisticUpdate.commentId,
6797
+ optimisticUpdate.reaction
6798
+ );
6794
6799
  break;
6795
6800
  }
6796
6801
  case "remove-reaction": {
@@ -6798,37 +6803,13 @@ function applyOptimisticUpdates(state) {
6798
6803
  if (thread === void 0) {
6799
6804
  break;
6800
6805
  }
6801
- result.threads[thread.id] = {
6802
- ...thread,
6803
- comments: thread.comments.map((comment) => {
6804
- if (comment.id !== optimisticUpdate.commentId) {
6805
- return comment;
6806
- }
6807
- const reactionIndex = comment.reactions.findIndex(
6808
- (reaction) => reaction.emoji === optimisticUpdate.emoji
6809
- );
6810
- let reactions = comment.reactions;
6811
- if (reactionIndex >= 0 && comment.reactions[reactionIndex].users.some(
6812
- (user) => user.id === optimisticUpdate.userId
6813
- )) {
6814
- if (comment.reactions[reactionIndex].users.length <= 1) {
6815
- reactions = [...comment.reactions];
6816
- reactions.splice(reactionIndex, 1);
6817
- } else {
6818
- reactions[reactionIndex] = {
6819
- ...reactions[reactionIndex],
6820
- users: reactions[reactionIndex].users.filter(
6821
- (user) => user.id !== optimisticUpdate.userId
6822
- )
6823
- };
6824
- }
6825
- }
6826
- return {
6827
- ...comment,
6828
- reactions
6829
- };
6830
- })
6831
- };
6806
+ result.threads[thread.id] = removeReaction(
6807
+ thread,
6808
+ optimisticUpdate.commentId,
6809
+ optimisticUpdate.emoji,
6810
+ optimisticUpdate.userId,
6811
+ optimisticUpdate.removedAt
6812
+ );
6832
6813
  break;
6833
6814
  }
6834
6815
  case "mark-inbox-notification-as-read": {
@@ -6910,6 +6891,169 @@ function compareInboxNotifications(inboxNotificationA, inboxNotificationB) {
6910
6891
  }
6911
6892
  return 0;
6912
6893
  }
6894
+ function upsertComment(thread, comment) {
6895
+ if (thread.deletedAt !== void 0) {
6896
+ return thread;
6897
+ }
6898
+ if (comment.threadId !== thread.id) {
6899
+ warn(
6900
+ `Comment ${comment.id} does not belong to thread ${thread.id}`
6901
+ );
6902
+ return thread;
6903
+ }
6904
+ const existingComment = thread.comments.find(
6905
+ (existingComment2) => existingComment2.id === comment.id
6906
+ );
6907
+ if (existingComment === void 0) {
6908
+ const updatedAt = new Date(
6909
+ Math.max(_optionalChain([thread, 'access', _149 => _149.updatedAt, 'optionalAccess', _150 => _150.getTime, 'call', _151 => _151()]) || 0, comment.createdAt.getTime())
6910
+ );
6911
+ const updatedThread = {
6912
+ ...thread,
6913
+ updatedAt,
6914
+ comments: [...thread.comments, comment]
6915
+ };
6916
+ return updatedThread;
6917
+ }
6918
+ if (existingComment.deletedAt !== void 0) {
6919
+ return thread;
6920
+ }
6921
+ if (existingComment.editedAt === void 0 || comment.editedAt === void 0 || existingComment.editedAt <= comment.editedAt) {
6922
+ const updatedComments = thread.comments.map(
6923
+ (existingComment2) => existingComment2.id === comment.id ? comment : existingComment2
6924
+ );
6925
+ const updatedThread = {
6926
+ ...thread,
6927
+ updatedAt: new Date(
6928
+ Math.max(
6929
+ _optionalChain([thread, 'access', _152 => _152.updatedAt, 'optionalAccess', _153 => _153.getTime, 'call', _154 => _154()]) || 0,
6930
+ _optionalChain([comment, 'access', _155 => _155.editedAt, 'optionalAccess', _156 => _156.getTime, 'call', _157 => _157()]) || comment.createdAt.getTime()
6931
+ )
6932
+ ),
6933
+ comments: updatedComments
6934
+ };
6935
+ return updatedThread;
6936
+ }
6937
+ return thread;
6938
+ }
6939
+ function deleteComment(thread, commentId, deletedAt) {
6940
+ if (thread.deletedAt !== void 0) {
6941
+ return thread;
6942
+ }
6943
+ const existingComment = thread.comments.find(
6944
+ (comment) => comment.id === commentId
6945
+ );
6946
+ if (existingComment === void 0) {
6947
+ return thread;
6948
+ }
6949
+ if (existingComment.deletedAt !== void 0) {
6950
+ return thread;
6951
+ }
6952
+ const updatedComments = thread.comments.map(
6953
+ (comment) => comment.id === commentId ? {
6954
+ ...comment,
6955
+ deletedAt,
6956
+ body: void 0
6957
+ } : comment
6958
+ );
6959
+ if (!updatedComments.some((comment) => comment.deletedAt === void 0)) {
6960
+ return {
6961
+ ...thread,
6962
+ deletedAt,
6963
+ updatedAt: deletedAt,
6964
+ comments: []
6965
+ };
6966
+ }
6967
+ return {
6968
+ ...thread,
6969
+ updatedAt: deletedAt,
6970
+ comments: updatedComments
6971
+ };
6972
+ }
6973
+ function addReaction(thread, commentId, reaction) {
6974
+ if (thread.deletedAt !== void 0) {
6975
+ return thread;
6976
+ }
6977
+ const existingComment = thread.comments.find(
6978
+ (comment) => comment.id === commentId
6979
+ );
6980
+ if (existingComment === void 0) {
6981
+ return thread;
6982
+ }
6983
+ if (existingComment.deletedAt !== void 0) {
6984
+ return thread;
6985
+ }
6986
+ const updatedComments = thread.comments.map(
6987
+ (comment) => comment.id === commentId ? {
6988
+ ...comment,
6989
+ reactions: upsertReaction(comment.reactions, reaction)
6990
+ } : comment
6991
+ );
6992
+ return {
6993
+ ...thread,
6994
+ updatedAt: new Date(
6995
+ Math.max(reaction.createdAt.getTime(), _optionalChain([thread, 'access', _158 => _158.updatedAt, 'optionalAccess', _159 => _159.getTime, 'call', _160 => _160()]) || 0)
6996
+ ),
6997
+ comments: updatedComments
6998
+ };
6999
+ }
7000
+ function removeReaction(thread, commentId, emoji, userId, removedAt) {
7001
+ if (thread.deletedAt !== void 0) {
7002
+ return thread;
7003
+ }
7004
+ const existingComment = thread.comments.find(
7005
+ (comment) => comment.id === commentId
7006
+ );
7007
+ if (existingComment === void 0) {
7008
+ return thread;
7009
+ }
7010
+ if (existingComment.deletedAt !== void 0) {
7011
+ return thread;
7012
+ }
7013
+ const updatedComments = thread.comments.map(
7014
+ (comment) => comment.id === commentId ? {
7015
+ ...comment,
7016
+ reactions: comment.reactions.map(
7017
+ (reaction) => reaction.emoji === emoji ? {
7018
+ ...reaction,
7019
+ users: reaction.users.filter((user) => user.id !== userId)
7020
+ } : reaction
7021
+ ).filter((reaction) => reaction.users.length > 0)
7022
+ // Remove reactions with no users left
7023
+ } : comment
7024
+ );
7025
+ return {
7026
+ ...thread,
7027
+ updatedAt: new Date(
7028
+ Math.max(removedAt.getTime(), _optionalChain([thread, 'access', _161 => _161.updatedAt, 'optionalAccess', _162 => _162.getTime, 'call', _163 => _163()]) || 0)
7029
+ ),
7030
+ comments: updatedComments
7031
+ };
7032
+ }
7033
+ function upsertReaction(reactions, reaction) {
7034
+ const existingReaction = reactions.find(
7035
+ (existingReaction2) => existingReaction2.emoji === reaction.emoji
7036
+ );
7037
+ if (existingReaction === void 0) {
7038
+ return [
7039
+ ...reactions,
7040
+ {
7041
+ emoji: reaction.emoji,
7042
+ createdAt: reaction.createdAt,
7043
+ users: [{ id: reaction.userId }]
7044
+ }
7045
+ ];
7046
+ }
7047
+ if (existingReaction.users.some((user) => user.id === reaction.userId) === false) {
7048
+ return reactions.map(
7049
+ (existingReaction2) => existingReaction2.emoji === reaction.emoji ? {
7050
+ ...existingReaction2,
7051
+ users: [...existingReaction2.users, { id: reaction.userId }]
7052
+ } : existingReaction2
7053
+ );
7054
+ }
7055
+ return reactions;
7056
+ }
6913
7057
 
6914
7058
  // src/client.ts
6915
7059
  var MIN_THROTTLE = 16;
@@ -6999,12 +7143,12 @@ function createClient(options) {
6999
7143
  createSocket: makeCreateSocketDelegateForRoom(
7000
7144
  roomId,
7001
7145
  baseUrl,
7002
- _optionalChain([clientOptions, 'access', _149 => _149.polyfills, 'optionalAccess', _150 => _150.WebSocket])
7146
+ _optionalChain([clientOptions, 'access', _164 => _164.polyfills, 'optionalAccess', _165 => _165.WebSocket])
7003
7147
  ),
7004
7148
  authenticate: makeAuthDelegateForRoom(roomId, authManager)
7005
7149
  })),
7006
7150
  enableDebugLogging: clientOptions.enableDebugLogging,
7007
- unstable_batchedUpdates: _optionalChain([options2, 'optionalAccess', _151 => _151.unstable_batchedUpdates]),
7151
+ unstable_batchedUpdates: _optionalChain([options2, 'optionalAccess', _166 => _166.unstable_batchedUpdates]),
7008
7152
  baseUrl,
7009
7153
  unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP,
7010
7154
  unstable_streamData: !!clientOptions.unstable_streamData
@@ -7020,7 +7164,7 @@ function createClient(options) {
7020
7164
  const shouldConnect = _nullishCoalesce(_nullishCoalesce(options2.autoConnect, () => ( options2.shouldInitiallyConnect)), () => ( true));
7021
7165
  if (shouldConnect) {
7022
7166
  if (typeof atob === "undefined") {
7023
- if (_optionalChain([clientOptions, 'access', _152 => _152.polyfills, 'optionalAccess', _153 => _153.atob]) === void 0) {
7167
+ if (_optionalChain([clientOptions, 'access', _167 => _167.polyfills, 'optionalAccess', _168 => _168.atob]) === void 0) {
7024
7168
  throw new Error(
7025
7169
  "You need to polyfill atob to use the client in your environment. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/atob-polyfill"
7026
7170
  );
@@ -7036,11 +7180,11 @@ function createClient(options) {
7036
7180
  return room;
7037
7181
  }
7038
7182
  function getRoom(roomId) {
7039
- const room = _optionalChain([roomsById, 'access', _154 => _154.get, 'call', _155 => _155(roomId), 'optionalAccess', _156 => _156.room]);
7183
+ const room = _optionalChain([roomsById, 'access', _169 => _169.get, 'call', _170 => _170(roomId), 'optionalAccess', _171 => _171.room]);
7040
7184
  return room ? room : null;
7041
7185
  }
7042
7186
  function forceLeave(roomId) {
7043
- const unsubs = _nullishCoalesce(_optionalChain([roomsById, 'access', _157 => _157.get, 'call', _158 => _158(roomId), 'optionalAccess', _159 => _159.unsubs]), () => ( /* @__PURE__ */ new Set()));
7187
+ const unsubs = _nullishCoalesce(_optionalChain([roomsById, 'access', _172 => _172.get, 'call', _173 => _173(roomId), 'optionalAccess', _174 => _174.unsubs]), () => ( /* @__PURE__ */ new Set()));
7044
7188
  for (const unsub of unsubs) {
7045
7189
  unsub();
7046
7190
  }
@@ -7059,9 +7203,9 @@ function createClient(options) {
7059
7203
  getUnreadInboxNotificationsCount,
7060
7204
  markAllInboxNotificationsAsRead,
7061
7205
  markInboxNotificationAsRead
7062
- } = createInboxNotificationsApi({
7206
+ } = createNotificationsApi({
7063
7207
  baseUrl,
7064
- fetcher: _optionalChain([clientOptions, 'access', _160 => _160.polyfills, 'optionalAccess', _161 => _161.fetch]) || /* istanbul ignore next */
7208
+ fetcher: _optionalChain([clientOptions, 'access', _175 => _175.polyfills, 'optionalAccess', _176 => _176.fetch]) || /* istanbul ignore next */
7065
7209
  fetch,
7066
7210
  authManager,
7067
7211
  currentUserIdStore
@@ -7075,7 +7219,7 @@ function createClient(options) {
7075
7219
  const usersStore = createBatchStore(
7076
7220
  async (batchedUserIds) => {
7077
7221
  const userIds = batchedUserIds.flat();
7078
- const users = await _optionalChain([resolveUsers, 'optionalCall', _162 => _162({ userIds })]);
7222
+ const users = await _optionalChain([resolveUsers, 'optionalCall', _177 => _177({ userIds })]);
7079
7223
  warnIfNoResolveUsers();
7080
7224
  return _nullishCoalesce(users, () => ( userIds.map(() => void 0)));
7081
7225
  },
@@ -7089,7 +7233,7 @@ function createClient(options) {
7089
7233
  const roomsInfoStore = createBatchStore(
7090
7234
  async (batchedRoomIds) => {
7091
7235
  const roomIds = batchedRoomIds.flat();
7092
- const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _163 => _163({ roomIds })]);
7236
+ const roomsInfo = await _optionalChain([resolveRoomsInfo, 'optionalCall', _178 => _178({ roomIds })]);
7093
7237
  warnIfNoResolveRoomsInfo();
7094
7238
  return _nullishCoalesce(roomsInfo, () => ( roomIds.map(() => void 0)));
7095
7239
  },
@@ -7104,13 +7248,14 @@ function createClient(options) {
7104
7248
  leave: forceLeave,
7105
7249
  // New, preferred API
7106
7250
  enterRoom,
7107
- // Notifications API
7108
- getInboxNotifications,
7109
- getUnreadInboxNotificationsCount,
7110
- markAllInboxNotificationsAsRead,
7111
- markInboxNotificationAsRead,
7112
7251
  // Internal
7113
7252
  [kInternal]: {
7253
+ notifications: {
7254
+ getInboxNotifications,
7255
+ getUnreadInboxNotificationsCount,
7256
+ markAllInboxNotificationsAsRead,
7257
+ markInboxNotificationAsRead
7258
+ },
7114
7259
  currentUserIdStore,
7115
7260
  resolveMentionSuggestions: clientOptions.resolveMentionSuggestions,
7116
7261
  cacheStore,
@@ -7205,7 +7350,7 @@ var commentBodyElementsTypes = {
7205
7350
  mention: "inline"
7206
7351
  };
7207
7352
  function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
7208
- if (!body || !_optionalChain([body, 'optionalAccess', _164 => _164.content])) {
7353
+ if (!body || !_optionalChain([body, 'optionalAccess', _179 => _179.content])) {
7209
7354
  return;
7210
7355
  }
7211
7356
  const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
@@ -7215,13 +7360,13 @@ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
7215
7360
  for (const block of body.content) {
7216
7361
  if (type === "all" || type === "block") {
7217
7362
  if (guard(block)) {
7218
- _optionalChain([visitor, 'optionalCall', _165 => _165(block)]);
7363
+ _optionalChain([visitor, 'optionalCall', _180 => _180(block)]);
7219
7364
  }
7220
7365
  }
7221
7366
  if (type === "all" || type === "inline") {
7222
7367
  for (const inline of block.children) {
7223
7368
  if (guard(inline)) {
7224
- _optionalChain([visitor, 'optionalCall', _166 => _166(inline)]);
7369
+ _optionalChain([visitor, 'optionalCall', _181 => _181(inline)]);
7225
7370
  }
7226
7371
  }
7227
7372
  }
@@ -7246,7 +7391,7 @@ async function resolveUsersInCommentBody(body, resolveUsers) {
7246
7391
  userIds
7247
7392
  });
7248
7393
  for (const [index, userId] of userIds.entries()) {
7249
- const user = _optionalChain([users, 'optionalAccess', _167 => _167[index]]);
7394
+ const user = _optionalChain([users, 'optionalAccess', _182 => _182[index]]);
7250
7395
  if (user) {
7251
7396
  resolvedUsers.set(userId, user);
7252
7397
  }
@@ -7369,7 +7514,7 @@ var stringifyCommentBodyPlainElements = {
7369
7514
  text: ({ element }) => element.text,
7370
7515
  link: ({ element }) => element.url,
7371
7516
  mention: ({ element, user }) => {
7372
- return `@${_nullishCoalesce(_optionalChain([user, 'optionalAccess', _168 => _168.name]), () => ( element.id))}`;
7517
+ return `@${_nullishCoalesce(_optionalChain([user, 'optionalAccess', _183 => _183.name]), () => ( element.id))}`;
7373
7518
  }
7374
7519
  };
7375
7520
  var stringifyCommentBodyHtmlElements = {
@@ -7399,7 +7544,7 @@ var stringifyCommentBodyHtmlElements = {
7399
7544
  return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
7400
7545
  },
7401
7546
  mention: ({ element, user }) => {
7402
- return html`<span data-mention>@${_nullishCoalesce(_optionalChain([user, 'optionalAccess', _169 => _169.name]), () => ( element.id))}</span>`;
7547
+ return html`<span data-mention>@${_nullishCoalesce(_optionalChain([user, 'optionalAccess', _184 => _184.name]), () => ( element.id))}</span>`;
7403
7548
  }
7404
7549
  };
7405
7550
  var stringifyCommentBodyMarkdownElements = {
@@ -7429,19 +7574,19 @@ var stringifyCommentBodyMarkdownElements = {
7429
7574
  return markdown`[${element.url}](${href})`;
7430
7575
  },
7431
7576
  mention: ({ element, user }) => {
7432
- return markdown`@${_nullishCoalesce(_optionalChain([user, 'optionalAccess', _170 => _170.name]), () => ( element.id))}`;
7577
+ return markdown`@${_nullishCoalesce(_optionalChain([user, 'optionalAccess', _185 => _185.name]), () => ( element.id))}`;
7433
7578
  }
7434
7579
  };
7435
7580
  async function stringifyCommentBody(body, options) {
7436
- const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _171 => _171.format]), () => ( "plain"));
7437
- const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _172 => _172.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
7581
+ const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _186 => _186.format]), () => ( "plain"));
7582
+ const separator = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _187 => _187.separator]), () => ( (format === "markdown" ? "\n\n" : "\n")));
7438
7583
  const elements = {
7439
7584
  ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
7440
- ..._optionalChain([options, 'optionalAccess', _173 => _173.elements])
7585
+ ..._optionalChain([options, 'optionalAccess', _188 => _188.elements])
7441
7586
  };
7442
7587
  const resolvedUsers = await resolveUsersInCommentBody(
7443
7588
  body,
7444
- _optionalChain([options, 'optionalAccess', _174 => _174.resolveUsers])
7589
+ _optionalChain([options, 'optionalAccess', _189 => _189.resolveUsers])
7445
7590
  );
7446
7591
  const blocks = body.content.flatMap((block, blockIndex) => {
7447
7592
  switch (block.type) {
@@ -7716,12 +7861,12 @@ function legacy_patchImmutableNode(state, path, update) {
7716
7861
  }
7717
7862
  const newState = Object.assign({}, state);
7718
7863
  for (const key in update.updates) {
7719
- if (_optionalChain([update, 'access', _175 => _175.updates, 'access', _176 => _176[key], 'optionalAccess', _177 => _177.type]) === "update") {
7864
+ if (_optionalChain([update, 'access', _190 => _190.updates, 'access', _191 => _191[key], 'optionalAccess', _192 => _192.type]) === "update") {
7720
7865
  const val = update.node.get(key);
7721
7866
  if (val !== void 0) {
7722
7867
  newState[key] = lsonToJson(val);
7723
7868
  }
7724
- } else if (_optionalChain([update, 'access', _178 => _178.updates, 'access', _179 => _179[key], 'optionalAccess', _180 => _180.type]) === "delete") {
7869
+ } else if (_optionalChain([update, 'access', _193 => _193.updates, 'access', _194 => _194[key], 'optionalAccess', _195 => _195.type]) === "delete") {
7725
7870
  delete newState[key];
7726
7871
  }
7727
7872
  }
@@ -7782,12 +7927,12 @@ function legacy_patchImmutableNode(state, path, update) {
7782
7927
  }
7783
7928
  const newState = Object.assign({}, state);
7784
7929
  for (const key in update.updates) {
7785
- if (_optionalChain([update, 'access', _181 => _181.updates, 'access', _182 => _182[key], 'optionalAccess', _183 => _183.type]) === "update") {
7930
+ if (_optionalChain([update, 'access', _196 => _196.updates, 'access', _197 => _197[key], 'optionalAccess', _198 => _198.type]) === "update") {
7786
7931
  const value = update.node.get(key);
7787
7932
  if (value !== void 0) {
7788
7933
  newState[key] = lsonToJson(value);
7789
7934
  }
7790
- } else if (_optionalChain([update, 'access', _184 => _184.updates, 'access', _185 => _185[key], 'optionalAccess', _186 => _186.type]) === "delete") {
7935
+ } else if (_optionalChain([update, 'access', _199 => _199.updates, 'access', _200 => _200[key], 'optionalAccess', _201 => _201.type]) === "delete") {
7791
7936
  delete newState[key];
7792
7937
  }
7793
7938
  }
@@ -8001,5 +8146,9 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
8001
8146
 
8002
8147
 
8003
8148
 
8004
- exports.ClientMsgCode = ClientMsgCode; exports.CommentsApiError = CommentsApiError; exports.CrdtType = CrdtType; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.NotificationsApiError = NotificationsApiError; exports.OpCode = OpCode; exports.ServerMsgCode = ServerMsgCode; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.ackOp = ackOp; exports.applyOptimisticUpdates = applyOptimisticUpdates; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.b64decode = b64decode; exports.cloneLson = cloneLson; exports.console = fancy_console_exports; exports.convertToCommentData = convertToCommentData; exports.convertToCommentUserReaction = convertToCommentUserReaction; exports.convertToThreadData = convertToThreadData; exports.createClient = createClient; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.errorIf = errorIf; exports.freeze = freeze; exports.getMentionedIdsFromCommentBody = getMentionedIdsFromCommentBody; exports.isChildCrdt = isChildCrdt; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isLiveNode = isLiveNode; exports.isPlainObject = isPlainObject; exports.isRootCrdt = isRootCrdt; exports.kInternal = kInternal; exports.legacy_patchImmutableObject = legacy_patchImmutableObject; exports.lsonToJson = lsonToJson; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.nn = nn; exports.patchLiveObjectKey = patchLiveObjectKey; exports.raise = raise; exports.shallow = shallow; exports.stringify = stringify; exports.stringifyCommentBody = stringifyCommentBody; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.tryParseJson = tryParseJson; exports.withTimeout = withTimeout;
8149
+
8150
+
8151
+
8152
+
8153
+ exports.ClientMsgCode = ClientMsgCode; exports.CommentsApiError = CommentsApiError; exports.CrdtType = CrdtType; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.NotificationsApiError = NotificationsApiError; exports.OpCode = OpCode; exports.ServerMsgCode = ServerMsgCode; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.ackOp = ackOp; exports.addReaction = addReaction; exports.applyOptimisticUpdates = applyOptimisticUpdates; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.b64decode = b64decode; exports.cloneLson = cloneLson; exports.console = fancy_console_exports; exports.convertToCommentData = convertToCommentData; exports.convertToCommentUserReaction = convertToCommentUserReaction; exports.convertToThreadData = convertToThreadData; exports.createClient = createClient; exports.deleteComment = deleteComment; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.errorIf = errorIf; exports.freeze = freeze; exports.getMentionedIdsFromCommentBody = getMentionedIdsFromCommentBody; exports.isChildCrdt = isChildCrdt; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isLiveNode = isLiveNode; exports.isPlainObject = isPlainObject; exports.isRootCrdt = isRootCrdt; exports.kInternal = kInternal; exports.legacy_patchImmutableObject = legacy_patchImmutableObject; exports.lsonToJson = lsonToJson; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.nn = nn; exports.patchLiveObjectKey = patchLiveObjectKey; exports.raise = raise; exports.removeReaction = removeReaction; exports.shallow = shallow; exports.stringify = stringify; exports.stringifyCommentBody = stringifyCommentBody; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.tryParseJson = tryParseJson; exports.upsertComment = upsertComment; exports.withTimeout = withTimeout;
8005
8154
  //# sourceMappingURL=index.js.map