@liveblocks/core 1.10.0-beta2 → 1.10.0-beta3

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-beta2";
9
+ var PKG_VERSION = "1.10.0-beta3";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -1325,7 +1325,7 @@ function createAuthManager(authOptions) {
1325
1325
  }
1326
1326
  return false;
1327
1327
  }
1328
- function getCachedToken(roomOptions) {
1328
+ function getCachedToken(requestOptions) {
1329
1329
  const now = Math.ceil(Date.now() / 1e3);
1330
1330
  for (let i = tokens.length - 1; i >= 0; i--) {
1331
1331
  const token = tokens[i];
@@ -1338,11 +1338,12 @@ function createAuthManager(authOptions) {
1338
1338
  if (token.parsed.k === "id" /* ID_TOKEN */) {
1339
1339
  return token;
1340
1340
  } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
1341
- if (!roomOptions) {
1342
- return token;
1343
- }
1344
1341
  for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
1345
- if (resource.includes("*") && roomOptions.roomId.startsWith(resource.replace("*", "")) || roomOptions.roomId === resource && hasCorrespondingScopes(roomOptions.requestedScope, scopes)) {
1342
+ if (!requestOptions.roomId) {
1343
+ if (resource.includes("*") && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
1344
+ return token;
1345
+ }
1346
+ } else if (resource.includes("*") && requestOptions.roomId.startsWith(resource.replace("*", "")) || requestOptions.roomId === resource && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
1346
1347
  return token;
1347
1348
  }
1348
1349
  }
@@ -1350,7 +1351,7 @@ function createAuthManager(authOptions) {
1350
1351
  }
1351
1352
  return void 0;
1352
1353
  }
1353
- async function makeAuthRequest(roomId) {
1354
+ async function makeAuthRequest(options) {
1354
1355
  const fetcher = authOptions.polyfills?.fetch ?? (typeof window === "undefined" ? void 0 : window.fetch);
1355
1356
  if (authentication.type === "private") {
1356
1357
  if (fetcher === void 0) {
@@ -1359,9 +1360,10 @@ function createAuthManager(authOptions) {
1359
1360
  );
1360
1361
  }
1361
1362
  const response = await fetchAuthEndpoint(fetcher, authentication.url, {
1362
- room: roomId
1363
+ room: options.roomId
1363
1364
  });
1364
1365
  const parsed = parseAuthToken(response.token);
1366
+ verifyTokenPermissions(parsed, options);
1365
1367
  if (seenTokens.has(parsed.raw)) {
1366
1368
  throw new StopRetrying(
1367
1369
  "The same Liveblocks auth token was issued from the backend before. Caching Liveblocks tokens is not supported."
@@ -1370,10 +1372,12 @@ function createAuthManager(authOptions) {
1370
1372
  return parsed;
1371
1373
  }
1372
1374
  if (authentication.type === "custom") {
1373
- const response = await authentication.callback(roomId);
1375
+ const response = await authentication.callback(options.roomId);
1374
1376
  if (response && typeof response === "object") {
1375
1377
  if (typeof response.token === "string") {
1376
- return parseAuthToken(response.token);
1378
+ const parsed = parseAuthToken(response.token);
1379
+ verifyTokenPermissions(parsed, options);
1380
+ return parsed;
1377
1381
  } else if (typeof response.error === "string") {
1378
1382
  const reason = `Authentication failed: ${"reason" in response && typeof response.reason === "string" ? response.reason : "Forbidden"}`;
1379
1383
  if (response.error === "forbidden") {
@@ -1391,25 +1395,39 @@ function createAuthManager(authOptions) {
1391
1395
  "Unexpected authentication type. Must be private or custom."
1392
1396
  );
1393
1397
  }
1394
- async function getAuthValue(roomOptions) {
1398
+ function verifyTokenPermissions(parsedToken, options) {
1399
+ if (!options.roomId && parsedToken.parsed.k === "acc" /* ACCESS_TOKEN */) {
1400
+ for (const [resource, scopes] of Object.entries(
1401
+ parsedToken.parsed.perms
1402
+ )) {
1403
+ if (resource.includes("*") && hasCorrespondingScopes(options.requestedScope, scopes)) {
1404
+ return;
1405
+ }
1406
+ }
1407
+ 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"
1409
+ );
1410
+ }
1411
+ }
1412
+ async function getAuthValue(requestOptions) {
1395
1413
  if (authentication.type === "public") {
1396
1414
  return { type: "public", publicApiKey: authentication.publicApiKey };
1397
1415
  }
1398
- const cachedToken = getCachedToken(roomOptions);
1416
+ const cachedToken = getCachedToken(requestOptions);
1399
1417
  if (cachedToken !== void 0) {
1400
1418
  return { type: "secret", token: cachedToken };
1401
1419
  }
1402
1420
  let currentPromise;
1403
- if (roomOptions) {
1404
- currentPromise = requestPromises.get(roomOptions.roomId);
1421
+ if (requestOptions.roomId) {
1422
+ currentPromise = requestPromises.get(requestOptions.roomId);
1405
1423
  if (currentPromise === void 0) {
1406
- currentPromise = makeAuthRequest(roomOptions.roomId);
1407
- requestPromises.set(roomOptions.roomId, currentPromise);
1424
+ currentPromise = makeAuthRequest(requestOptions);
1425
+ requestPromises.set(requestOptions.roomId, currentPromise);
1408
1426
  }
1409
1427
  } else {
1410
1428
  currentPromise = requestPromises.get("liveblocks-user-token");
1411
1429
  if (currentPromise === void 0) {
1412
- currentPromise = makeAuthRequest();
1430
+ currentPromise = makeAuthRequest(requestOptions);
1413
1431
  requestPromises.set("liveblocks-user-token", currentPromise);
1414
1432
  }
1415
1433
  }
@@ -1424,8 +1442,8 @@ function createAuthManager(authOptions) {
1424
1442
  }
1425
1443
  return { type: "secret", token };
1426
1444
  } finally {
1427
- if (roomOptions) {
1428
- requestPromises.delete(roomOptions.roomId);
1445
+ if (requestOptions.roomId) {
1446
+ requestPromises.delete(requestOptions.roomId);
1429
1447
  } else {
1430
1448
  requestPromises.delete("liveblocks-user-token");
1431
1449
  }
@@ -1996,6 +2014,38 @@ function convertToInboxNotificationData(data) {
1996
2014
  readAt
1997
2015
  };
1998
2016
  }
2017
+ function convertToThreadDeleteInfo(data) {
2018
+ const deletedAt = new Date(data.deletedAt);
2019
+ return {
2020
+ ...data,
2021
+ deletedAt
2022
+ };
2023
+ }
2024
+ function convertToInboxNotificationDeleteInfo(data) {
2025
+ const deletedAt = new Date(data.deletedAt);
2026
+ return {
2027
+ ...data,
2028
+ deletedAt
2029
+ };
2030
+ }
2031
+
2032
+ // src/lib/url.ts
2033
+ function toURLSearchParams(params) {
2034
+ const result = new URLSearchParams();
2035
+ for (const [key, value] of Object.entries(params)) {
2036
+ if (value !== void 0 && value !== null) {
2037
+ result.set(key, value.toString());
2038
+ }
2039
+ }
2040
+ return result;
2041
+ }
2042
+ function urljoin(baseUrl, path, params) {
2043
+ const url = new URL(path, baseUrl);
2044
+ if (params !== void 0) {
2045
+ url.search = (params instanceof URLSearchParams ? params : toURLSearchParams(params)).toString();
2046
+ }
2047
+ return url.toString();
2048
+ }
1999
2049
 
2000
2050
  // src/notifications.ts
2001
2051
  var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY = 50;
@@ -2005,13 +2055,15 @@ function createInboxNotificationsApi({
2005
2055
  currentUserIdStore,
2006
2056
  fetcher
2007
2057
  }) {
2008
- async function fetchJson(endpoint, options) {
2009
- const authValue = await authManager.getAuthValue();
2058
+ async function fetchJson(endpoint, options, params) {
2059
+ const authValue = await authManager.getAuthValue({
2060
+ requestedScope: "comments:read"
2061
+ });
2010
2062
  if (authValue.type === "secret" && authValue.token.parsed.k === "acc" /* ACCESS_TOKEN */) {
2011
2063
  const userId = authValue.token.parsed.uid;
2012
2064
  currentUserIdStore.set(() => userId);
2013
2065
  }
2014
- const url = new URL(`/v2/c${endpoint}`, baseUrl);
2066
+ const url = urljoin(baseUrl, `/v2/c${endpoint}`, params);
2015
2067
  const response = await fetcher(url.toString(), {
2016
2068
  ...options,
2017
2069
  headers: {
@@ -2047,13 +2099,24 @@ function createInboxNotificationsApi({
2047
2099
  return body;
2048
2100
  }
2049
2101
  async function getInboxNotifications(options) {
2050
- const queryParams = toURLSearchParams({ limit: options?.limit });
2051
- const json = await fetchJson(`/inbox-notifications?${queryParams.toString()}`);
2102
+ const json = await fetchJson("/inbox-notifications", void 0, {
2103
+ limit: options?.limit,
2104
+ since: options?.since?.toISOString()
2105
+ });
2052
2106
  return {
2053
2107
  threads: json.threads.map((thread) => convertToThreadData(thread)),
2054
2108
  inboxNotifications: json.inboxNotifications.map(
2055
2109
  (notification) => convertToInboxNotificationData(notification)
2056
- )
2110
+ ),
2111
+ deletedThreads: json.deletedThreads.map(
2112
+ (info) => convertToThreadDeleteInfo(info)
2113
+ ),
2114
+ deletedInboxNotifications: json.deletedInboxNotifications.map(
2115
+ (info) => convertToInboxNotificationDeleteInfo(info)
2116
+ ),
2117
+ meta: {
2118
+ requestedAt: new Date(json.meta.requestedAt)
2119
+ }
2057
2120
  };
2058
2121
  }
2059
2122
  async function getUnreadInboxNotificationsCount() {
@@ -2096,15 +2159,6 @@ function createInboxNotificationsApi({
2096
2159
  markInboxNotificationAsRead
2097
2160
  };
2098
2161
  }
2099
- function toURLSearchParams(params) {
2100
- const result = new URLSearchParams();
2101
- for (const [key, value] of Object.entries(params)) {
2102
- if (value !== void 0 && value !== null) {
2103
- result.set(key, value.toString());
2104
- }
2105
- }
2106
- return result;
2107
- }
2108
2162
 
2109
2163
  // src/lib/position.ts
2110
2164
  var MIN_CODE = 32;
@@ -4959,12 +5013,12 @@ var CommentsApiError = class extends Error {
4959
5013
  }
4960
5014
  };
4961
5015
  function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
4962
- async function fetchCommentsApi(endpoint, options) {
5016
+ async function fetchCommentsApi(endpoint, params, options) {
4963
5017
  const authValue = await getAuthValue();
4964
- return fetchClientApi(roomId, endpoint, authValue, options);
5018
+ return fetchClientApi(roomId, endpoint, authValue, options, params);
4965
5019
  }
4966
- async function fetchJson(endpoint, options) {
4967
- const response = await fetchCommentsApi(endpoint, options);
5020
+ async function fetchJson(endpoint, options, params) {
5021
+ const response = await fetchCommentsApi(endpoint, params, options);
4968
5022
  if (!response.ok) {
4969
5023
  if (response.status >= 400 && response.status < 600) {
4970
5024
  let error3;
@@ -4990,25 +5044,48 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
4990
5044
  return body;
4991
5045
  }
4992
5046
  async function getThreads(options) {
4993
- const response = await fetchCommentsApi("/threads/search", {
4994
- body: JSON.stringify({
4995
- ...options?.query?.metadata && { metadata: options.query.metadata }
4996
- }),
4997
- headers: {
4998
- "Content-Type": "application/json"
5047
+ const response = await fetchCommentsApi(
5048
+ "/threads/search",
5049
+ {
5050
+ since: options?.since?.toISOString()
4999
5051
  },
5000
- method: "POST"
5001
- });
5052
+ {
5053
+ body: JSON.stringify({
5054
+ ...options?.query?.metadata && { metadata: options.query.metadata }
5055
+ }),
5056
+ headers: {
5057
+ "Content-Type": "application/json"
5058
+ },
5059
+ method: "POST"
5060
+ }
5061
+ );
5002
5062
  if (response.ok) {
5003
5063
  const json = await response.json();
5004
5064
  return {
5005
5065
  threads: json.data.map((thread) => convertToThreadData(thread)),
5006
5066
  inboxNotifications: json.inboxNotifications.map(
5007
5067
  (notification) => convertToInboxNotificationData(notification)
5008
- )
5068
+ ),
5069
+ deletedThreads: json.deletedThreads.map(
5070
+ (info) => convertToThreadDeleteInfo(info)
5071
+ ),
5072
+ deletedInboxNotifications: json.deletedInboxNotifications.map(
5073
+ (info) => convertToInboxNotificationDeleteInfo(info)
5074
+ ),
5075
+ meta: {
5076
+ requestedAt: new Date(json.meta.requestedAt)
5077
+ }
5009
5078
  };
5010
5079
  } else if (response.status === 404) {
5011
- return { threads: [], inboxNotifications: [] };
5080
+ return {
5081
+ threads: [],
5082
+ inboxNotifications: [],
5083
+ deletedThreads: [],
5084
+ deletedInboxNotifications: [],
5085
+ meta: {
5086
+ requestedAt: /* @__PURE__ */ new Date()
5087
+ }
5088
+ };
5012
5089
  } else {
5013
5090
  throw new Error("There was an error while getting threads.");
5014
5091
  }
@@ -5215,6 +5292,9 @@ function createRoom(options, config) {
5215
5292
  opClock: 0,
5216
5293
  nodes: /* @__PURE__ */ new Map(),
5217
5294
  root: void 0,
5295
+ comments: {
5296
+ lastRequestedAt: null
5297
+ },
5218
5298
  undoStack: [],
5219
5299
  redoStack: [],
5220
5300
  pausedHistory: null,
@@ -5383,14 +5463,15 @@ function createRoom(options, config) {
5383
5463
  ydoc: makeEventSource(),
5384
5464
  comments: makeEventSource()
5385
5465
  };
5386
- async function fetchClientApi(roomId, endpoint, authValue, options2) {
5387
- const url = new URL(
5466
+ async function fetchClientApi(roomId, endpoint, authValue, options2, params) {
5467
+ const url = urljoin(
5468
+ config.baseUrl,
5388
5469
  `/v2/c/rooms/${encodeURIComponent(roomId)}${endpoint}`,
5389
- config.baseUrl
5470
+ params
5390
5471
  );
5391
5472
  const fetcher = config.polyfills?.fetch || /* istanbul ignore next */
5392
5473
  fetch;
5393
- return await fetcher(url.toString(), {
5474
+ return await fetcher(url, {
5394
5475
  ...options2,
5395
5476
  headers: {
5396
5477
  ...options2?.headers,
@@ -6284,6 +6365,14 @@ ${Array.from(traces).join("\n\n")}`
6284
6365
  // These exist only for our E2E testing app
6285
6366
  explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
6286
6367
  rawSend: (data) => managedSocket.send(data)
6368
+ },
6369
+ comments: {
6370
+ get lastRequestedAt() {
6371
+ return context.comments.lastRequestedAt;
6372
+ },
6373
+ set lastRequestedAt(value) {
6374
+ context.comments.lastRequestedAt = value;
6375
+ }
6287
6376
  }
6288
6377
  },
6289
6378
  id: config.roomId,
@@ -6466,28 +6555,6 @@ function createClientStore() {
6466
6555
  inboxNotifications: {},
6467
6556
  notificationSettings: {}
6468
6557
  });
6469
- function mergeThreads(existingThreads, newThreads) {
6470
- const updatedThreads = { ...existingThreads };
6471
- Object.entries(newThreads).forEach(([id, thread]) => {
6472
- const existingThread = updatedThreads[id];
6473
- if (existingThread) {
6474
- const result = compareThreads(existingThread, thread);
6475
- if (result === 1)
6476
- return;
6477
- }
6478
- updatedThreads[id] = thread;
6479
- });
6480
- return updatedThreads;
6481
- }
6482
- function mergeNotifications(existingInboxNotifications, newInboxNotifications) {
6483
- const inboxNotifications = Object.values({
6484
- ...existingInboxNotifications,
6485
- ...newInboxNotifications
6486
- });
6487
- return Object.fromEntries(
6488
- inboxNotifications.map((notification) => [notification.id, notification])
6489
- );
6490
- }
6491
6558
  return {
6492
6559
  ...store,
6493
6560
  deleteThread(threadId) {
@@ -6516,21 +6583,19 @@ function createClientStore() {
6516
6583
  };
6517
6584
  });
6518
6585
  },
6519
- updateThreadsAndNotifications(threads, inboxNotifications, queryKey) {
6586
+ updateThreadsAndNotifications(threads, inboxNotifications, deletedThreads, deletedInboxNotifications, queryKey) {
6520
6587
  store.set((state) => ({
6521
6588
  ...state,
6522
- threads: mergeThreads(
6523
- state.threads,
6524
- Object.fromEntries(threads.map((thread) => [thread.id, thread]))
6525
- ),
6526
- inboxNotifications: mergeNotifications(
6589
+ threads: applyThreadUpdates(state.threads, {
6590
+ newThreads: threads,
6591
+ deletedThreads
6592
+ }),
6593
+ inboxNotifications: applyNotificationsUpdates(
6527
6594
  state.inboxNotifications,
6528
- Object.fromEntries(
6529
- inboxNotifications.map((notification) => [
6530
- notification.id,
6531
- notification
6532
- ])
6533
- )
6595
+ {
6596
+ newInboxNotifications: inboxNotifications,
6597
+ deletedNotifications: deletedInboxNotifications
6598
+ }
6534
6599
  ),
6535
6600
  queries: queryKey !== void 0 ? {
6536
6601
  ...state.queries,
@@ -6540,6 +6605,21 @@ function createClientStore() {
6540
6605
  } : state.queries
6541
6606
  }));
6542
6607
  },
6608
+ updateRoomInboxNotificationSettings(roomId, settings, queryKey) {
6609
+ store.set((state) => ({
6610
+ ...state,
6611
+ notificationSettings: {
6612
+ ...state.notificationSettings,
6613
+ [roomId]: settings
6614
+ },
6615
+ queries: {
6616
+ ...state.queries,
6617
+ [queryKey]: {
6618
+ isLoading: false
6619
+ }
6620
+ }
6621
+ }));
6622
+ },
6543
6623
  pushOptimisticUpdate(optimisticUpdate) {
6544
6624
  store.set((state) => ({
6545
6625
  ...state,
@@ -6777,6 +6857,59 @@ function applyOptimisticUpdates(state) {
6777
6857
  }
6778
6858
  return result;
6779
6859
  }
6860
+ function applyThreadUpdates(existingThreads, updates) {
6861
+ const updatedThreads = { ...existingThreads };
6862
+ updates.newThreads.forEach((thread) => {
6863
+ const existingThread = updatedThreads[thread.id];
6864
+ if (existingThread) {
6865
+ const result = compareThreads(existingThread, thread);
6866
+ if (result === 1)
6867
+ return;
6868
+ }
6869
+ updatedThreads[thread.id] = thread;
6870
+ });
6871
+ updates.deletedThreads.forEach(({ id, deletedAt }) => {
6872
+ const existingThread = updatedThreads[id];
6873
+ if (existingThread === void 0)
6874
+ return;
6875
+ existingThread.deletedAt = deletedAt;
6876
+ existingThread.updatedAt = deletedAt;
6877
+ existingThread.comments = [];
6878
+ });
6879
+ return updatedThreads;
6880
+ }
6881
+ function applyNotificationsUpdates(existingInboxNotifications, updates) {
6882
+ const updatedInboxNotifications = { ...existingInboxNotifications };
6883
+ updates.newInboxNotifications.forEach((notification) => {
6884
+ const existingNotification = updatedInboxNotifications[notification.id];
6885
+ if (existingNotification) {
6886
+ const result = compareInboxNotifications(
6887
+ existingNotification,
6888
+ notification
6889
+ );
6890
+ if (result === 1)
6891
+ return;
6892
+ }
6893
+ updatedInboxNotifications[notification.id] = notification;
6894
+ });
6895
+ updates.deletedNotifications.forEach(
6896
+ ({ id }) => delete updatedInboxNotifications[id]
6897
+ );
6898
+ return updatedInboxNotifications;
6899
+ }
6900
+ function compareInboxNotifications(inboxNotificationA, inboxNotificationB) {
6901
+ if (inboxNotificationA.notifiedAt > inboxNotificationB.notifiedAt) {
6902
+ return 1;
6903
+ } else if (inboxNotificationA.notifiedAt < inboxNotificationB.notifiedAt) {
6904
+ return -1;
6905
+ }
6906
+ if (inboxNotificationA.readAt && inboxNotificationB.readAt) {
6907
+ return inboxNotificationA.readAt > inboxNotificationB.readAt ? 1 : inboxNotificationA.readAt < inboxNotificationB.readAt ? -1 : 0;
6908
+ } else if (inboxNotificationA.readAt || inboxNotificationB.readAt) {
6909
+ return inboxNotificationA.readAt ? 1 : -1;
6910
+ }
6911
+ return 0;
6912
+ }
6780
6913
 
6781
6914
  // src/client.ts
6782
6915
  var MIN_THROTTLE = 16;
@@ -6982,7 +7115,10 @@ function createClient(options) {
6982
7115
  resolveMentionSuggestions: clientOptions.resolveMentionSuggestions,
6983
7116
  cacheStore,
6984
7117
  usersStore,
6985
- roomsInfoStore
7118
+ roomsInfoStore,
7119
+ getRoomIds() {
7120
+ return Array.from(roomsById.keys());
7121
+ }
6986
7122
  }
6987
7123
  },
6988
7124
  kInternal,