@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.mjs 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 = "esm";
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,16 +6330,21 @@ ${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 {
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;
@@ -6330,20 +6356,44 @@ ${Array.from(traces).join("\n\n")}`
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(context.buffer.presenceUpdates?.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(thread.updatedAt?.getTime() || 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
+ thread.updatedAt?.getTime() || 0,
6930
+ comment.editedAt?.getTime() || 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(), thread.updatedAt?.getTime() || 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(), thread.updatedAt?.getTime() || 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;
@@ -7059,7 +7203,7 @@ function createClient(options) {
7059
7203
  getUnreadInboxNotificationsCount,
7060
7204
  markAllInboxNotificationsAsRead,
7061
7205
  markInboxNotificationAsRead
7062
- } = createInboxNotificationsApi({
7206
+ } = createNotificationsApi({
7063
7207
  baseUrl,
7064
7208
  fetcher: clientOptions.polyfills?.fetch || /* istanbul ignore next */
7065
7209
  fetch,
@@ -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,
@@ -7961,6 +8106,7 @@ export {
7961
8106
  ServerMsgCode,
7962
8107
  WebsocketCloseCodes,
7963
8108
  ackOp,
8109
+ addReaction,
7964
8110
  applyOptimisticUpdates,
7965
8111
  asPos,
7966
8112
  assert,
@@ -7972,6 +8118,7 @@ export {
7972
8118
  convertToCommentUserReaction,
7973
8119
  convertToThreadData,
7974
8120
  createClient,
8121
+ deleteComment,
7975
8122
  deprecate,
7976
8123
  deprecateIf,
7977
8124
  detectDupes,
@@ -7994,12 +8141,14 @@ export {
7994
8141
  nn,
7995
8142
  patchLiveObjectKey,
7996
8143
  raise,
8144
+ removeReaction,
7997
8145
  shallow,
7998
8146
  stringify,
7999
8147
  stringifyCommentBody,
8000
8148
  throwUsageError,
8001
8149
  toPlainLson,
8002
8150
  tryParseJson,
8151
+ upsertComment,
8003
8152
  withTimeout
8004
8153
  };
8005
8154
  //# sourceMappingURL=index.mjs.map