@liveblocks/core 1.19.0-test1 → 2.0.0-alpha2

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.19.0-test1";
9
+ var PKG_VERSION = "2.0.0-alpha2";
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,8 @@ 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["ROOM_ID_UPDATED"] = 4006] = "ROOM_ID_UPDATED";
672
+ WebsocketCloseCodes2[WebsocketCloseCodes2["KICKED"] = 4100] = "KICKED";
664
673
  WebsocketCloseCodes2[WebsocketCloseCodes2["TOKEN_EXPIRED"] = 4109] = "TOKEN_EXPIRED";
665
674
  WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
666
675
  return WebsocketCloseCodes2;
@@ -679,22 +688,6 @@ function shouldRetryWithoutReauth(code) {
679
688
  function isIdle(status) {
680
689
  return status === "initial" || status === "disconnected";
681
690
  }
682
- function newToLegacyStatus(status) {
683
- switch (status) {
684
- case "connecting":
685
- return "connecting";
686
- case "connected":
687
- return "open";
688
- case "reconnecting":
689
- return "unavailable";
690
- case "disconnected":
691
- return "failed";
692
- case "initial":
693
- return "closed";
694
- default:
695
- return "closed";
696
- }
697
- }
698
691
  function toNewConnectionStatus(machine) {
699
692
  const state = machine.currentState;
700
693
  switch (state) {
@@ -728,16 +721,19 @@ var StopRetrying = class extends Error {
728
721
  }
729
722
  };
730
723
  var LiveblocksError = class extends Error {
724
+ /** @internal */
731
725
  constructor(message, code) {
732
726
  super(message);
733
727
  this.code = code;
734
728
  }
735
729
  };
736
- function nextBackoffDelay(currentDelay, delays = BACKOFF_DELAYS) {
730
+ function nextBackoffDelay(currentDelay, delays) {
737
731
  return delays.find((delay) => delay > currentDelay) ?? delays[delays.length - 1];
738
732
  }
739
733
  function increaseBackoffDelay(context) {
740
- context.patch({ backoffDelay: nextBackoffDelay(context.backoffDelay) });
734
+ context.patch({
735
+ backoffDelay: nextBackoffDelay(context.backoffDelay, BACKOFF_DELAYS)
736
+ });
741
737
  }
742
738
  function increaseBackoffDelayAggressively(context) {
743
739
  context.patch({
@@ -770,9 +766,13 @@ function logPrematureErrorOrCloseEvent(e) {
770
766
  };
771
767
  }
772
768
  function logCloseEvent(event) {
769
+ const details = [`code: ${event.code}`];
770
+ if (event.reason) {
771
+ details.push(`reason: ${event.reason}`);
772
+ }
773
773
  return (ctx) => {
774
774
  warn(
775
- `Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${ctx.backoffDelay}ms.`
775
+ `Connection to Liveblocks websocket server closed (${details.join(", ")}). Retrying in ${ctx.backoffDelay}ms.`
776
776
  );
777
777
  };
778
778
  }
@@ -884,8 +884,7 @@ function createConnectionStateMachine(delegates, options) {
884
884
  (okEvent) => ({
885
885
  target: "@connecting.busy",
886
886
  effect: assign({
887
- authValue: okEvent.data,
888
- backoffDelay: RESET_DELAY
887
+ authValue: okEvent.data
889
888
  })
890
889
  }),
891
890
  // Auth failed
@@ -942,14 +941,16 @@ function createConnectionStateMachine(delegates, options) {
942
941
  // When the "open" event happens, we're ready to transition to the
943
942
  // OK state. This is done by resolving the Promise.
944
943
  //
945
- async (ctx) => {
944
+ async (ctx, signal) => {
946
945
  let capturedPrematureEvent = null;
946
+ let unconfirmedSocket = null;
947
947
  const connect$ = new Promise(
948
948
  (resolve, rej) => {
949
949
  if (ctx.authValue === null) {
950
950
  throw new Error("No auth authValue");
951
951
  }
952
952
  const socket = delegates.createSocket(ctx.authValue);
953
+ unconfirmedSocket = socket;
953
954
  function reject(event) {
954
955
  capturedPrematureEvent = event;
955
956
  socket.removeEventListener("message", onSocketMessage);
@@ -1005,12 +1006,18 @@ function createConnectionStateMachine(delegates, options) {
1005
1006
  //
1006
1007
  ([socket, unsub]) => {
1007
1008
  unsub();
1009
+ if (signal.aborted) {
1010
+ throw new Error("Aborted");
1011
+ }
1008
1012
  if (capturedPrematureEvent) {
1009
1013
  throw capturedPrematureEvent;
1010
1014
  }
1011
1015
  return socket;
1012
1016
  }
1013
- );
1017
+ ).catch((e) => {
1018
+ teardownSocket(unconfirmedSocket);
1019
+ throw e;
1020
+ });
1014
1021
  },
1015
1022
  // Only transition to OK state after a successfully opened WebSocket connection
1016
1023
  (okEvent) => ({
@@ -1205,9 +1212,6 @@ var ManagedSocket = class {
1205
1212
  this.events = events;
1206
1213
  this.cleanups = cleanups;
1207
1214
  }
1208
- getLegacyStatus() {
1209
- return newToLegacyStatus(this.getStatus());
1210
- }
1211
1215
  getStatus() {
1212
1216
  try {
1213
1217
  return toNewConnectionStatus(this.machine);
@@ -1325,7 +1329,7 @@ function createAuthManager(authOptions) {
1325
1329
  }
1326
1330
  return false;
1327
1331
  }
1328
- function getCachedToken(requestedScope, roomId) {
1332
+ function getCachedToken(requestOptions) {
1329
1333
  const now = Math.ceil(Date.now() / 1e3);
1330
1334
  for (let i = tokens.length - 1; i >= 0; i--) {
1331
1335
  const token = tokens[i];
@@ -1338,8 +1342,15 @@ function createAuthManager(authOptions) {
1338
1342
  if (token.parsed.k === "id" /* ID_TOKEN */) {
1339
1343
  return token;
1340
1344
  } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
1345
+ if (!requestOptions.roomId && Object.entries(token.parsed.perms).length === 0) {
1346
+ return token;
1347
+ }
1341
1348
  for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
1342
- if (resource.includes("*") && roomId.startsWith(resource.replace("*", "")) || roomId === resource && hasCorrespondingScopes(requestedScope, scopes)) {
1349
+ if (!requestOptions.roomId) {
1350
+ if (resource.includes("*") && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
1351
+ return token;
1352
+ }
1353
+ } else if (resource.includes("*") && requestOptions.roomId.startsWith(resource.replace("*", "")) || requestOptions.roomId === resource && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
1343
1354
  return token;
1344
1355
  }
1345
1356
  }
@@ -1347,7 +1358,7 @@ function createAuthManager(authOptions) {
1347
1358
  }
1348
1359
  return void 0;
1349
1360
  }
1350
- async function makeAuthRequest(roomId) {
1361
+ async function makeAuthRequest(options) {
1351
1362
  const fetcher = authOptions.polyfills?.fetch ?? (typeof window === "undefined" ? void 0 : window.fetch);
1352
1363
  if (authentication.type === "private") {
1353
1364
  if (fetcher === void 0) {
@@ -1356,7 +1367,7 @@ function createAuthManager(authOptions) {
1356
1367
  );
1357
1368
  }
1358
1369
  const response = await fetchAuthEndpoint(fetcher, authentication.url, {
1359
- room: roomId
1370
+ room: options.roomId
1360
1371
  });
1361
1372
  const parsed = parseAuthToken(response.token);
1362
1373
  if (seenTokens.has(parsed.raw)) {
@@ -1367,10 +1378,11 @@ function createAuthManager(authOptions) {
1367
1378
  return parsed;
1368
1379
  }
1369
1380
  if (authentication.type === "custom") {
1370
- const response = await authentication.callback(roomId);
1381
+ const response = await authentication.callback(options.roomId);
1371
1382
  if (response && typeof response === "object") {
1372
1383
  if (typeof response.token === "string") {
1373
- return parseAuthToken(response.token);
1384
+ const parsed = parseAuthToken(response.token);
1385
+ return parsed;
1374
1386
  } else if (typeof response.error === "string") {
1375
1387
  const reason = `Authentication failed: ${"reason" in response && typeof response.reason === "string" ? response.reason : "Forbidden"}`;
1376
1388
  if (response.error === "forbidden") {
@@ -1388,18 +1400,27 @@ function createAuthManager(authOptions) {
1388
1400
  "Unexpected authentication type. Must be private or custom."
1389
1401
  );
1390
1402
  }
1391
- async function getAuthValue(requestedScope, roomId) {
1403
+ async function getAuthValue(requestOptions) {
1392
1404
  if (authentication.type === "public") {
1393
1405
  return { type: "public", publicApiKey: authentication.publicApiKey };
1394
1406
  }
1395
- const cachedToken = getCachedToken(requestedScope, roomId);
1407
+ const cachedToken = getCachedToken(requestOptions);
1396
1408
  if (cachedToken !== void 0) {
1397
1409
  return { type: "secret", token: cachedToken };
1398
1410
  }
1399
- let currentPromise = requestPromises.get(roomId);
1400
- if (currentPromise === void 0) {
1401
- currentPromise = makeAuthRequest(roomId);
1402
- requestPromises.set(roomId, currentPromise);
1411
+ let currentPromise;
1412
+ if (requestOptions.roomId) {
1413
+ currentPromise = requestPromises.get(requestOptions.roomId);
1414
+ if (currentPromise === void 0) {
1415
+ currentPromise = makeAuthRequest(requestOptions);
1416
+ requestPromises.set(requestOptions.roomId, currentPromise);
1417
+ }
1418
+ } else {
1419
+ currentPromise = requestPromises.get("liveblocks-user-token");
1420
+ if (currentPromise === void 0) {
1421
+ currentPromise = makeAuthRequest(requestOptions);
1422
+ requestPromises.set("liveblocks-user-token", currentPromise);
1423
+ }
1403
1424
  }
1404
1425
  try {
1405
1426
  const token = await currentPromise;
@@ -1412,7 +1433,11 @@ function createAuthManager(authOptions) {
1412
1433
  }
1413
1434
  return { type: "secret", token };
1414
1435
  } finally {
1415
- requestPromises.delete(roomId);
1436
+ if (requestOptions.roomId) {
1437
+ requestPromises.delete(requestOptions.roomId);
1438
+ } else {
1439
+ requestPromises.delete("liveblocks-user-token");
1440
+ }
1416
1441
  }
1417
1442
  }
1418
1443
  return {
@@ -1501,6 +1526,9 @@ async function fetchAuthEndpoint(fetch2, endpoint, body) {
1501
1526
  // src/constants.ts
1502
1527
  var DEFAULT_BASE_URL = "https://api.liveblocks.io";
1503
1528
 
1529
+ // src/internal.ts
1530
+ var kInternal = Symbol();
1531
+
1504
1532
  // src/devtools/bridge.ts
1505
1533
  var _bridgeActive = false;
1506
1534
  function activateBridge(allowed) {
@@ -1630,7 +1658,7 @@ function partialSyncStorage(room) {
1630
1658
  }
1631
1659
  }
1632
1660
  function partialSyncMe(room) {
1633
- const me = room.__internal.getSelf_forDevTools();
1661
+ const me = room[kInternal].getSelf_forDevTools();
1634
1662
  if (me) {
1635
1663
  sendToPanel({
1636
1664
  msg: "room::sync::partial",
@@ -1640,7 +1668,7 @@ function partialSyncMe(room) {
1640
1668
  }
1641
1669
  }
1642
1670
  function partialSyncOthers(room) {
1643
- const others = room.__internal.getOthers_forDevTools();
1671
+ const others = room[kInternal].getOthers_forDevTools();
1644
1672
  if (others) {
1645
1673
  sendToPanel({
1646
1674
  msg: "room::sync::partial",
@@ -1651,8 +1679,8 @@ function partialSyncOthers(room) {
1651
1679
  }
1652
1680
  function fullSync(room) {
1653
1681
  const root = room.getStorageSnapshot();
1654
- const me = room.__internal.getSelf_forDevTools();
1655
- const others = room.__internal.getOthers_forDevTools();
1682
+ const me = room[kInternal].getSelf_forDevTools();
1683
+ const others = room[kInternal].getOthers_forDevTools();
1656
1684
  room.fetchYDoc("");
1657
1685
  sendToPanel({
1658
1686
  msg: "room::sync::full",
@@ -1711,6 +1739,183 @@ function unlinkDevTools(roomId) {
1711
1739
  });
1712
1740
  }
1713
1741
 
1742
+ // src/lib/stringify.ts
1743
+ function stringify(object, ...args) {
1744
+ if (typeof object !== "object" || object === null || Array.isArray(object)) {
1745
+ return JSON.stringify(object, ...args);
1746
+ }
1747
+ const sortedObject = Object.keys(object).sort().reduce(
1748
+ (sortedObject2, key) => {
1749
+ sortedObject2[key] = object[key];
1750
+ return sortedObject2;
1751
+ },
1752
+ {}
1753
+ );
1754
+ return JSON.stringify(sortedObject, ...args);
1755
+ }
1756
+
1757
+ // src/lib/batch.ts
1758
+ var DEFAULT_SIZE = 50;
1759
+ var DEFAULT_DELAY = 100;
1760
+ var noop = () => {
1761
+ };
1762
+ var BatchCall = class {
1763
+ constructor(args) {
1764
+ this.resolve = noop;
1765
+ this.reject = noop;
1766
+ this.promise = new Promise(noop);
1767
+ this.args = args;
1768
+ }
1769
+ };
1770
+ var Batch = class {
1771
+ constructor(callback, options) {
1772
+ this.queue = [];
1773
+ this.error = false;
1774
+ this.callback = callback;
1775
+ this.size = options?.size ?? DEFAULT_SIZE;
1776
+ this.delay = options?.delay ?? DEFAULT_DELAY;
1777
+ }
1778
+ clearDelayTimeout() {
1779
+ if (this.delayTimeoutId !== void 0) {
1780
+ clearTimeout(this.delayTimeoutId);
1781
+ this.delayTimeoutId = void 0;
1782
+ }
1783
+ }
1784
+ schedule() {
1785
+ if (this.queue.length === this.size) {
1786
+ void this.flush();
1787
+ } else if (this.queue.length === 1) {
1788
+ this.clearDelayTimeout();
1789
+ this.delayTimeoutId = setTimeout(() => void this.flush(), this.delay);
1790
+ }
1791
+ }
1792
+ async flush() {
1793
+ if (this.queue.length === 0) {
1794
+ return;
1795
+ }
1796
+ const calls = this.queue.splice(0);
1797
+ const args = calls.map((call) => call.args);
1798
+ try {
1799
+ const results = await this.callback(args);
1800
+ this.error = false;
1801
+ calls.forEach((call, index) => {
1802
+ const result = results?.[index];
1803
+ if (!Array.isArray(results)) {
1804
+ call.reject(new Error("Callback must return an array."));
1805
+ } else if (calls.length !== results.length) {
1806
+ call.reject(
1807
+ new Error(
1808
+ `Callback must return an array of the same length as the number of provided items. Expected ${calls.length}, but got ${results.length}.`
1809
+ )
1810
+ );
1811
+ } else if (result instanceof Error) {
1812
+ call.reject(result);
1813
+ } else {
1814
+ call.resolve(result);
1815
+ }
1816
+ });
1817
+ } catch (error3) {
1818
+ this.error = true;
1819
+ calls.forEach((call) => {
1820
+ call.reject(error3);
1821
+ });
1822
+ }
1823
+ }
1824
+ get(...args) {
1825
+ const existingCall = this.queue.find(
1826
+ (call2) => stringify(call2.args) === stringify(args)
1827
+ );
1828
+ if (existingCall) {
1829
+ return existingCall.promise;
1830
+ }
1831
+ const call = new BatchCall(args);
1832
+ call.promise = new Promise((resolve, reject) => {
1833
+ call.resolve = resolve;
1834
+ call.reject = reject;
1835
+ });
1836
+ this.queue.push(call);
1837
+ this.schedule();
1838
+ return call.promise;
1839
+ }
1840
+ clear() {
1841
+ this.queue = [];
1842
+ this.error = false;
1843
+ this.clearDelayTimeout();
1844
+ }
1845
+ };
1846
+ function createBatchStore(callback, options) {
1847
+ const batch = new Batch(callback, options);
1848
+ const cache = /* @__PURE__ */ new Map();
1849
+ const eventSource2 = makeEventSource();
1850
+ function getCacheKey(args) {
1851
+ return stringify(args);
1852
+ }
1853
+ function setStateAndNotify(cacheKey, state) {
1854
+ if (state) {
1855
+ cache.set(cacheKey, state);
1856
+ } else {
1857
+ cache.delete(cacheKey);
1858
+ }
1859
+ eventSource2.notify(state);
1860
+ }
1861
+ async function get(...args) {
1862
+ const cacheKey = getCacheKey(args);
1863
+ if (cache.has(cacheKey)) {
1864
+ return;
1865
+ }
1866
+ try {
1867
+ setStateAndNotify(cacheKey, { isLoading: true });
1868
+ const result = await batch.get(...args);
1869
+ setStateAndNotify(cacheKey, { isLoading: false, data: result });
1870
+ } catch (error3) {
1871
+ setStateAndNotify(cacheKey, {
1872
+ isLoading: false,
1873
+ error: error3
1874
+ });
1875
+ }
1876
+ }
1877
+ function getState(...args) {
1878
+ const cacheKey = getCacheKey(args);
1879
+ return cache.get(cacheKey);
1880
+ }
1881
+ return {
1882
+ ...eventSource2,
1883
+ get,
1884
+ getState
1885
+ };
1886
+ }
1887
+
1888
+ // src/lib/create-store.ts
1889
+ function createStore(initialState) {
1890
+ let state = initialState;
1891
+ const subscribers = /* @__PURE__ */ new Set();
1892
+ function get() {
1893
+ return state;
1894
+ }
1895
+ function set(callback) {
1896
+ const newState = callback(state);
1897
+ if (state === newState) {
1898
+ return;
1899
+ }
1900
+ state = newState;
1901
+ for (const subscriber of subscribers) {
1902
+ subscriber(state);
1903
+ }
1904
+ }
1905
+ function subscribe(callback) {
1906
+ subscribers.add(callback);
1907
+ callback(state);
1908
+ return () => {
1909
+ subscribers.delete(callback);
1910
+ };
1911
+ }
1912
+ return {
1913
+ get,
1914
+ set,
1915
+ subscribe
1916
+ };
1917
+ }
1918
+
1714
1919
  // src/lib/deprecation.ts
1715
1920
  var _emittedDeprecationWarnings = /* @__PURE__ */ new Set();
1716
1921
  function deprecate(message, key = message) {
@@ -1744,886 +1949,538 @@ function errorIf(condition, message) {
1744
1949
  }
1745
1950
  }
1746
1951
 
1747
- // src/comments/comment-body.ts
1748
- function isCommentBodyParagraph(element) {
1749
- return "type" in element && element.type === "mention";
1952
+ // src/convert-plain-data.ts
1953
+ function convertToCommentData(data) {
1954
+ const editedAt = data.editedAt ? new Date(data.editedAt) : void 0;
1955
+ const createdAt = new Date(data.createdAt);
1956
+ const reactions = data.reactions.map((reaction) => ({
1957
+ ...reaction,
1958
+ createdAt: new Date(reaction.createdAt)
1959
+ }));
1960
+ if (data.body) {
1961
+ return {
1962
+ ...data,
1963
+ reactions,
1964
+ createdAt,
1965
+ editedAt
1966
+ };
1967
+ } else {
1968
+ const deletedAt = new Date(data.deletedAt);
1969
+ return {
1970
+ ...data,
1971
+ reactions,
1972
+ createdAt,
1973
+ editedAt,
1974
+ deletedAt
1975
+ };
1976
+ }
1750
1977
  }
1751
- function isCommentBodyText(element) {
1752
- return "text" in element && typeof element.text === "string";
1978
+ function convertToThreadData(data) {
1979
+ const updatedAt = data.updatedAt ? new Date(data.updatedAt) : void 0;
1980
+ const createdAt = new Date(data.createdAt);
1981
+ const comments = data.comments.map(
1982
+ (comment) => convertToCommentData(comment)
1983
+ );
1984
+ return {
1985
+ ...data,
1986
+ createdAt,
1987
+ updatedAt,
1988
+ comments
1989
+ };
1753
1990
  }
1754
- function isCommentBodyMention(element) {
1755
- return "type" in element && element.type === "mention";
1991
+ function convertToCommentUserReaction(data) {
1992
+ return {
1993
+ ...data,
1994
+ createdAt: new Date(data.createdAt)
1995
+ };
1756
1996
  }
1757
- function isCommentBodyLink(element) {
1758
- return "type" in element && element.type === "link";
1997
+ function convertToInboxNotificationData(data) {
1998
+ const notifiedAt = new Date(data.notifiedAt);
1999
+ const readAt = data.readAt ? new Date(data.readAt) : null;
2000
+ if ("activities" in data) {
2001
+ const activities = data.activities.map((activity) => ({
2002
+ ...activity,
2003
+ createdAt: new Date(activity.createdAt)
2004
+ }));
2005
+ return {
2006
+ ...data,
2007
+ notifiedAt,
2008
+ readAt,
2009
+ activities
2010
+ };
2011
+ }
2012
+ return {
2013
+ ...data,
2014
+ notifiedAt,
2015
+ readAt
2016
+ };
1759
2017
  }
1760
- var commentBodyElementsGuards = {
1761
- paragraph: isCommentBodyParagraph,
1762
- text: isCommentBodyText,
1763
- link: isCommentBodyLink,
1764
- mention: isCommentBodyMention
1765
- };
1766
- var commentBodyElementsTypes = {
1767
- paragraph: "block",
1768
- text: "inline",
1769
- link: "inline",
1770
- mention: "inline"
1771
- };
1772
- function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
1773
- if (!body || !body?.content) {
1774
- return;
2018
+ function convertToThreadDeleteInfo(data) {
2019
+ const deletedAt = new Date(data.deletedAt);
2020
+ return {
2021
+ ...data,
2022
+ deletedAt
2023
+ };
2024
+ }
2025
+ function convertToInboxNotificationDeleteInfo(data) {
2026
+ const deletedAt = new Date(data.deletedAt);
2027
+ return {
2028
+ ...data,
2029
+ deletedAt
2030
+ };
2031
+ }
2032
+
2033
+ // src/lib/url.ts
2034
+ function toURLSearchParams(params) {
2035
+ const result = new URLSearchParams();
2036
+ for (const [key, value] of Object.entries(params)) {
2037
+ if (value !== void 0 && value !== null) {
2038
+ result.set(key, value.toString());
2039
+ }
1775
2040
  }
1776
- const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
1777
- const type = element ? commentBodyElementsTypes[element] : "all";
1778
- const guard = element ? commentBodyElementsGuards[element] : () => true;
1779
- const visitor = typeof elementOrVisitor === "function" ? elementOrVisitor : possiblyVisitor;
1780
- for (const block of body.content) {
1781
- if (type === "all" || type === "block") {
1782
- if (guard(block)) {
1783
- visitor?.(block);
1784
- }
2041
+ return result;
2042
+ }
2043
+ function urljoin(baseUrl, path, params) {
2044
+ const url = new URL(path, baseUrl);
2045
+ if (params !== void 0) {
2046
+ url.search = (params instanceof URLSearchParams ? params : toURLSearchParams(params)).toString();
2047
+ }
2048
+ return url.toString();
2049
+ }
2050
+
2051
+ // src/notifications.ts
2052
+ var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY = 50;
2053
+ function createNotificationsApi({
2054
+ baseUrl,
2055
+ authManager,
2056
+ currentUserIdStore,
2057
+ fetcher
2058
+ }) {
2059
+ async function fetchJson(endpoint, options, params) {
2060
+ const authValue = await authManager.getAuthValue({
2061
+ requestedScope: "comments:read"
2062
+ });
2063
+ if (authValue.type === "secret" && authValue.token.parsed.k === "acc" /* ACCESS_TOKEN */) {
2064
+ const userId = authValue.token.parsed.uid;
2065
+ currentUserIdStore.set(() => userId);
1785
2066
  }
1786
- if (type === "all" || type === "inline") {
1787
- for (const inline of block.children) {
1788
- if (guard(inline)) {
1789
- visitor?.(inline);
2067
+ const url = urljoin(baseUrl, `/v2/c${endpoint}`, params);
2068
+ const response = await fetcher(url.toString(), {
2069
+ ...options,
2070
+ headers: {
2071
+ ...options?.headers,
2072
+ Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
2073
+ }
2074
+ });
2075
+ if (!response.ok) {
2076
+ if (response.status >= 400 && response.status < 600) {
2077
+ let error3;
2078
+ try {
2079
+ const errorBody = await response.json();
2080
+ error3 = new NotificationsApiError(
2081
+ errorBody.message,
2082
+ response.status,
2083
+ errorBody
2084
+ );
2085
+ } catch {
2086
+ error3 = new NotificationsApiError(
2087
+ response.statusText,
2088
+ response.status
2089
+ );
1790
2090
  }
2091
+ throw error3;
1791
2092
  }
1792
2093
  }
2094
+ let body;
2095
+ try {
2096
+ body = await response.json();
2097
+ } catch {
2098
+ body = {};
2099
+ }
2100
+ return body;
1793
2101
  }
1794
- }
1795
- function getMentionedIdsFromCommentBody(body) {
1796
- const mentionedIds = /* @__PURE__ */ new Set();
1797
- traverseCommentBody(
1798
- body,
1799
- "mention",
1800
- (mention) => mentionedIds.add(mention.id)
1801
- );
1802
- return Array.from(mentionedIds);
1803
- }
1804
- async function resolveUsersInCommentBody(body, resolveUsers) {
1805
- const resolvedUsers = /* @__PURE__ */ new Map();
1806
- if (!resolveUsers) {
1807
- return resolvedUsers;
2102
+ async function getInboxNotifications(options) {
2103
+ const json = await fetchJson("/inbox-notifications", void 0, {
2104
+ limit: options?.limit,
2105
+ since: options?.since?.toISOString()
2106
+ });
2107
+ return {
2108
+ threads: json.threads.map((thread) => convertToThreadData(thread)),
2109
+ inboxNotifications: json.inboxNotifications.map(
2110
+ (notification) => convertToInboxNotificationData(notification)
2111
+ ),
2112
+ deletedThreads: json.deletedThreads.map(
2113
+ (info) => convertToThreadDeleteInfo(info)
2114
+ ),
2115
+ deletedInboxNotifications: json.deletedInboxNotifications.map(
2116
+ (info) => convertToInboxNotificationDeleteInfo(info)
2117
+ ),
2118
+ meta: {
2119
+ requestedAt: new Date(json.meta.requestedAt)
2120
+ }
2121
+ };
1808
2122
  }
1809
- const userIds = getMentionedIdsFromCommentBody(body);
1810
- const users = await resolveUsers({
1811
- userIds
1812
- });
1813
- for (const [index, userId] of userIds.entries()) {
1814
- const user = users?.[index];
1815
- if (user) {
1816
- resolvedUsers.set(userId, user);
1817
- }
2123
+ async function getUnreadInboxNotificationsCount() {
2124
+ const { count } = await fetchJson("/inbox-notifications/count");
2125
+ return count;
1818
2126
  }
1819
- return resolvedUsers;
1820
- }
1821
- var htmlEscapables = {
1822
- "&": "&amp;",
1823
- "<": "&lt;",
1824
- ">": "&gt;",
1825
- '"': "&quot;",
1826
- "'": "&#39;"
1827
- };
1828
- var htmlEscapablesRegex = new RegExp(
1829
- Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
1830
- "g"
1831
- );
1832
- function htmlSafe(value) {
1833
- return new HtmlSafeString([String(value)], []);
1834
- }
1835
- function joinHtml(strings) {
1836
- if (strings.length <= 0) {
1837
- return new HtmlSafeString([""], []);
2127
+ async function markAllInboxNotificationsAsRead() {
2128
+ await fetchJson("/inbox-notifications/read", {
2129
+ method: "POST",
2130
+ headers: {
2131
+ "Content-Type": "application/json"
2132
+ },
2133
+ body: JSON.stringify({ inboxNotificationIds: "all" })
2134
+ });
1838
2135
  }
1839
- return new HtmlSafeString(
1840
- ["", ...Array(strings.length - 1).fill(""), ""],
1841
- strings
2136
+ async function markInboxNotificationsAsRead(inboxNotificationIds) {
2137
+ await fetchJson("/inbox-notifications/read", {
2138
+ method: "POST",
2139
+ headers: {
2140
+ "Content-Type": "application/json"
2141
+ },
2142
+ body: JSON.stringify({ inboxNotificationIds })
2143
+ });
2144
+ }
2145
+ const batchedMarkInboxNotificationsAsRead = new Batch(
2146
+ async (batchedInboxNotificationIds) => {
2147
+ const inboxNotificationIds = batchedInboxNotificationIds.flat();
2148
+ await markInboxNotificationsAsRead(inboxNotificationIds);
2149
+ return inboxNotificationIds;
2150
+ },
2151
+ { delay: MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY }
1842
2152
  );
1843
- }
1844
- function escapeHtml(value) {
1845
- if (value instanceof HtmlSafeString) {
1846
- return value.toString();
2153
+ async function markInboxNotificationAsRead(inboxNotificationId) {
2154
+ await batchedMarkInboxNotificationsAsRead.get(inboxNotificationId);
1847
2155
  }
1848
- if (Array.isArray(value)) {
1849
- return joinHtml(value).toString();
2156
+ return {
2157
+ getInboxNotifications,
2158
+ getUnreadInboxNotificationsCount,
2159
+ markAllInboxNotificationsAsRead,
2160
+ markInboxNotificationAsRead
2161
+ };
2162
+ }
2163
+
2164
+ // src/lib/position.ts
2165
+ var MIN_CODE = 32;
2166
+ var MAX_CODE = 126;
2167
+ var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
2168
+ var ZERO = nthDigit(0);
2169
+ var ONE = nthDigit(1);
2170
+ var ZERO_NINE = ZERO + nthDigit(-1);
2171
+ function nthDigit(n) {
2172
+ const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
2173
+ if (code < MIN_CODE || code > MAX_CODE) {
2174
+ throw new Error(`Invalid n value: ${n}`);
1850
2175
  }
1851
- return String(value).replace(
1852
- htmlEscapablesRegex,
1853
- (character) => htmlEscapables[character]
1854
- );
2176
+ return String.fromCharCode(code);
1855
2177
  }
1856
- var HtmlSafeString = class {
1857
- constructor(strings, values) {
1858
- this._strings = strings;
1859
- this._values = values;
2178
+ function makePosition(x, y) {
2179
+ if (x !== void 0 && y !== void 0) {
2180
+ return between(x, y);
2181
+ } else if (x !== void 0) {
2182
+ return after(x);
2183
+ } else if (y !== void 0) {
2184
+ return before(y);
2185
+ } else {
2186
+ return ONE;
1860
2187
  }
1861
- toString() {
1862
- return this._strings.reduce((result, str, i) => {
1863
- return result + escapeHtml(nn(this._values[i - 1])) + str;
1864
- });
2188
+ }
2189
+ function before(pos) {
2190
+ const lastIndex = pos.length - 1;
2191
+ for (let i = 0; i <= lastIndex; i++) {
2192
+ const code = pos.charCodeAt(i);
2193
+ if (code <= MIN_CODE) {
2194
+ continue;
2195
+ }
2196
+ if (i === lastIndex) {
2197
+ if (code === MIN_CODE + 1) {
2198
+ return pos.substring(0, i) + ZERO_NINE;
2199
+ } else {
2200
+ return pos.substring(0, i) + String.fromCharCode(code - 1);
2201
+ }
2202
+ } else {
2203
+ return pos.substring(0, i + 1);
2204
+ }
1865
2205
  }
1866
- };
1867
- function html(strings, ...values) {
1868
- return new HtmlSafeString(strings, values);
2206
+ return ONE;
1869
2207
  }
1870
- var markdownEscapables = {
1871
- _: "\\_",
1872
- "*": "\\*",
1873
- "#": "\\#",
1874
- "`": "\\`",
1875
- "~": "\\~",
1876
- "!": "\\!",
1877
- "|": "\\|",
1878
- "(": "\\(",
1879
- ")": "\\)",
1880
- "{": "\\{",
1881
- "}": "\\}",
1882
- "[": "\\[",
1883
- "]": "\\]"
1884
- };
1885
- var markdownEscapablesRegex = new RegExp(
1886
- Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
1887
- "g"
1888
- );
1889
- function joinMarkdown(strings) {
1890
- if (strings.length <= 0) {
1891
- return new MarkdownSafeString([""], []);
2208
+ function after(pos) {
2209
+ for (let i = 0; i <= pos.length - 1; i++) {
2210
+ const code = pos.charCodeAt(i);
2211
+ if (code >= MAX_CODE) {
2212
+ continue;
2213
+ }
2214
+ return pos.substring(0, i) + String.fromCharCode(code + 1);
1892
2215
  }
1893
- return new MarkdownSafeString(
1894
- ["", ...Array(strings.length - 1).fill(""), ""],
1895
- strings
1896
- );
2216
+ return pos + ONE;
1897
2217
  }
1898
- function escapeMarkdown(value) {
1899
- if (value instanceof MarkdownSafeString) {
1900
- return value.toString();
2218
+ function between(lo, hi) {
2219
+ if (lo < hi) {
2220
+ return _between(lo, hi);
2221
+ } else if (lo > hi) {
2222
+ return _between(hi, lo);
2223
+ } else {
2224
+ throw new Error("Cannot compute value between two equal positions");
1901
2225
  }
1902
- if (Array.isArray(value)) {
1903
- return joinMarkdown(value).toString();
2226
+ }
2227
+ function _between(lo, hi) {
2228
+ let index = 0;
2229
+ const loLen = lo.length;
2230
+ const hiLen = hi.length;
2231
+ while (true) {
2232
+ const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
2233
+ const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
2234
+ if (loCode === hiCode) {
2235
+ index++;
2236
+ continue;
2237
+ }
2238
+ if (hiCode - loCode === 1) {
2239
+ const size = index + 1;
2240
+ let prefix = lo.substring(0, size);
2241
+ if (prefix.length < size) {
2242
+ prefix += ZERO.repeat(size - prefix.length);
2243
+ }
2244
+ const suffix = lo.substring(size);
2245
+ const nines = "";
2246
+ return prefix + _between(suffix, nines);
2247
+ } else {
2248
+ return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
2249
+ }
1904
2250
  }
1905
- return String(value).replace(
1906
- markdownEscapablesRegex,
1907
- (character) => markdownEscapables[character]
1908
- );
1909
2251
  }
1910
- var MarkdownSafeString = class {
1911
- constructor(strings, values) {
1912
- this._strings = strings;
1913
- this._values = values;
2252
+ function takeN(pos, n) {
2253
+ return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
2254
+ }
2255
+ var MIN_NON_ZERO_CODE = MIN_CODE + 1;
2256
+ function isPos(str) {
2257
+ if (str === "") {
2258
+ return false;
1914
2259
  }
1915
- toString() {
1916
- return this._strings.reduce((result, str, i) => {
1917
- return result + escapeMarkdown(nn(this._values[i - 1])) + str;
1918
- });
2260
+ const lastIdx = str.length - 1;
2261
+ const last = str.charCodeAt(lastIdx);
2262
+ if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
2263
+ return false;
1919
2264
  }
1920
- };
1921
- function markdown(strings, ...values) {
1922
- return new MarkdownSafeString(strings, values);
1923
- }
1924
- function toAbsoluteUrl(url) {
1925
- if (url.startsWith("http://") || url.startsWith("https://")) {
1926
- return url;
1927
- } else if (url.startsWith("www.")) {
1928
- return "https://" + url;
2265
+ for (let i = 0; i < lastIdx; i++) {
2266
+ const code = str.charCodeAt(i);
2267
+ if (code < MIN_CODE || code > MAX_CODE) {
2268
+ return false;
2269
+ }
1929
2270
  }
1930
- return;
2271
+ return true;
1931
2272
  }
1932
- var stringifyCommentBodyPlainElements = {
1933
- paragraph: ({ children }) => children,
1934
- text: ({ element }) => element.text,
1935
- link: ({ element }) => element.url,
1936
- mention: ({ element, user }) => {
1937
- return `@${user?.name ?? element.id}`;
2273
+ function convertToPos(str) {
2274
+ const codes = [];
2275
+ for (let i = 0; i < str.length; i++) {
2276
+ const code = str.charCodeAt(i);
2277
+ codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
1938
2278
  }
1939
- };
1940
- var stringifyCommentBodyHtmlElements = {
1941
- paragraph: ({ children }) => {
1942
- return children ? html`<p>${htmlSafe(children)}</p>` : children;
1943
- },
1944
- text: ({ element }) => {
1945
- let children = element.text;
1946
- if (!children) {
1947
- return children;
1948
- }
1949
- if (element.bold) {
1950
- children = html`<strong>${children}</strong>`;
1951
- }
1952
- if (element.italic) {
1953
- children = html`<em>${children}</em>`;
1954
- }
1955
- if (element.strikethrough) {
1956
- children = html`<s>${children}</s>`;
1957
- }
1958
- if (element.code) {
1959
- children = html`<code>${children}</code>`;
1960
- }
1961
- return children;
1962
- },
1963
- link: ({ element, href }) => {
1964
- return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
1965
- },
1966
- mention: ({ element, user }) => {
1967
- return html`<span data-mention>@${user?.name ?? element.id}</span>`;
2279
+ while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
2280
+ codes.length--;
1968
2281
  }
1969
- };
1970
- var stringifyCommentBodyMarkdownElements = {
1971
- paragraph: ({ children }) => {
1972
- return children;
1973
- },
1974
- text: ({ element }) => {
1975
- let children = element.text;
1976
- if (!children) {
1977
- return children;
1978
- }
1979
- if (element.bold) {
1980
- children = markdown`**${children}**`;
1981
- }
1982
- if (element.italic) {
1983
- children = markdown`_${children}_`;
1984
- }
1985
- if (element.strikethrough) {
1986
- children = markdown`~~${children}~~`;
1987
- }
1988
- if (element.code) {
1989
- children = markdown`\`${children}\``;
1990
- }
1991
- return children;
1992
- },
1993
- link: ({ element, href }) => {
1994
- return markdown`[${element.url}](${href})`;
1995
- },
1996
- mention: ({ element, user }) => {
1997
- return markdown`@${user?.name ?? element.id}`;
1998
- }
1999
- };
2000
- async function stringifyCommentBody(body, options) {
2001
- const format = options?.format ?? "plain";
2002
- const separator = options?.separator ?? (format === "markdown" ? "\n\n" : "\n");
2003
- const elements = {
2004
- ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
2005
- ...options?.elements
2006
- };
2007
- const resolvedUsers = await resolveUsersInCommentBody(
2008
- body,
2009
- options?.resolveUsers
2282
+ return codes.length > 0 ? String.fromCharCode(...codes) : (
2283
+ // Edge case: the str was a 0-only string, which is invalid. Default back to .1
2284
+ ONE
2010
2285
  );
2011
- const blocks = body.content.flatMap((block, blockIndex) => {
2012
- switch (block.type) {
2013
- case "paragraph": {
2014
- const inlines = block.children.flatMap((inline, inlineIndex) => {
2015
- if (isCommentBodyMention(inline)) {
2016
- return inline.id ? [
2017
- elements.mention(
2018
- {
2019
- element: inline,
2020
- user: resolvedUsers.get(inline.id)
2021
- },
2022
- inlineIndex
2023
- )
2024
- ] : [];
2025
- }
2026
- if (isCommentBodyLink(inline)) {
2027
- return [
2028
- elements.link(
2029
- {
2030
- element: inline,
2031
- href: toAbsoluteUrl(inline.url) ?? inline.url
2032
- },
2033
- inlineIndex
2034
- )
2035
- ];
2036
- }
2037
- if (isCommentBodyText(inline)) {
2038
- return [elements.text({ element: inline }, inlineIndex)];
2039
- }
2040
- return [];
2041
- });
2042
- return [
2043
- elements.paragraph(
2044
- { element: block, children: inlines.join("") },
2045
- blockIndex
2046
- )
2047
- ];
2048
- }
2049
- default:
2050
- return [];
2051
- }
2052
- });
2053
- return blocks.join(separator);
2054
2286
  }
2055
- function convertToCommentData(data) {
2056
- const editedAt = data.editedAt ? new Date(data.editedAt) : void 0;
2057
- const createdAt = new Date(data.createdAt);
2058
- const reactions = data.reactions.map((reaction) => ({
2059
- ...reaction,
2060
- createdAt: new Date(reaction.createdAt)
2061
- }));
2062
- if (data.body) {
2063
- return {
2064
- ...data,
2065
- reactions,
2066
- createdAt,
2067
- editedAt
2068
- };
2069
- } else {
2070
- const deletedAt = new Date(data.deletedAt);
2071
- return {
2072
- ...data,
2073
- reactions,
2074
- createdAt,
2075
- editedAt,
2076
- deletedAt
2077
- };
2078
- }
2287
+ function asPos(str) {
2288
+ return isPos(str) ? str : convertToPos(str);
2079
2289
  }
2080
- function convertToThreadData(data) {
2081
- const updatedAt = data.updatedAt ? new Date(data.updatedAt) : void 0;
2082
- const createdAt = new Date(data.createdAt);
2083
- const comments = data.comments.map(
2084
- (comment) => convertToCommentData(comment)
2085
- );
2290
+
2291
+ // src/protocol/Op.ts
2292
+ var OpCode = /* @__PURE__ */ ((OpCode2) => {
2293
+ OpCode2[OpCode2["INIT"] = 0] = "INIT";
2294
+ OpCode2[OpCode2["SET_PARENT_KEY"] = 1] = "SET_PARENT_KEY";
2295
+ OpCode2[OpCode2["CREATE_LIST"] = 2] = "CREATE_LIST";
2296
+ OpCode2[OpCode2["UPDATE_OBJECT"] = 3] = "UPDATE_OBJECT";
2297
+ OpCode2[OpCode2["CREATE_OBJECT"] = 4] = "CREATE_OBJECT";
2298
+ OpCode2[OpCode2["DELETE_CRDT"] = 5] = "DELETE_CRDT";
2299
+ OpCode2[OpCode2["DELETE_OBJECT_KEY"] = 6] = "DELETE_OBJECT_KEY";
2300
+ OpCode2[OpCode2["CREATE_MAP"] = 7] = "CREATE_MAP";
2301
+ OpCode2[OpCode2["CREATE_REGISTER"] = 8] = "CREATE_REGISTER";
2302
+ return OpCode2;
2303
+ })(OpCode || {});
2304
+ function ackOp(opId) {
2086
2305
  return {
2087
- ...data,
2088
- createdAt,
2089
- updatedAt,
2090
- comments
2306
+ type: 5 /* DELETE_CRDT */,
2307
+ id: "ACK",
2308
+ // (H)ACK
2309
+ opId
2091
2310
  };
2092
2311
  }
2093
- function convertToCommentUserReaction(data) {
2094
- return {
2095
- ...data,
2096
- createdAt: new Date(data.createdAt)
2097
- };
2312
+ function isAckOp(op) {
2313
+ return op.type === 5 /* DELETE_CRDT */ && op.id === "ACK";
2098
2314
  }
2099
2315
 
2100
- // src/comments/index.ts
2101
- function getAuthBearerHeaderFromAuthValue(authValue) {
2102
- if (authValue.type === "public") {
2103
- return authValue.publicApiKey;
2104
- } else {
2105
- return authValue.token.raw;
2106
- }
2316
+ // src/crdts/AbstractCrdt.ts
2317
+ function crdtAsLiveNode(value) {
2318
+ return value;
2107
2319
  }
2108
- var CommentsApiError = class extends Error {
2109
- constructor(message, status, details) {
2110
- super(message);
2111
- this.message = message;
2112
- this.status = status;
2113
- this.details = details;
2320
+ function HasParent(node, key, pos = asPos(key)) {
2321
+ return Object.freeze({ type: "HasParent", node, key, pos });
2322
+ }
2323
+ var NoParent = Object.freeze({ type: "NoParent" });
2324
+ function Orphaned(oldKey, oldPos = asPos(oldKey)) {
2325
+ return Object.freeze({ type: "Orphaned", oldKey, oldPos });
2326
+ }
2327
+ var AbstractCrdt = class {
2328
+ constructor() {
2329
+ /** @internal */
2330
+ this._parent = NoParent;
2114
2331
  }
2115
- };
2116
- function createCommentsApi(roomId, getAuthValue, config) {
2117
- async function fetchJson(endpoint, options) {
2118
- const response = await fetchApi(roomId, endpoint, options);
2119
- if (!response.ok) {
2120
- if (response.status >= 400 && response.status < 600) {
2121
- let error3;
2122
- try {
2123
- const errorBody = await response.json();
2124
- error3 = new CommentsApiError(
2125
- errorBody.message,
2126
- response.status,
2127
- errorBody
2128
- );
2129
- } catch {
2130
- error3 = new CommentsApiError(response.statusText, response.status);
2131
- }
2132
- throw error3;
2133
- }
2332
+ /** @internal */
2333
+ _getParentKeyOrThrow() {
2334
+ switch (this.parent.type) {
2335
+ case "HasParent":
2336
+ return this.parent.key;
2337
+ case "NoParent":
2338
+ throw new Error("Parent key is missing");
2339
+ case "Orphaned":
2340
+ return this.parent.oldKey;
2341
+ default:
2342
+ return assertNever(this.parent, "Unknown state");
2134
2343
  }
2135
- let body;
2136
- try {
2137
- body = await response.json();
2138
- } catch {
2139
- body = {};
2344
+ }
2345
+ /** @internal */
2346
+ get _parentPos() {
2347
+ switch (this.parent.type) {
2348
+ case "HasParent":
2349
+ return this.parent.pos;
2350
+ case "NoParent":
2351
+ throw new Error("Parent key is missing");
2352
+ case "Orphaned":
2353
+ return this.parent.oldPos;
2354
+ default:
2355
+ return assertNever(this.parent, "Unknown state");
2140
2356
  }
2141
- return body;
2142
2357
  }
2143
- async function fetchApi(roomId2, endpoint, options) {
2144
- const authValue = await getAuthValue();
2145
- const url = new URL(
2146
- `/v2/c/rooms/${encodeURIComponent(roomId2)}${endpoint}`,
2147
- config.baseUrl
2148
- );
2149
- return await fetch(url.toString(), {
2150
- ...options,
2151
- headers: {
2152
- ...options?.headers,
2153
- Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
2154
- }
2155
- });
2358
+ /** @internal */
2359
+ get _pool() {
2360
+ return this.__pool;
2156
2361
  }
2157
- async function getThreads(options) {
2158
- const response = await fetchApi(roomId, "/threads/search", {
2159
- body: JSON.stringify({
2160
- ...options?.query?.metadata && { metadata: options.query.metadata }
2161
- }),
2162
- headers: {
2163
- "Content-Type": "application/json"
2164
- },
2165
- method: "POST"
2166
- });
2167
- if (response.ok) {
2168
- const json = await response.json();
2169
- return json.data.map((thread) => convertToThreadData(thread));
2170
- } else if (response.status === 404) {
2171
- return [];
2172
- } else {
2173
- throw new Error("There was an error while getting threads.");
2174
- }
2362
+ get roomId() {
2363
+ return this.__pool ? this.__pool.roomId : null;
2175
2364
  }
2176
- async function createThread({
2177
- metadata,
2178
- body,
2179
- commentId,
2180
- threadId
2181
- }) {
2182
- const thread = await fetchJson(
2183
- "/threads",
2184
- {
2185
- method: "POST",
2186
- headers: {
2187
- "Content-Type": "application/json"
2188
- },
2189
- body: JSON.stringify({
2190
- id: threadId,
2191
- comment: {
2192
- id: commentId,
2193
- body
2194
- },
2195
- metadata
2196
- })
2197
- }
2198
- );
2199
- return convertToThreadData(thread);
2365
+ /** @internal */
2366
+ get _id() {
2367
+ return this.__id;
2200
2368
  }
2201
- async function editThreadMetadata({
2202
- metadata,
2203
- threadId
2204
- }) {
2205
- return await fetchJson(
2206
- `/threads/${encodeURIComponent(threadId)}/metadata`,
2207
- {
2208
- method: "POST",
2209
- headers: {
2210
- "Content-Type": "application/json"
2211
- },
2212
- body: JSON.stringify(metadata)
2213
- }
2214
- );
2369
+ /** @internal */
2370
+ get parent() {
2371
+ return this._parent;
2215
2372
  }
2216
- async function createComment({
2217
- threadId,
2218
- commentId,
2219
- body
2220
- }) {
2221
- const comment = await fetchJson(
2222
- `/threads/${encodeURIComponent(threadId)}/comments`,
2223
- {
2224
- method: "POST",
2225
- headers: {
2226
- "Content-Type": "application/json"
2227
- },
2228
- body: JSON.stringify({
2229
- id: commentId,
2230
- body
2231
- })
2232
- }
2233
- );
2234
- return convertToCommentData(comment);
2373
+ /** @internal */
2374
+ get _parentKey() {
2375
+ switch (this.parent.type) {
2376
+ case "HasParent":
2377
+ return this.parent.key;
2378
+ case "NoParent":
2379
+ return null;
2380
+ case "Orphaned":
2381
+ return this.parent.oldKey;
2382
+ default:
2383
+ return assertNever(this.parent, "Unknown state");
2384
+ }
2235
2385
  }
2236
- async function editComment({
2237
- threadId,
2238
- commentId,
2239
- body
2240
- }) {
2241
- const comment = await fetchJson(
2242
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2243
- commentId
2244
- )}`,
2245
- {
2246
- method: "POST",
2247
- headers: {
2248
- "Content-Type": "application/json"
2249
- },
2250
- body: JSON.stringify({
2251
- body
2252
- })
2386
+ /** @internal */
2387
+ _apply(op, _isLocal) {
2388
+ switch (op.type) {
2389
+ case 5 /* DELETE_CRDT */: {
2390
+ if (this.parent.type === "HasParent") {
2391
+ return this.parent.node._detachChild(crdtAsLiveNode(this));
2392
+ }
2393
+ return { modified: false };
2253
2394
  }
2254
- );
2255
- return convertToCommentData(comment);
2395
+ }
2396
+ return { modified: false };
2256
2397
  }
2257
- async function deleteComment({
2258
- threadId,
2259
- commentId
2260
- }) {
2261
- await fetchJson(
2262
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2263
- commentId
2264
- )}`,
2265
- {
2266
- method: "DELETE"
2398
+ /** @internal */
2399
+ _setParentLink(newParentNode, newParentKey) {
2400
+ switch (this.parent.type) {
2401
+ case "HasParent":
2402
+ if (this.parent.node !== newParentNode) {
2403
+ throw new Error("Cannot set parent: node already has a parent");
2404
+ } else {
2405
+ this._parent = HasParent(newParentNode, newParentKey);
2406
+ return;
2407
+ }
2408
+ case "Orphaned":
2409
+ case "NoParent": {
2410
+ this._parent = HasParent(newParentNode, newParentKey);
2411
+ return;
2267
2412
  }
2268
- );
2413
+ default:
2414
+ return assertNever(this.parent, "Unknown state");
2415
+ }
2269
2416
  }
2270
- async function addReaction({
2271
- threadId,
2272
- commentId,
2273
- emoji
2274
- }) {
2275
- const reaction = await fetchJson(
2276
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2277
- commentId
2278
- )}/reactions`,
2279
- {
2280
- method: "POST",
2281
- headers: {
2282
- "Content-Type": "application/json"
2283
- },
2284
- body: JSON.stringify({ emoji })
2285
- }
2286
- );
2287
- return convertToCommentUserReaction(reaction);
2417
+ /** @internal */
2418
+ _attach(id, pool) {
2419
+ if (this.__id || this.__pool) {
2420
+ throw new Error("Cannot attach node: already attached");
2421
+ }
2422
+ pool.addNode(id, crdtAsLiveNode(this));
2423
+ this.__id = id;
2424
+ this.__pool = pool;
2288
2425
  }
2289
- async function removeReaction({
2290
- threadId,
2291
- commentId,
2292
- emoji
2293
- }) {
2294
- await fetchJson(
2295
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2296
- commentId
2297
- )}/reactions/${encodeURIComponent(emoji)}`,
2298
- {
2299
- method: "DELETE"
2426
+ /** @internal */
2427
+ _detach() {
2428
+ if (this.__pool && this.__id) {
2429
+ this.__pool.deleteNode(this.__id);
2430
+ }
2431
+ switch (this.parent.type) {
2432
+ case "HasParent": {
2433
+ this._parent = Orphaned(this.parent.key, this.parent.pos);
2434
+ break;
2300
2435
  }
2301
- );
2302
- }
2303
- return {
2304
- getThreads,
2305
- createThread,
2306
- editThreadMetadata,
2307
- createComment,
2308
- editComment,
2309
- deleteComment,
2310
- addReaction,
2311
- removeReaction
2312
- };
2313
- }
2314
-
2315
- // src/lib/position.ts
2316
- var MIN_CODE = 32;
2317
- var MAX_CODE = 126;
2318
- var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
2319
- var ZERO = nthDigit(0);
2320
- var ONE = nthDigit(1);
2321
- var ZERO_NINE = ZERO + nthDigit(-1);
2322
- function nthDigit(n) {
2323
- const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
2324
- if (code < MIN_CODE || code > MAX_CODE) {
2325
- throw new Error(`Invalid n value: ${n}`);
2326
- }
2327
- return String.fromCharCode(code);
2328
- }
2329
- function makePosition(x, y) {
2330
- if (x !== void 0 && y !== void 0) {
2331
- return between(x, y);
2332
- } else if (x !== void 0) {
2333
- return after(x);
2334
- } else if (y !== void 0) {
2335
- return before(y);
2336
- } else {
2337
- return ONE;
2338
- }
2339
- }
2340
- function before(pos) {
2341
- const lastIndex = pos.length - 1;
2342
- for (let i = 0; i <= lastIndex; i++) {
2343
- const code = pos.charCodeAt(i);
2344
- if (code <= MIN_CODE) {
2345
- continue;
2436
+ case "NoParent": {
2437
+ this._parent = NoParent;
2438
+ break;
2439
+ }
2440
+ case "Orphaned": {
2441
+ break;
2442
+ }
2443
+ default:
2444
+ assertNever(this.parent, "Unknown state");
2346
2445
  }
2347
- if (i === lastIndex) {
2348
- if (code === MIN_CODE + 1) {
2349
- return pos.substring(0, i) + ZERO_NINE;
2350
- } else {
2351
- return pos.substring(0, i) + String.fromCharCode(code - 1);
2446
+ this.__pool = void 0;
2447
+ }
2448
+ /**
2449
+ * @internal
2450
+ *
2451
+ * Clear the Immutable cache, so that the next call to `.toImmutable()` will
2452
+ * recompute the equivalent Immutable value again. Call this after every
2453
+ * mutation to the Live node.
2454
+ */
2455
+ invalidate() {
2456
+ if (this._cachedImmutable !== void 0 || this._cachedTreeNode !== void 0) {
2457
+ this._cachedImmutable = void 0;
2458
+ this._cachedTreeNode = void 0;
2459
+ if (this.parent.type === "HasParent") {
2460
+ this.parent.node.invalidate();
2352
2461
  }
2353
- } else {
2354
- return pos.substring(0, i + 1);
2355
2462
  }
2356
2463
  }
2357
- return ONE;
2358
- }
2359
- function after(pos) {
2360
- for (let i = 0; i <= pos.length - 1; i++) {
2361
- const code = pos.charCodeAt(i);
2362
- if (code >= MAX_CODE) {
2363
- continue;
2464
+ /**
2465
+ * @internal
2466
+ *
2467
+ * Return an snapshot of this Live tree for use in DevTools.
2468
+ */
2469
+ toTreeNode(key) {
2470
+ if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
2471
+ this._cachedTreeNodeKey = key;
2472
+ this._cachedTreeNode = this._toTreeNode(key);
2364
2473
  }
2365
- return pos.substring(0, i) + String.fromCharCode(code + 1);
2474
+ return this._cachedTreeNode;
2366
2475
  }
2367
- return pos + ONE;
2368
- }
2369
- function between(lo, hi) {
2370
- if (lo < hi) {
2371
- return _between(lo, hi);
2372
- } else if (lo > hi) {
2373
- return _between(hi, lo);
2374
- } else {
2375
- throw new Error("Cannot compute value between two equal positions");
2376
- }
2377
- }
2378
- function _between(lo, hi) {
2379
- let index = 0;
2380
- const loLen = lo.length;
2381
- const hiLen = hi.length;
2382
- while (true) {
2383
- const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
2384
- const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
2385
- if (loCode === hiCode) {
2386
- index++;
2387
- continue;
2388
- }
2389
- if (hiCode - loCode === 1) {
2390
- const size = index + 1;
2391
- let prefix = lo.substring(0, size);
2392
- if (prefix.length < size) {
2393
- prefix += ZERO.repeat(size - prefix.length);
2394
- }
2395
- const suffix = lo.substring(size);
2396
- const nines = "";
2397
- return prefix + _between(suffix, nines);
2398
- } else {
2399
- return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
2400
- }
2401
- }
2402
- }
2403
- function takeN(pos, n) {
2404
- return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
2405
- }
2406
- var MIN_NON_ZERO_CODE = MIN_CODE + 1;
2407
- function isPos(str) {
2408
- if (str === "") {
2409
- return false;
2410
- }
2411
- const lastIdx = str.length - 1;
2412
- const last = str.charCodeAt(lastIdx);
2413
- if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
2414
- return false;
2415
- }
2416
- for (let i = 0; i < lastIdx; i++) {
2417
- const code = str.charCodeAt(i);
2418
- if (code < MIN_CODE || code > MAX_CODE) {
2419
- return false;
2420
- }
2421
- }
2422
- return true;
2423
- }
2424
- function convertToPos(str) {
2425
- const codes = [];
2426
- for (let i = 0; i < str.length; i++) {
2427
- const code = str.charCodeAt(i);
2428
- codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
2429
- }
2430
- while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
2431
- codes.length--;
2432
- }
2433
- return codes.length > 0 ? String.fromCharCode(...codes) : (
2434
- // Edge case: the str was a 0-only string, which is invalid. Default back to .1
2435
- ONE
2436
- );
2437
- }
2438
- function asPos(str) {
2439
- return isPos(str) ? str : convertToPos(str);
2440
- }
2441
-
2442
- // src/protocol/Op.ts
2443
- var OpCode = /* @__PURE__ */ ((OpCode2) => {
2444
- OpCode2[OpCode2["INIT"] = 0] = "INIT";
2445
- OpCode2[OpCode2["SET_PARENT_KEY"] = 1] = "SET_PARENT_KEY";
2446
- OpCode2[OpCode2["CREATE_LIST"] = 2] = "CREATE_LIST";
2447
- OpCode2[OpCode2["UPDATE_OBJECT"] = 3] = "UPDATE_OBJECT";
2448
- OpCode2[OpCode2["CREATE_OBJECT"] = 4] = "CREATE_OBJECT";
2449
- OpCode2[OpCode2["DELETE_CRDT"] = 5] = "DELETE_CRDT";
2450
- OpCode2[OpCode2["DELETE_OBJECT_KEY"] = 6] = "DELETE_OBJECT_KEY";
2451
- OpCode2[OpCode2["CREATE_MAP"] = 7] = "CREATE_MAP";
2452
- OpCode2[OpCode2["CREATE_REGISTER"] = 8] = "CREATE_REGISTER";
2453
- return OpCode2;
2454
- })(OpCode || {});
2455
- function isAckOp(op) {
2456
- return op.type === 5 /* DELETE_CRDT */ && op.id === "ACK";
2457
- }
2458
-
2459
- // src/crdts/AbstractCrdt.ts
2460
- function crdtAsLiveNode(value) {
2461
- return value;
2462
- }
2463
- function HasParent(node, key, pos = asPos(key)) {
2464
- return Object.freeze({ type: "HasParent", node, key, pos });
2465
- }
2466
- var NoParent = Object.freeze({ type: "NoParent" });
2467
- function Orphaned(oldKey, oldPos = asPos(oldKey)) {
2468
- return Object.freeze({ type: "Orphaned", oldKey, oldPos });
2469
- }
2470
- var AbstractCrdt = class {
2471
- constructor() {
2472
- /** @internal */
2473
- this._parent = NoParent;
2474
- }
2475
- /** @internal */
2476
- _getParentKeyOrThrow() {
2477
- switch (this.parent.type) {
2478
- case "HasParent":
2479
- return this.parent.key;
2480
- case "NoParent":
2481
- throw new Error("Parent key is missing");
2482
- case "Orphaned":
2483
- return this.parent.oldKey;
2484
- default:
2485
- return assertNever(this.parent, "Unknown state");
2486
- }
2487
- }
2488
- /** @internal */
2489
- get _parentPos() {
2490
- switch (this.parent.type) {
2491
- case "HasParent":
2492
- return this.parent.pos;
2493
- case "NoParent":
2494
- throw new Error("Parent key is missing");
2495
- case "Orphaned":
2496
- return this.parent.oldPos;
2497
- default:
2498
- return assertNever(this.parent, "Unknown state");
2499
- }
2500
- }
2501
- /** @internal */
2502
- get _pool() {
2503
- return this.__pool;
2504
- }
2505
- get roomId() {
2506
- return this.__pool ? this.__pool.roomId : null;
2507
- }
2508
- /** @internal */
2509
- get _id() {
2510
- return this.__id;
2511
- }
2512
- /** @internal */
2513
- get parent() {
2514
- return this._parent;
2515
- }
2516
- /** @internal */
2517
- get _parentKey() {
2518
- switch (this.parent.type) {
2519
- case "HasParent":
2520
- return this.parent.key;
2521
- case "NoParent":
2522
- return null;
2523
- case "Orphaned":
2524
- return this.parent.oldKey;
2525
- default:
2526
- return assertNever(this.parent, "Unknown state");
2527
- }
2528
- }
2529
- /** @internal */
2530
- _apply(op, _isLocal) {
2531
- switch (op.type) {
2532
- case 5 /* DELETE_CRDT */: {
2533
- if (this.parent.type === "HasParent") {
2534
- return this.parent.node._detachChild(crdtAsLiveNode(this));
2535
- }
2536
- return { modified: false };
2537
- }
2538
- }
2539
- return { modified: false };
2540
- }
2541
- /** @internal */
2542
- _setParentLink(newParentNode, newParentKey) {
2543
- switch (this.parent.type) {
2544
- case "HasParent":
2545
- if (this.parent.node !== newParentNode) {
2546
- throw new Error("Cannot set parent: node already has a parent");
2547
- } else {
2548
- this._parent = HasParent(newParentNode, newParentKey);
2549
- return;
2550
- }
2551
- case "Orphaned":
2552
- case "NoParent": {
2553
- this._parent = HasParent(newParentNode, newParentKey);
2554
- return;
2555
- }
2556
- default:
2557
- return assertNever(this.parent, "Unknown state");
2558
- }
2559
- }
2560
- /** @internal */
2561
- _attach(id, pool) {
2562
- if (this.__id || this.__pool) {
2563
- throw new Error("Cannot attach node: already attached");
2564
- }
2565
- pool.addNode(id, crdtAsLiveNode(this));
2566
- this.__id = id;
2567
- this.__pool = pool;
2568
- }
2569
- /** @internal */
2570
- _detach() {
2571
- if (this.__pool && this.__id) {
2572
- this.__pool.deleteNode(this.__id);
2573
- }
2574
- switch (this.parent.type) {
2575
- case "HasParent": {
2576
- this._parent = Orphaned(this.parent.key, this.parent.pos);
2577
- break;
2578
- }
2579
- case "NoParent": {
2580
- this._parent = NoParent;
2581
- break;
2582
- }
2583
- case "Orphaned": {
2584
- break;
2585
- }
2586
- default:
2587
- assertNever(this.parent, "Unknown state");
2588
- }
2589
- this.__pool = void 0;
2590
- }
2591
- /**
2592
- * @internal
2593
- *
2594
- * Clear the Immutable cache, so that the next call to `.toImmutable()` will
2595
- * recompute the equivalent Immutable value again. Call this after every
2596
- * mutation to the Live node.
2597
- */
2598
- invalidate() {
2599
- if (this._cachedImmutable !== void 0 || this._cachedTreeNode !== void 0) {
2600
- this._cachedImmutable = void 0;
2601
- this._cachedTreeNode = void 0;
2602
- if (this.parent.type === "HasParent") {
2603
- this.parent.node.invalidate();
2604
- }
2605
- }
2606
- }
2607
- /**
2608
- * @internal
2609
- *
2610
- * Return an snapshot of this Live tree for use in DevTools.
2611
- */
2612
- toTreeNode(key) {
2613
- if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
2614
- this._cachedTreeNodeKey = key;
2615
- this._cachedTreeNode = this._toTreeNode(key);
2616
- }
2617
- return this._cachedTreeNode;
2618
- }
2619
- /**
2620
- * Return an immutable snapshot of this Live node and its children.
2621
- */
2622
- toImmutable() {
2623
- if (this._cachedImmutable === void 0) {
2624
- this._cachedImmutable = this._toImmutable();
2625
- }
2626
- return this._cachedImmutable;
2476
+ /**
2477
+ * Return an immutable snapshot of this Live node and its children.
2478
+ */
2479
+ toImmutable() {
2480
+ if (this._cachedImmutable === void 0) {
2481
+ this._cachedImmutable = this._toImmutable();
2482
+ }
2483
+ return this._cachedImmutable;
2627
2484
  }
2628
2485
  };
2629
2486
 
@@ -4097,19 +3954,6 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
4097
3954
 
4098
3955
  // src/crdts/LiveObject.ts
4099
3956
  var LiveObject = class _LiveObject extends AbstractCrdt {
4100
- constructor(obj = {}) {
4101
- super();
4102
- this._propToLastUpdate = /* @__PURE__ */ new Map();
4103
- for (const key in obj) {
4104
- const value = obj[key];
4105
- if (value === void 0) {
4106
- continue;
4107
- } else if (isLiveNode(value)) {
4108
- value._setParentLink(this, key);
4109
- }
4110
- }
4111
- this._map = new Map(Object.entries(obj));
4112
- }
4113
3957
  /** @internal */
4114
3958
  static _buildRootAndParentToChildren(items) {
4115
3959
  const parentToChildren = /* @__PURE__ */ new Map();
@@ -4141,10 +3985,22 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
4141
3985
  pool
4142
3986
  );
4143
3987
  }
4144
- /** @internal */
4145
- _toOps(parentId, parentKey, pool) {
4146
- if (this._id === void 0) {
4147
- throw new Error("Cannot serialize item is not attached");
3988
+ constructor(obj = {}) {
3989
+ super();
3990
+ this._propToLastUpdate = /* @__PURE__ */ new Map();
3991
+ const o = compactObject(obj);
3992
+ for (const key of Object.keys(o)) {
3993
+ const value = o[key];
3994
+ if (isLiveNode(value)) {
3995
+ value._setParentLink(this, key);
3996
+ }
3997
+ }
3998
+ this._map = new Map(Object.entries(o));
3999
+ }
4000
+ /** @internal */
4001
+ _toOps(parentId, parentKey, pool) {
4002
+ if (this._id === void 0) {
4003
+ throw new Error("Cannot serialize item is not attached");
4148
4004
  }
4149
4005
  const opId = pool?.generateOpId();
4150
4006
  const ops = [];
@@ -4886,6 +4742,115 @@ function isJsonObject(data) {
4886
4742
  return !isJsonScalar(data) && !isJsonArray(data);
4887
4743
  }
4888
4744
 
4745
+ // src/lib/objectToQuery.ts
4746
+ var identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
4747
+ function objectToQuery(obj) {
4748
+ let filterList = [];
4749
+ const entries2 = Object.entries(obj);
4750
+ const keyValuePairs = [];
4751
+ const keyValuePairsWithOperator = [];
4752
+ const indexedKeys = [];
4753
+ entries2.forEach(([key, value]) => {
4754
+ if (!identifierRegex.test(key)) {
4755
+ throw new Error("Key must only contain letters, numbers, _");
4756
+ }
4757
+ if (isSimpleValue(value)) {
4758
+ keyValuePairs.push([key, value]);
4759
+ } else if (isValueWithOperator(value)) {
4760
+ keyValuePairsWithOperator.push([key, value]);
4761
+ } else if (typeof value === "object" && !("startsWith" in value)) {
4762
+ indexedKeys.push([key, value]);
4763
+ }
4764
+ });
4765
+ filterList = [
4766
+ ...getFiltersFromKeyValuePairs(keyValuePairs),
4767
+ ...getFiltersFromKeyValuePairsWithOperator(keyValuePairsWithOperator)
4768
+ ];
4769
+ indexedKeys.forEach(([key, value]) => {
4770
+ const nestedEntries = Object.entries(value);
4771
+ const nKeyValuePairs = [];
4772
+ const nKeyValuePairsWithOperator = [];
4773
+ nestedEntries.forEach(([nestedKey, nestedValue]) => {
4774
+ if (isStringEmpty(nestedKey)) {
4775
+ throw new Error("Key cannot be empty");
4776
+ }
4777
+ if (isSimpleValue(nestedValue)) {
4778
+ nKeyValuePairs.push([formatFilterKey(key, nestedKey), nestedValue]);
4779
+ } else if (isValueWithOperator(nestedValue)) {
4780
+ nKeyValuePairsWithOperator.push([
4781
+ formatFilterKey(key, nestedKey),
4782
+ nestedValue
4783
+ ]);
4784
+ }
4785
+ });
4786
+ filterList = [
4787
+ ...filterList,
4788
+ ...getFiltersFromKeyValuePairs(nKeyValuePairs),
4789
+ ...getFiltersFromKeyValuePairsWithOperator(nKeyValuePairsWithOperator)
4790
+ ];
4791
+ });
4792
+ return filterList.map(
4793
+ ({ key, operator, value }) => formatFilter(key, operator, formatFilterValue(value))
4794
+ ).join(" AND ");
4795
+ }
4796
+ var getFiltersFromKeyValuePairs = (keyValuePairs) => {
4797
+ const filters = [];
4798
+ keyValuePairs.forEach(([key, value]) => {
4799
+ filters.push({
4800
+ key,
4801
+ operator: ":",
4802
+ value
4803
+ });
4804
+ });
4805
+ return filters;
4806
+ };
4807
+ var getFiltersFromKeyValuePairsWithOperator = (keyValuePairsWithOperator) => {
4808
+ const filters = [];
4809
+ keyValuePairsWithOperator.forEach(([key, value]) => {
4810
+ if ("startsWith" in value && typeof value.startsWith === "string") {
4811
+ filters.push({
4812
+ key,
4813
+ operator: "^",
4814
+ value: value.startsWith
4815
+ });
4816
+ }
4817
+ });
4818
+ return filters;
4819
+ };
4820
+ var isSimpleValue = (value) => {
4821
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4822
+ return true;
4823
+ }
4824
+ return false;
4825
+ };
4826
+ var isValueWithOperator = (value) => {
4827
+ if (typeof value === "object" && value !== null && "startsWith" in value) {
4828
+ return true;
4829
+ }
4830
+ return false;
4831
+ };
4832
+ var formatFilter = (key, operator, value) => {
4833
+ return `${key}${operator}${value}`;
4834
+ };
4835
+ var formatFilterKey = (key, nestedKey) => {
4836
+ if (nestedKey) {
4837
+ return `${key}[${JSON.stringify(nestedKey)}]`;
4838
+ }
4839
+ return key;
4840
+ };
4841
+ var formatFilterValue = (value) => {
4842
+ if (typeof value === "string") {
4843
+ if (isStringEmpty(value)) {
4844
+ throw new Error("Value cannot be empty");
4845
+ }
4846
+ return JSON.stringify(value);
4847
+ }
4848
+ return value.toString();
4849
+ };
4850
+ var isStringEmpty = (value) => {
4851
+ return !value || value.toString().trim() === "";
4852
+ };
4853
+
4889
4854
  // src/protocol/ClientMsg.ts
4890
4855
  var ClientMsgCode = /* @__PURE__ */ ((ClientMsgCode2) => {
4891
4856
  ClientMsgCode2[ClientMsgCode2["UPDATE_PRESENCE"] = 100] = "UPDATE_PRESENCE";
@@ -5129,7 +5094,13 @@ function userToTreeNode(key, user) {
5129
5094
  type: "User",
5130
5095
  id: `${user.connectionId}`,
5131
5096
  key,
5132
- payload: user
5097
+ payload: {
5098
+ connectionId: user.connectionId,
5099
+ id: user.id,
5100
+ info: user.info,
5101
+ presence: user.presence,
5102
+ isReadOnly: !user.canWrite
5103
+ }
5133
5104
  };
5134
5105
  }
5135
5106
  function installBackgroundTabSpy() {
@@ -5148,6 +5119,246 @@ function installBackgroundTabSpy() {
5148
5119
  };
5149
5120
  return [inBackgroundSince, unsub];
5150
5121
  }
5122
+ var CommentsApiError = class extends Error {
5123
+ constructor(message, status, details) {
5124
+ super(message);
5125
+ this.message = message;
5126
+ this.status = status;
5127
+ this.details = details;
5128
+ }
5129
+ };
5130
+ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5131
+ async function fetchCommentsApi(endpoint, params, options) {
5132
+ const authValue = await getAuthValue();
5133
+ return fetchClientApi(roomId, endpoint, authValue, options, params);
5134
+ }
5135
+ async function fetchJson(endpoint, options, params) {
5136
+ const response = await fetchCommentsApi(endpoint, params, options);
5137
+ if (!response.ok) {
5138
+ if (response.status >= 400 && response.status < 600) {
5139
+ let error3;
5140
+ try {
5141
+ const errorBody = await response.json();
5142
+ error3 = new CommentsApiError(
5143
+ errorBody.message,
5144
+ response.status,
5145
+ errorBody
5146
+ );
5147
+ } catch {
5148
+ error3 = new CommentsApiError(response.statusText, response.status);
5149
+ }
5150
+ throw error3;
5151
+ }
5152
+ }
5153
+ let body;
5154
+ try {
5155
+ body = await response.json();
5156
+ } catch {
5157
+ body = {};
5158
+ }
5159
+ return body;
5160
+ }
5161
+ async function getThreads(options) {
5162
+ let query;
5163
+ if (options?.query) {
5164
+ query = objectToQuery(options.query);
5165
+ }
5166
+ const response = await fetchCommentsApi(
5167
+ "/threads",
5168
+ {
5169
+ since: options?.since?.toISOString(),
5170
+ query
5171
+ },
5172
+ {
5173
+ headers: {
5174
+ "Content-Type": "application/json"
5175
+ }
5176
+ }
5177
+ );
5178
+ if (response.ok) {
5179
+ const json = await response.json();
5180
+ return {
5181
+ threads: json.data.map((thread) => convertToThreadData(thread)),
5182
+ inboxNotifications: json.inboxNotifications.map(
5183
+ (notification) => convertToInboxNotificationData(notification)
5184
+ ),
5185
+ deletedThreads: json.deletedThreads.map(
5186
+ (info) => convertToThreadDeleteInfo(info)
5187
+ ),
5188
+ deletedInboxNotifications: json.deletedInboxNotifications.map(
5189
+ (info) => convertToInboxNotificationDeleteInfo(info)
5190
+ ),
5191
+ meta: {
5192
+ requestedAt: new Date(json.meta.requestedAt)
5193
+ }
5194
+ };
5195
+ } else if (response.status === 404) {
5196
+ return {
5197
+ threads: [],
5198
+ inboxNotifications: [],
5199
+ deletedThreads: [],
5200
+ deletedInboxNotifications: [],
5201
+ meta: {
5202
+ requestedAt: /* @__PURE__ */ new Date()
5203
+ }
5204
+ };
5205
+ } else {
5206
+ throw new Error("There was an error while getting threads.");
5207
+ }
5208
+ }
5209
+ async function getThread({ threadId }) {
5210
+ const response = await fetchCommentsApi(
5211
+ `/thread-with-notification/${threadId}`
5212
+ );
5213
+ if (response.ok) {
5214
+ const json = await response.json();
5215
+ return {
5216
+ thread: convertToThreadData(json.thread),
5217
+ inboxNotification: json.inboxNotification ? convertToInboxNotificationData(json.inboxNotification) : void 0
5218
+ };
5219
+ } else if (response.status === 404) {
5220
+ return;
5221
+ } else {
5222
+ throw new Error(`There was an error while getting thread ${threadId}.`);
5223
+ }
5224
+ }
5225
+ async function createThread({
5226
+ metadata,
5227
+ body,
5228
+ commentId,
5229
+ threadId
5230
+ }) {
5231
+ const thread = await fetchJson("/threads", {
5232
+ method: "POST",
5233
+ headers: {
5234
+ "Content-Type": "application/json"
5235
+ },
5236
+ body: JSON.stringify({
5237
+ id: threadId,
5238
+ comment: {
5239
+ id: commentId,
5240
+ body
5241
+ },
5242
+ metadata
5243
+ })
5244
+ });
5245
+ return convertToThreadData(thread);
5246
+ }
5247
+ async function editThreadMetadata({
5248
+ metadata,
5249
+ threadId
5250
+ }) {
5251
+ return await fetchJson(
5252
+ `/threads/${encodeURIComponent(threadId)}/metadata`,
5253
+ {
5254
+ method: "POST",
5255
+ headers: {
5256
+ "Content-Type": "application/json"
5257
+ },
5258
+ body: JSON.stringify(metadata)
5259
+ }
5260
+ );
5261
+ }
5262
+ async function createComment({
5263
+ threadId,
5264
+ commentId,
5265
+ body
5266
+ }) {
5267
+ const comment = await fetchJson(
5268
+ `/threads/${encodeURIComponent(threadId)}/comments`,
5269
+ {
5270
+ method: "POST",
5271
+ headers: {
5272
+ "Content-Type": "application/json"
5273
+ },
5274
+ body: JSON.stringify({
5275
+ id: commentId,
5276
+ body
5277
+ })
5278
+ }
5279
+ );
5280
+ return convertToCommentData(comment);
5281
+ }
5282
+ async function editComment({
5283
+ threadId,
5284
+ commentId,
5285
+ body
5286
+ }) {
5287
+ const comment = await fetchJson(
5288
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5289
+ commentId
5290
+ )}`,
5291
+ {
5292
+ method: "POST",
5293
+ headers: {
5294
+ "Content-Type": "application/json"
5295
+ },
5296
+ body: JSON.stringify({
5297
+ body
5298
+ })
5299
+ }
5300
+ );
5301
+ return convertToCommentData(comment);
5302
+ }
5303
+ async function deleteComment2({
5304
+ threadId,
5305
+ commentId
5306
+ }) {
5307
+ await fetchJson(
5308
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5309
+ commentId
5310
+ )}`,
5311
+ {
5312
+ method: "DELETE"
5313
+ }
5314
+ );
5315
+ }
5316
+ async function addReaction2({
5317
+ threadId,
5318
+ commentId,
5319
+ emoji
5320
+ }) {
5321
+ const reaction = await fetchJson(
5322
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5323
+ commentId
5324
+ )}/reactions`,
5325
+ {
5326
+ method: "POST",
5327
+ headers: {
5328
+ "Content-Type": "application/json"
5329
+ },
5330
+ body: JSON.stringify({ emoji })
5331
+ }
5332
+ );
5333
+ return convertToCommentUserReaction(reaction);
5334
+ }
5335
+ async function removeReaction2({
5336
+ threadId,
5337
+ commentId,
5338
+ emoji
5339
+ }) {
5340
+ await fetchJson(
5341
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5342
+ commentId
5343
+ )}/reactions/${encodeURIComponent(emoji)}`,
5344
+ {
5345
+ method: "DELETE"
5346
+ }
5347
+ );
5348
+ }
5349
+ return {
5350
+ getThreads,
5351
+ getThread,
5352
+ createThread,
5353
+ editThreadMetadata,
5354
+ createComment,
5355
+ editComment,
5356
+ deleteComment: deleteComment2,
5357
+ addReaction: addReaction2,
5358
+ removeReaction: removeReaction2
5359
+ };
5360
+ }
5361
+ var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY2 = 50;
5151
5362
  function createRoom(options, config) {
5152
5363
  const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
5153
5364
  const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
@@ -5209,7 +5420,7 @@ function createRoom(options, config) {
5209
5420
  function onStatusDidChange(newStatus) {
5210
5421
  const authValue = managedSocket.authValue;
5211
5422
  if (authValue !== null) {
5212
- const tokenKey = authValue.type === "secret" ? authValue.token.raw : authValue.publicApiKey;
5423
+ const tokenKey = getAuthBearerHeaderFromAuthValue(authValue);
5213
5424
  if (tokenKey !== lastTokenKey) {
5214
5425
  lastTokenKey = tokenKey;
5215
5426
  if (authValue.type === "secret") {
@@ -5312,7 +5523,9 @@ function createRoom(options, config) {
5312
5523
  }
5313
5524
  }
5314
5525
  if (activeBatch) {
5315
- activeBatch.ops.push(...ops);
5526
+ for (const op of ops) {
5527
+ activeBatch.ops.push(op);
5528
+ }
5316
5529
  for (const [key, value] of storageUpdates) {
5317
5530
  activeBatch.updates.storageUpdates.set(
5318
5531
  key,
@@ -5346,8 +5559,6 @@ function createRoom(options, config) {
5346
5559
  }
5347
5560
  };
5348
5561
  const eventHub = {
5349
- connection: makeEventSource(),
5350
- // Old/deprecated API
5351
5562
  status: makeEventSource(),
5352
5563
  // New/recommended API
5353
5564
  lostConnection: makeEventSource(),
@@ -5363,27 +5574,89 @@ function createRoom(options, config) {
5363
5574
  ydoc: makeEventSource(),
5364
5575
  comments: makeEventSource()
5365
5576
  };
5577
+ async function fetchClientApi(roomId, endpoint, authValue, options2, params) {
5578
+ const url = urljoin(
5579
+ config.baseUrl,
5580
+ `/v2/c/rooms/${encodeURIComponent(roomId)}${endpoint}`,
5581
+ params
5582
+ );
5583
+ const fetcher = config.polyfills?.fetch || /* istanbul ignore next */
5584
+ fetch;
5585
+ return await fetcher(url, {
5586
+ ...options2,
5587
+ headers: {
5588
+ ...options2?.headers,
5589
+ Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
5590
+ }
5591
+ });
5592
+ }
5593
+ async function streamFetch(authValue, roomId) {
5594
+ return fetchClientApi(roomId, "/storage", authValue, {
5595
+ method: "GET",
5596
+ headers: {
5597
+ "Content-Type": "application/json"
5598
+ }
5599
+ });
5600
+ }
5366
5601
  async function httpPostToRoom(endpoint, body) {
5367
5602
  if (!managedSocket.authValue) {
5368
5603
  throw new Error("Not authorized");
5369
5604
  }
5370
- const authTokenOrPublicApiKey = managedSocket.authValue.type === "public" ? managedSocket.authValue.publicApiKey : managedSocket.authValue.token.raw;
5371
- const url = new URL(
5372
- `/v2/c/rooms/${encodeURIComponent(config.roomId)}${endpoint}`,
5373
- config.baseUrl
5374
- ).toString();
5375
- const fetcher = config.polyfills?.fetch || /* istanbul ignore next */
5376
- fetch;
5377
- return fetcher(url, {
5605
+ return fetchClientApi(config.roomId, endpoint, managedSocket.authValue, {
5378
5606
  method: "POST",
5379
5607
  headers: {
5380
- "Content-Type": "application/json",
5381
- Authorization: `Bearer ${authTokenOrPublicApiKey}`
5608
+ "Content-Type": "application/json"
5382
5609
  },
5383
5610
  body: JSON.stringify(body)
5384
5611
  });
5385
5612
  }
5386
- function sendMessages(messages) {
5613
+ async function createTextMention(userId, mentionId) {
5614
+ if (!managedSocket.authValue) {
5615
+ throw new Error("Not authorized");
5616
+ }
5617
+ return fetchClientApi(
5618
+ config.roomId,
5619
+ "/text-mentions",
5620
+ managedSocket.authValue,
5621
+ {
5622
+ method: "POST",
5623
+ headers: {
5624
+ "Content-Type": "application/json"
5625
+ },
5626
+ body: JSON.stringify({
5627
+ userId,
5628
+ mentionId
5629
+ })
5630
+ }
5631
+ );
5632
+ }
5633
+ async function deleteTextMention(mentionId) {
5634
+ if (!managedSocket.authValue) {
5635
+ throw new Error("Not authorized");
5636
+ }
5637
+ return fetchClientApi(
5638
+ config.roomId,
5639
+ `/text-mentions/${mentionId}`,
5640
+ managedSocket.authValue,
5641
+ {
5642
+ method: "DELETE"
5643
+ }
5644
+ );
5645
+ }
5646
+ async function reportTextEditor(type, rootKey) {
5647
+ const authValue = await delegates.authenticate();
5648
+ return fetchClientApi(config.roomId, "/text-metadata", authValue, {
5649
+ method: "POST",
5650
+ headers: {
5651
+ "Content-Type": "application/json"
5652
+ },
5653
+ body: JSON.stringify({
5654
+ type,
5655
+ rootKey
5656
+ })
5657
+ });
5658
+ }
5659
+ function sendMessages(messages) {
5387
5660
  const serializedPayload = JSON.stringify(messages);
5388
5661
  const nonce = context.dynamicSessionInfo.current?.nonce;
5389
5662
  if (config.unstable_fallbackToHTTP && nonce) {
@@ -5419,9 +5692,7 @@ function createRoom(options, config) {
5419
5692
  info: staticSession.userInfo,
5420
5693
  presence: myPresence,
5421
5694
  canWrite,
5422
- canComment: canComment(dynamicSession.scopes),
5423
- isReadOnly: !canWrite
5424
- // Deprecated, kept for backward-compatibility
5695
+ canComment: canComment(dynamicSession.scopes)
5425
5696
  };
5426
5697
  }
5427
5698
  }
@@ -5834,12 +6105,7 @@ function createRoom(options, config) {
5834
6105
  break;
5835
6106
  }
5836
6107
  case 200 /* INITIAL_STORAGE_STATE */: {
5837
- const unacknowledgedOps = new Map(context.unacknowledgedOps);
5838
- createOrUpdateRootFromMessage(message, doNotBatchUpdates);
5839
- applyAndSendOps(unacknowledgedOps, doNotBatchUpdates);
5840
- _resolveStoragePromise?.();
5841
- notifyStorageStatus();
5842
- eventHub.storageDidLoad.notify();
6108
+ processInitialStorage(message);
5843
6109
  break;
5844
6110
  }
5845
6111
  case 201 /* UPDATE_STORAGE */: {
@@ -5980,14 +6246,35 @@ ${Array.from(traces).join("\n\n")}`
5980
6246
  flushNowOrSoon();
5981
6247
  }
5982
6248
  function dispatchOps(ops) {
5983
- context.buffer.storageOperations.push(...ops);
6249
+ const { storageOperations } = context.buffer;
6250
+ for (const op of ops) {
6251
+ storageOperations.push(op);
6252
+ }
5984
6253
  flushNowOrSoon();
5985
6254
  }
5986
6255
  let _getStorage$ = null;
5987
6256
  let _resolveStoragePromise = null;
6257
+ function processInitialStorage(message) {
6258
+ const unacknowledgedOps = new Map(context.unacknowledgedOps);
6259
+ createOrUpdateRootFromMessage(message, doNotBatchUpdates);
6260
+ applyAndSendOps(unacknowledgedOps, doNotBatchUpdates);
6261
+ _resolveStoragePromise?.();
6262
+ notifyStorageStatus();
6263
+ eventHub.storageDidLoad.notify();
6264
+ }
6265
+ async function streamStorage() {
6266
+ if (!managedSocket.authValue) {
6267
+ return;
6268
+ }
6269
+ const result = await streamFetch(managedSocket.authValue, config.roomId);
6270
+ const items = await result.json();
6271
+ processInitialStorage({ type: 200 /* INITIAL_STORAGE_STATE */, items });
6272
+ }
5988
6273
  function refreshStorage(options2) {
5989
6274
  const messages = context.buffer.messages;
5990
- if (!messages.some((msg) => msg.type === 200 /* FETCH_STORAGE */)) {
6275
+ if (config.unstable_streamData) {
6276
+ void streamStorage();
6277
+ } else if (!messages.some((msg) => msg.type === 200 /* FETCH_STORAGE */)) {
5991
6278
  messages.push({ type: 200 /* FETCH_STORAGE */ });
5992
6279
  }
5993
6280
  if (options2.flush) {
@@ -6165,372 +6452,1303 @@ ${Array.from(traces).join("\n\n")}`
6165
6452
  ydoc: eventHub.ydoc.observable,
6166
6453
  comments: eventHub.comments.observable
6167
6454
  };
6168
- const commentsApi = createCommentsApi(config.roomId, delegates.authenticate, {
6169
- baseUrl: config.baseUrl
6455
+ const commentsApi = createCommentsApi(
6456
+ config.roomId,
6457
+ delegates.authenticate,
6458
+ fetchClientApi
6459
+ );
6460
+ async function fetchNotificationsJson(endpoint, options2) {
6461
+ const authValue = await delegates.authenticate();
6462
+ const response = await fetchClientApi(
6463
+ config.roomId,
6464
+ endpoint,
6465
+ authValue,
6466
+ options2
6467
+ );
6468
+ if (!response.ok) {
6469
+ if (response.status >= 400 && response.status < 600) {
6470
+ let error3;
6471
+ try {
6472
+ const errorBody = await response.json();
6473
+ error3 = new NotificationsApiError(
6474
+ errorBody.message,
6475
+ response.status,
6476
+ errorBody
6477
+ );
6478
+ } catch {
6479
+ error3 = new NotificationsApiError(
6480
+ response.statusText,
6481
+ response.status
6482
+ );
6483
+ }
6484
+ throw error3;
6485
+ }
6486
+ }
6487
+ let body;
6488
+ try {
6489
+ body = await response.json();
6490
+ } catch {
6491
+ body = {};
6492
+ }
6493
+ return body;
6494
+ }
6495
+ function getRoomNotificationSettings() {
6496
+ return fetchNotificationsJson(
6497
+ "/notification-settings"
6498
+ );
6499
+ }
6500
+ function updateRoomNotificationSettings(settings) {
6501
+ return fetchNotificationsJson(
6502
+ "/notification-settings",
6503
+ {
6504
+ method: "POST",
6505
+ body: JSON.stringify(settings),
6506
+ headers: {
6507
+ "Content-Type": "application/json"
6508
+ }
6509
+ }
6510
+ );
6511
+ }
6512
+ async function markInboxNotificationsAsRead(inboxNotificationIds) {
6513
+ await fetchNotificationsJson("/inbox-notifications/read", {
6514
+ method: "POST",
6515
+ headers: {
6516
+ "Content-Type": "application/json"
6517
+ },
6518
+ body: JSON.stringify({ inboxNotificationIds })
6519
+ });
6520
+ }
6521
+ const batchedMarkInboxNotificationsAsRead = new Batch(
6522
+ async (batchedInboxNotificationIds) => {
6523
+ const inboxNotificationIds = batchedInboxNotificationIds.flat();
6524
+ await markInboxNotificationsAsRead(inboxNotificationIds);
6525
+ return inboxNotificationIds;
6526
+ },
6527
+ { delay: MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY2 }
6528
+ );
6529
+ async function markInboxNotificationAsRead(inboxNotificationId) {
6530
+ await batchedMarkInboxNotificationsAsRead.get(inboxNotificationId);
6531
+ }
6532
+ return Object.defineProperty(
6533
+ {
6534
+ [kInternal]: {
6535
+ get presenceBuffer() {
6536
+ return deepClone(context.buffer.presenceUpdates?.data ?? null);
6537
+ },
6538
+ // prettier-ignore
6539
+ get undoStack() {
6540
+ return deepClone(context.undoStack);
6541
+ },
6542
+ // prettier-ignore
6543
+ get nodeCount() {
6544
+ return context.nodes.size;
6545
+ },
6546
+ // prettier-ignore
6547
+ // send metadata when using a text editor
6548
+ reportTextEditor,
6549
+ // create a text mention when using a text editor
6550
+ createTextMention,
6551
+ // delete a text mention when using a text editor
6552
+ deleteTextMention,
6553
+ // Support for the Liveblocks browser extension
6554
+ getSelf_forDevTools: () => selfAsTreeNode.current,
6555
+ getOthers_forDevTools: () => others_forDevTools.current,
6556
+ // prettier-ignore
6557
+ simulate: {
6558
+ // These exist only for our E2E testing app
6559
+ explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
6560
+ rawSend: (data) => managedSocket.send(data)
6561
+ },
6562
+ comments: {
6563
+ ...commentsApi
6564
+ },
6565
+ notifications: {
6566
+ getRoomNotificationSettings,
6567
+ updateRoomNotificationSettings,
6568
+ markInboxNotificationAsRead
6569
+ }
6570
+ },
6571
+ id: config.roomId,
6572
+ subscribe: makeClassicSubscribeFn(events),
6573
+ connect: () => managedSocket.connect(),
6574
+ reconnect: () => managedSocket.reconnect(),
6575
+ disconnect: () => managedSocket.disconnect(),
6576
+ destroy: () => {
6577
+ uninstallBgTabSpy();
6578
+ managedSocket.destroy();
6579
+ },
6580
+ // Presence
6581
+ updatePresence,
6582
+ updateYDoc,
6583
+ broadcastEvent,
6584
+ // Storage
6585
+ batch,
6586
+ history: {
6587
+ undo,
6588
+ redo,
6589
+ canUndo,
6590
+ canRedo,
6591
+ clear,
6592
+ pause: pauseHistory,
6593
+ resume: resumeHistory
6594
+ },
6595
+ fetchYDoc,
6596
+ getStorage,
6597
+ getStorageSnapshot,
6598
+ getStorageStatus,
6599
+ events,
6600
+ // Core
6601
+ getStatus: () => managedSocket.getStatus(),
6602
+ getSelf: () => self.current,
6603
+ // Presence
6604
+ getPresence: () => context.myPresence.current,
6605
+ getOthers: () => context.others.current
6606
+ },
6607
+ // Explictly make the internal field non-enumerable, to avoid aggressive
6608
+ // freezing when used with Immer
6609
+ kInternal,
6610
+ { enumerable: false }
6611
+ );
6612
+ }
6613
+ function makeClassicSubscribeFn(events) {
6614
+ function subscribeToLiveStructureDeeply(node, callback) {
6615
+ return events.storage.subscribe((updates) => {
6616
+ const relatedUpdates = updates.filter(
6617
+ (update) => isSameNodeOrChildOf(update.node, node)
6618
+ );
6619
+ if (relatedUpdates.length > 0) {
6620
+ callback(relatedUpdates);
6621
+ }
6622
+ });
6623
+ }
6624
+ function subscribeToLiveStructureShallowly(node, callback) {
6625
+ return events.storage.subscribe((updates) => {
6626
+ for (const update of updates) {
6627
+ if (update.node._id === node._id) {
6628
+ callback(update.node);
6629
+ }
6630
+ }
6631
+ });
6632
+ }
6633
+ function subscribe(first, second, options) {
6634
+ if (typeof first === "string" && isRoomEventName(first)) {
6635
+ if (typeof second !== "function") {
6636
+ throw new Error("Second argument must be a callback function");
6637
+ }
6638
+ const callback = second;
6639
+ switch (first) {
6640
+ case "event":
6641
+ return events.customEvent.subscribe(
6642
+ callback
6643
+ );
6644
+ case "my-presence":
6645
+ return events.myPresence.subscribe(callback);
6646
+ case "others": {
6647
+ const cb = callback;
6648
+ return events.others.subscribe((event) => {
6649
+ const { others, ...internalEvent } = event;
6650
+ return cb(others, internalEvent);
6651
+ });
6652
+ }
6653
+ case "error":
6654
+ return events.error.subscribe(callback);
6655
+ case "status":
6656
+ return events.status.subscribe(callback);
6657
+ case "lost-connection":
6658
+ return events.lostConnection.subscribe(
6659
+ callback
6660
+ );
6661
+ case "history":
6662
+ return events.history.subscribe(callback);
6663
+ case "storage-status":
6664
+ return events.storageStatus.subscribe(
6665
+ callback
6666
+ );
6667
+ default:
6668
+ return assertNever(
6669
+ first,
6670
+ `"${String(first)}" is not a valid event name`
6671
+ );
6672
+ }
6673
+ }
6674
+ if (second === void 0 || typeof first === "function") {
6675
+ if (typeof first === "function") {
6676
+ const storageCallback = first;
6677
+ return events.storage.subscribe(storageCallback);
6678
+ } else {
6679
+ throw new Error("Please specify a listener callback");
6680
+ }
6681
+ }
6682
+ if (isLiveNode(first)) {
6683
+ const node = first;
6684
+ if (options?.isDeep) {
6685
+ const storageCallback = second;
6686
+ return subscribeToLiveStructureDeeply(node, storageCallback);
6687
+ } else {
6688
+ const nodeCallback = second;
6689
+ return subscribeToLiveStructureShallowly(node, nodeCallback);
6690
+ }
6691
+ }
6692
+ throw new Error(
6693
+ `${String(first)} is not a value that can be subscribed to.`
6694
+ );
6695
+ }
6696
+ return subscribe;
6697
+ }
6698
+ function isRoomEventName(value) {
6699
+ return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "history" || value === "status" || value === "storage-status" || value === "lost-connection" || value === "connection";
6700
+ }
6701
+ function makeAuthDelegateForRoom(roomId, authManager) {
6702
+ return async () => {
6703
+ return authManager.getAuthValue({ requestedScope: "room:read", roomId });
6704
+ };
6705
+ }
6706
+ function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
6707
+ return (authValue) => {
6708
+ const ws = WebSocketPolyfill ?? (typeof WebSocket === "undefined" ? void 0 : WebSocket);
6709
+ if (ws === void 0) {
6710
+ throw new StopRetrying(
6711
+ "To use Liveblocks client in a non-DOM environment, you need to provide a WebSocket polyfill."
6712
+ );
6713
+ }
6714
+ const url = new URL(baseUrl);
6715
+ url.protocol = url.protocol === "http:" ? "ws" : "wss";
6716
+ url.pathname = "/v7";
6717
+ url.searchParams.set("roomId", roomId);
6718
+ if (authValue.type === "secret") {
6719
+ url.searchParams.set("tok", authValue.token.raw);
6720
+ } else if (authValue.type === "public") {
6721
+ url.searchParams.set("pubkey", authValue.publicApiKey);
6722
+ } else {
6723
+ return assertNever(authValue, "Unhandled case");
6724
+ }
6725
+ url.searchParams.set("version", PKG_VERSION || "dev");
6726
+ return new ws(url.toString());
6727
+ };
6728
+ }
6729
+
6730
+ // src/store.ts
6731
+ function createClientStore() {
6732
+ const store = createStore({
6733
+ threads: {},
6734
+ queries: {},
6735
+ optimisticUpdates: [],
6736
+ inboxNotifications: {},
6737
+ notificationSettings: {}
6738
+ });
6739
+ const optimisticUpdatesEventSource = makeEventSource();
6740
+ return {
6741
+ ...store,
6742
+ deleteThread(threadId) {
6743
+ store.set((state) => {
6744
+ return {
6745
+ ...state,
6746
+ threads: deleteKeyImmutable(state.threads, threadId),
6747
+ inboxNotifications: Object.fromEntries(
6748
+ Object.entries(state.inboxNotifications).filter(
6749
+ ([_id, notification]) => notification.kind === "thread" && notification.threadId === threadId
6750
+ )
6751
+ )
6752
+ };
6753
+ });
6754
+ },
6755
+ updateThreadAndNotification(thread, inboxNotification) {
6756
+ store.set((state) => {
6757
+ const existingThread = state.threads[thread.id];
6758
+ return {
6759
+ ...state,
6760
+ threads: existingThread === void 0 || compareThreads(thread, existingThread) === 1 ? { ...state.threads, [thread.id]: thread } : state.threads,
6761
+ inboxNotifications: inboxNotification === void 0 ? state.inboxNotifications : {
6762
+ ...state.inboxNotifications,
6763
+ [inboxNotification.id]: inboxNotification
6764
+ }
6765
+ };
6766
+ });
6767
+ },
6768
+ updateThreadsAndNotifications(threads, inboxNotifications, deletedThreads, deletedInboxNotifications, queryKey) {
6769
+ store.set((state) => ({
6770
+ ...state,
6771
+ threads: applyThreadUpdates(state.threads, {
6772
+ newThreads: threads,
6773
+ deletedThreads
6774
+ }),
6775
+ inboxNotifications: applyNotificationsUpdates(
6776
+ state.inboxNotifications,
6777
+ {
6778
+ newInboxNotifications: inboxNotifications,
6779
+ deletedNotifications: deletedInboxNotifications
6780
+ }
6781
+ ),
6782
+ queries: queryKey !== void 0 ? {
6783
+ ...state.queries,
6784
+ [queryKey]: {
6785
+ isLoading: false
6786
+ }
6787
+ } : state.queries
6788
+ }));
6789
+ },
6790
+ updateRoomInboxNotificationSettings(roomId, settings, queryKey) {
6791
+ store.set((state) => ({
6792
+ ...state,
6793
+ notificationSettings: {
6794
+ ...state.notificationSettings,
6795
+ [roomId]: settings
6796
+ },
6797
+ queries: {
6798
+ ...state.queries,
6799
+ [queryKey]: {
6800
+ isLoading: false
6801
+ }
6802
+ }
6803
+ }));
6804
+ },
6805
+ pushOptimisticUpdate(optimisticUpdate) {
6806
+ optimisticUpdatesEventSource.notify(optimisticUpdate);
6807
+ store.set((state) => ({
6808
+ ...state,
6809
+ optimisticUpdates: [...state.optimisticUpdates, optimisticUpdate]
6810
+ }));
6811
+ },
6812
+ setQueryState(queryKey, queryState) {
6813
+ store.set((state) => ({
6814
+ ...state,
6815
+ queries: {
6816
+ ...state.queries,
6817
+ [queryKey]: queryState
6818
+ }
6819
+ }));
6820
+ },
6821
+ optimisticUpdatesEventSource
6822
+ };
6823
+ }
6824
+ function deleteKeyImmutable(record, key) {
6825
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
6826
+ const { [key]: _toDelete, ...rest } = record;
6827
+ return rest;
6828
+ }
6829
+ return record;
6830
+ }
6831
+ function compareThreads(thread1, thread2) {
6832
+ if (thread1.updatedAt && thread2.updatedAt) {
6833
+ return thread1.updatedAt > thread2.updatedAt ? 1 : thread1.updatedAt < thread2.updatedAt ? -1 : 0;
6834
+ } else if (thread1.updatedAt || thread2.updatedAt) {
6835
+ return thread1.updatedAt ? 1 : -1;
6836
+ }
6837
+ if (thread1.createdAt > thread2.createdAt) {
6838
+ return 1;
6839
+ } else if (thread1.createdAt < thread2.createdAt) {
6840
+ return -1;
6841
+ }
6842
+ return 0;
6843
+ }
6844
+ function applyOptimisticUpdates(state) {
6845
+ const result = {
6846
+ threads: {
6847
+ ...state.threads
6848
+ },
6849
+ inboxNotifications: {
6850
+ ...state.inboxNotifications
6851
+ },
6852
+ notificationSettings: {
6853
+ ...state.notificationSettings
6854
+ }
6855
+ };
6856
+ for (const optimisticUpdate of state.optimisticUpdates) {
6857
+ switch (optimisticUpdate.type) {
6858
+ case "create-thread": {
6859
+ result.threads[optimisticUpdate.thread.id] = optimisticUpdate.thread;
6860
+ break;
6861
+ }
6862
+ case "edit-thread-metadata": {
6863
+ const thread = result.threads[optimisticUpdate.threadId];
6864
+ if (thread === void 0) {
6865
+ break;
6866
+ }
6867
+ if (thread.deletedAt !== void 0) {
6868
+ break;
6869
+ }
6870
+ if (thread.updatedAt !== void 0 && thread.updatedAt > optimisticUpdate.updatedAt) {
6871
+ break;
6872
+ }
6873
+ result.threads[thread.id] = {
6874
+ ...thread,
6875
+ updatedAt: optimisticUpdate.updatedAt,
6876
+ metadata: {
6877
+ ...thread.metadata,
6878
+ ...optimisticUpdate.metadata
6879
+ }
6880
+ };
6881
+ break;
6882
+ }
6883
+ case "create-comment": {
6884
+ const thread = result.threads[optimisticUpdate.comment.threadId];
6885
+ if (thread === void 0) {
6886
+ break;
6887
+ }
6888
+ result.threads[thread.id] = upsertComment(
6889
+ thread,
6890
+ optimisticUpdate.comment
6891
+ );
6892
+ const inboxNotification = Object.values(result.inboxNotifications).find(
6893
+ (notification) => notification.kind === "thread" && notification.threadId === thread.id
6894
+ );
6895
+ if (inboxNotification === void 0) {
6896
+ break;
6897
+ }
6898
+ result.inboxNotifications[inboxNotification.id] = {
6899
+ ...inboxNotification,
6900
+ notifiedAt: optimisticUpdate.comment.createdAt,
6901
+ readAt: optimisticUpdate.comment.createdAt
6902
+ };
6903
+ break;
6904
+ }
6905
+ case "edit-comment": {
6906
+ const thread = result.threads[optimisticUpdate.comment.threadId];
6907
+ if (thread === void 0) {
6908
+ break;
6909
+ }
6910
+ result.threads[thread.id] = upsertComment(
6911
+ thread,
6912
+ optimisticUpdate.comment
6913
+ );
6914
+ break;
6915
+ }
6916
+ case "delete-comment": {
6917
+ const thread = result.threads[optimisticUpdate.threadId];
6918
+ if (thread === void 0) {
6919
+ break;
6920
+ }
6921
+ result.threads[thread.id] = deleteComment(
6922
+ thread,
6923
+ optimisticUpdate.commentId,
6924
+ optimisticUpdate.deletedAt
6925
+ );
6926
+ break;
6927
+ }
6928
+ case "add-reaction": {
6929
+ const thread = result.threads[optimisticUpdate.threadId];
6930
+ if (thread === void 0) {
6931
+ break;
6932
+ }
6933
+ result.threads[thread.id] = addReaction(
6934
+ thread,
6935
+ optimisticUpdate.commentId,
6936
+ optimisticUpdate.reaction
6937
+ );
6938
+ break;
6939
+ }
6940
+ case "remove-reaction": {
6941
+ const thread = result.threads[optimisticUpdate.threadId];
6942
+ if (thread === void 0) {
6943
+ break;
6944
+ }
6945
+ result.threads[thread.id] = removeReaction(
6946
+ thread,
6947
+ optimisticUpdate.commentId,
6948
+ optimisticUpdate.emoji,
6949
+ optimisticUpdate.userId,
6950
+ optimisticUpdate.removedAt
6951
+ );
6952
+ break;
6953
+ }
6954
+ case "mark-inbox-notification-as-read": {
6955
+ result.inboxNotifications[optimisticUpdate.inboxNotificationId] = {
6956
+ ...state.inboxNotifications[optimisticUpdate.inboxNotificationId],
6957
+ readAt: optimisticUpdate.readAt
6958
+ };
6959
+ break;
6960
+ }
6961
+ case "mark-inbox-notifications-as-read": {
6962
+ for (const id in result.inboxNotifications) {
6963
+ result.inboxNotifications[id] = {
6964
+ ...result.inboxNotifications[id],
6965
+ readAt: optimisticUpdate.readAt
6966
+ };
6967
+ }
6968
+ break;
6969
+ }
6970
+ case "update-notification-settings": {
6971
+ result.notificationSettings[optimisticUpdate.roomId] = {
6972
+ ...result.notificationSettings[optimisticUpdate.roomId],
6973
+ ...optimisticUpdate.settings
6974
+ };
6975
+ }
6976
+ }
6977
+ }
6978
+ return result;
6979
+ }
6980
+ function applyThreadUpdates(existingThreads, updates) {
6981
+ const updatedThreads = { ...existingThreads };
6982
+ updates.newThreads.forEach((thread) => {
6983
+ const existingThread = updatedThreads[thread.id];
6984
+ if (existingThread) {
6985
+ const result = compareThreads(existingThread, thread);
6986
+ if (result === 1) return;
6987
+ }
6988
+ updatedThreads[thread.id] = thread;
6989
+ });
6990
+ updates.deletedThreads.forEach(({ id, deletedAt }) => {
6991
+ const existingThread = updatedThreads[id];
6992
+ if (existingThread === void 0) return;
6993
+ existingThread.deletedAt = deletedAt;
6994
+ existingThread.updatedAt = deletedAt;
6995
+ existingThread.comments = [];
6996
+ });
6997
+ return updatedThreads;
6998
+ }
6999
+ function applyNotificationsUpdates(existingInboxNotifications, updates) {
7000
+ const updatedInboxNotifications = { ...existingInboxNotifications };
7001
+ updates.newInboxNotifications.forEach((notification) => {
7002
+ const existingNotification = updatedInboxNotifications[notification.id];
7003
+ if (existingNotification) {
7004
+ const result = compareInboxNotifications(
7005
+ existingNotification,
7006
+ notification
7007
+ );
7008
+ if (result === 1) return;
7009
+ }
7010
+ updatedInboxNotifications[notification.id] = notification;
7011
+ });
7012
+ updates.deletedNotifications.forEach(
7013
+ ({ id }) => delete updatedInboxNotifications[id]
7014
+ );
7015
+ return updatedInboxNotifications;
7016
+ }
7017
+ function compareInboxNotifications(inboxNotificationA, inboxNotificationB) {
7018
+ if (inboxNotificationA.notifiedAt > inboxNotificationB.notifiedAt) {
7019
+ return 1;
7020
+ } else if (inboxNotificationA.notifiedAt < inboxNotificationB.notifiedAt) {
7021
+ return -1;
7022
+ }
7023
+ if (inboxNotificationA.readAt && inboxNotificationB.readAt) {
7024
+ return inboxNotificationA.readAt > inboxNotificationB.readAt ? 1 : inboxNotificationA.readAt < inboxNotificationB.readAt ? -1 : 0;
7025
+ } else if (inboxNotificationA.readAt || inboxNotificationB.readAt) {
7026
+ return inboxNotificationA.readAt ? 1 : -1;
7027
+ }
7028
+ return 0;
7029
+ }
7030
+ function upsertComment(thread, comment) {
7031
+ if (thread.deletedAt !== void 0) {
7032
+ return thread;
7033
+ }
7034
+ if (comment.threadId !== thread.id) {
7035
+ warn(
7036
+ `Comment ${comment.id} does not belong to thread ${thread.id}`
7037
+ );
7038
+ return thread;
7039
+ }
7040
+ const existingComment = thread.comments.find(
7041
+ (existingComment2) => existingComment2.id === comment.id
7042
+ );
7043
+ if (existingComment === void 0) {
7044
+ const updatedAt = new Date(
7045
+ Math.max(thread.updatedAt?.getTime() || 0, comment.createdAt.getTime())
7046
+ );
7047
+ const updatedThread = {
7048
+ ...thread,
7049
+ updatedAt,
7050
+ comments: [...thread.comments, comment]
7051
+ };
7052
+ return updatedThread;
7053
+ }
7054
+ if (existingComment.deletedAt !== void 0) {
7055
+ return thread;
7056
+ }
7057
+ if (existingComment.editedAt === void 0 || comment.editedAt === void 0 || existingComment.editedAt <= comment.editedAt) {
7058
+ const updatedComments = thread.comments.map(
7059
+ (existingComment2) => existingComment2.id === comment.id ? comment : existingComment2
7060
+ );
7061
+ const updatedThread = {
7062
+ ...thread,
7063
+ updatedAt: new Date(
7064
+ Math.max(
7065
+ thread.updatedAt?.getTime() || 0,
7066
+ comment.editedAt?.getTime() || comment.createdAt.getTime()
7067
+ )
7068
+ ),
7069
+ comments: updatedComments
7070
+ };
7071
+ return updatedThread;
7072
+ }
7073
+ return thread;
7074
+ }
7075
+ function deleteComment(thread, commentId, deletedAt) {
7076
+ if (thread.deletedAt !== void 0) {
7077
+ return thread;
7078
+ }
7079
+ const existingComment = thread.comments.find(
7080
+ (comment) => comment.id === commentId
7081
+ );
7082
+ if (existingComment === void 0) {
7083
+ return thread;
7084
+ }
7085
+ if (existingComment.deletedAt !== void 0) {
7086
+ return thread;
7087
+ }
7088
+ const updatedComments = thread.comments.map(
7089
+ (comment) => comment.id === commentId ? {
7090
+ ...comment,
7091
+ deletedAt,
7092
+ body: void 0
7093
+ } : comment
7094
+ );
7095
+ if (!updatedComments.some((comment) => comment.deletedAt === void 0)) {
7096
+ return {
7097
+ ...thread,
7098
+ deletedAt,
7099
+ updatedAt: deletedAt,
7100
+ comments: []
7101
+ };
7102
+ }
7103
+ return {
7104
+ ...thread,
7105
+ updatedAt: deletedAt,
7106
+ comments: updatedComments
7107
+ };
7108
+ }
7109
+ function addReaction(thread, commentId, reaction) {
7110
+ if (thread.deletedAt !== void 0) {
7111
+ return thread;
7112
+ }
7113
+ const existingComment = thread.comments.find(
7114
+ (comment) => comment.id === commentId
7115
+ );
7116
+ if (existingComment === void 0) {
7117
+ return thread;
7118
+ }
7119
+ if (existingComment.deletedAt !== void 0) {
7120
+ return thread;
7121
+ }
7122
+ const updatedComments = thread.comments.map(
7123
+ (comment) => comment.id === commentId ? {
7124
+ ...comment,
7125
+ reactions: upsertReaction(comment.reactions, reaction)
7126
+ } : comment
7127
+ );
7128
+ return {
7129
+ ...thread,
7130
+ updatedAt: new Date(
7131
+ Math.max(reaction.createdAt.getTime(), thread.updatedAt?.getTime() || 0)
7132
+ ),
7133
+ comments: updatedComments
7134
+ };
7135
+ }
7136
+ function removeReaction(thread, commentId, emoji, userId, removedAt) {
7137
+ if (thread.deletedAt !== void 0) {
7138
+ return thread;
7139
+ }
7140
+ const existingComment = thread.comments.find(
7141
+ (comment) => comment.id === commentId
7142
+ );
7143
+ if (existingComment === void 0) {
7144
+ return thread;
7145
+ }
7146
+ if (existingComment.deletedAt !== void 0) {
7147
+ return thread;
7148
+ }
7149
+ const updatedComments = thread.comments.map(
7150
+ (comment) => comment.id === commentId ? {
7151
+ ...comment,
7152
+ reactions: comment.reactions.map(
7153
+ (reaction) => reaction.emoji === emoji ? {
7154
+ ...reaction,
7155
+ users: reaction.users.filter((user) => user.id !== userId)
7156
+ } : reaction
7157
+ ).filter((reaction) => reaction.users.length > 0)
7158
+ // Remove reactions with no users left
7159
+ } : comment
7160
+ );
7161
+ return {
7162
+ ...thread,
7163
+ updatedAt: new Date(
7164
+ Math.max(removedAt.getTime(), thread.updatedAt?.getTime() || 0)
7165
+ ),
7166
+ comments: updatedComments
7167
+ };
7168
+ }
7169
+ function upsertReaction(reactions, reaction) {
7170
+ const existingReaction = reactions.find(
7171
+ (existingReaction2) => existingReaction2.emoji === reaction.emoji
7172
+ );
7173
+ if (existingReaction === void 0) {
7174
+ return [
7175
+ ...reactions,
7176
+ {
7177
+ emoji: reaction.emoji,
7178
+ createdAt: reaction.createdAt,
7179
+ users: [{ id: reaction.userId }]
7180
+ }
7181
+ ];
7182
+ }
7183
+ if (existingReaction.users.some((user) => user.id === reaction.userId) === false) {
7184
+ return reactions.map(
7185
+ (existingReaction2) => existingReaction2.emoji === reaction.emoji ? {
7186
+ ...existingReaction2,
7187
+ users: [...existingReaction2.users, { id: reaction.userId }]
7188
+ } : existingReaction2
7189
+ );
7190
+ }
7191
+ return reactions;
7192
+ }
7193
+
7194
+ // src/client.ts
7195
+ var MIN_THROTTLE = 16;
7196
+ var MAX_THROTTLE = 1e3;
7197
+ var DEFAULT_THROTTLE = 100;
7198
+ var MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT = 15e3;
7199
+ var MIN_LOST_CONNECTION_TIMEOUT = 200;
7200
+ var RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1e3;
7201
+ var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
7202
+ var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
7203
+ var RESOLVE_USERS_BATCH_DELAY = 50;
7204
+ var RESOLVE_ROOMS_INFO_BATCH_DELAY = 50;
7205
+ function getBaseUrl(baseUrl) {
7206
+ if (typeof baseUrl === "string" && baseUrl.startsWith("http")) {
7207
+ return baseUrl;
7208
+ } else {
7209
+ return DEFAULT_BASE_URL;
7210
+ }
7211
+ }
7212
+ function getAuthBearerHeaderFromAuthValue(authValue) {
7213
+ if (authValue.type === "public") {
7214
+ return authValue.publicApiKey;
7215
+ } else {
7216
+ return authValue.token.raw;
7217
+ }
7218
+ }
7219
+ function createClient(options) {
7220
+ const clientOptions = options;
7221
+ const throttleDelay = getThrottle(clientOptions.throttle ?? DEFAULT_THROTTLE);
7222
+ const lostConnectionTimeout = getLostConnectionTimeout(
7223
+ clientOptions.lostConnectionTimeout ?? DEFAULT_LOST_CONNECTION_TIMEOUT
7224
+ );
7225
+ const backgroundKeepAliveTimeout = getBackgroundKeepAliveTimeout(
7226
+ clientOptions.backgroundKeepAliveTimeout
7227
+ );
7228
+ const baseUrl = getBaseUrl(clientOptions.baseUrl);
7229
+ const authManager = createAuthManager(options);
7230
+ const roomsById = /* @__PURE__ */ new Map();
7231
+ function teardownRoom(room) {
7232
+ unlinkDevTools(room.id);
7233
+ roomsById.delete(room.id);
7234
+ room.destroy();
7235
+ }
7236
+ function leaseRoom(details) {
7237
+ const leave = () => {
7238
+ const self = leave;
7239
+ if (!details.unsubs.delete(self)) {
7240
+ warn(
7241
+ "This leave function was already called. Calling it more than once has no effect."
7242
+ );
7243
+ } else {
7244
+ if (details.unsubs.size === 0) {
7245
+ teardownRoom(details.room);
7246
+ }
7247
+ }
7248
+ };
7249
+ details.unsubs.add(leave);
7250
+ return {
7251
+ room: details.room,
7252
+ leave
7253
+ };
7254
+ }
7255
+ function enterRoom(roomId, options2) {
7256
+ const existing = roomsById.get(roomId);
7257
+ if (existing !== void 0) {
7258
+ return leaseRoom(existing);
7259
+ }
7260
+ deprecateIf(
7261
+ options2.initialPresence === null || options2.initialPresence === void 0,
7262
+ "Please provide an initial presence value for the current user when entering the room."
7263
+ );
7264
+ const newRoom = createRoom(
7265
+ {
7266
+ initialPresence: options2.initialPresence ?? {},
7267
+ initialStorage: options2.initialStorage
7268
+ },
7269
+ {
7270
+ roomId,
7271
+ throttleDelay,
7272
+ lostConnectionTimeout,
7273
+ backgroundKeepAliveTimeout,
7274
+ polyfills: clientOptions.polyfills,
7275
+ delegates: clientOptions.mockedDelegates ?? {
7276
+ createSocket: makeCreateSocketDelegateForRoom(
7277
+ roomId,
7278
+ baseUrl,
7279
+ clientOptions.polyfills?.WebSocket
7280
+ ),
7281
+ authenticate: makeAuthDelegateForRoom(roomId, authManager)
7282
+ },
7283
+ enableDebugLogging: clientOptions.enableDebugLogging,
7284
+ unstable_batchedUpdates: options2?.unstable_batchedUpdates,
7285
+ baseUrl,
7286
+ unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP,
7287
+ unstable_streamData: !!clientOptions.unstable_streamData
7288
+ }
7289
+ );
7290
+ const newRoomDetails = {
7291
+ room: newRoom,
7292
+ unsubs: /* @__PURE__ */ new Set()
7293
+ };
7294
+ roomsById.set(roomId, newRoomDetails);
7295
+ setupDevTools(() => Array.from(roomsById.keys()));
7296
+ linkDevTools(roomId, newRoom);
7297
+ const shouldConnect = options2.autoConnect ?? true;
7298
+ if (shouldConnect) {
7299
+ if (typeof atob === "undefined") {
7300
+ if (clientOptions.polyfills?.atob === void 0) {
7301
+ throw new Error(
7302
+ "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"
7303
+ );
7304
+ }
7305
+ global.atob = clientOptions.polyfills.atob;
7306
+ }
7307
+ newRoom.connect();
7308
+ }
7309
+ return leaseRoom(newRoomDetails);
7310
+ }
7311
+ function getRoom(roomId) {
7312
+ const room = roomsById.get(roomId)?.room;
7313
+ return room ? room : null;
7314
+ }
7315
+ function logout() {
7316
+ authManager.reset();
7317
+ for (const { room } of roomsById.values()) {
7318
+ if (!isIdle(room.getStatus())) {
7319
+ room.reconnect();
7320
+ }
7321
+ }
7322
+ }
7323
+ const currentUserIdStore = createStore(null);
7324
+ const {
7325
+ getInboxNotifications,
7326
+ getUnreadInboxNotificationsCount,
7327
+ markAllInboxNotificationsAsRead,
7328
+ markInboxNotificationAsRead
7329
+ } = createNotificationsApi({
7330
+ baseUrl,
7331
+ fetcher: clientOptions.polyfills?.fetch || /* istanbul ignore next */
7332
+ fetch,
7333
+ authManager,
7334
+ currentUserIdStore
6170
7335
  });
7336
+ const cacheStore = createClientStore();
7337
+ const resolveUsers = clientOptions.resolveUsers;
7338
+ const warnIfNoResolveUsers = createDevelopmentWarning(
7339
+ () => !resolveUsers,
7340
+ "Set the resolveUsers option in createClient to specify user info."
7341
+ );
7342
+ const usersStore = createBatchStore(
7343
+ async (batchedUserIds) => {
7344
+ const userIds = batchedUserIds.flat();
7345
+ const users = await resolveUsers?.({ userIds });
7346
+ warnIfNoResolveUsers();
7347
+ return users ?? userIds.map(() => void 0);
7348
+ },
7349
+ { delay: RESOLVE_USERS_BATCH_DELAY }
7350
+ );
7351
+ const resolveRoomsInfo = clientOptions.resolveRoomsInfo;
7352
+ const warnIfNoResolveRoomsInfo = createDevelopmentWarning(
7353
+ () => !resolveRoomsInfo,
7354
+ "Set the resolveRoomsInfo option in createClient to specify room info."
7355
+ );
7356
+ const roomsInfoStore = createBatchStore(
7357
+ async (batchedRoomIds) => {
7358
+ const roomIds = batchedRoomIds.flat();
7359
+ const roomsInfo = await resolveRoomsInfo?.({ roomIds });
7360
+ warnIfNoResolveRoomsInfo();
7361
+ return roomsInfo ?? roomIds.map(() => void 0);
7362
+ },
7363
+ { delay: RESOLVE_ROOMS_INFO_BATCH_DELAY }
7364
+ );
6171
7365
  return Object.defineProperty(
6172
7366
  {
6173
- /* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
6174
- __internal: {
6175
- get presenceBuffer() {
6176
- return deepClone(context.buffer.presenceUpdates?.data ?? null);
6177
- },
6178
- // prettier-ignore
6179
- get undoStack() {
6180
- return deepClone(context.undoStack);
6181
- },
6182
- // prettier-ignore
6183
- get nodeCount() {
6184
- return context.nodes.size;
7367
+ enterRoom,
7368
+ getRoom,
7369
+ logout,
7370
+ // Internal
7371
+ [kInternal]: {
7372
+ notifications: {
7373
+ getInboxNotifications,
7374
+ getUnreadInboxNotificationsCount,
7375
+ markAllInboxNotificationsAsRead,
7376
+ markInboxNotificationAsRead
6185
7377
  },
6186
- // prettier-ignore
6187
- // Support for the Liveblocks browser extension
6188
- getSelf_forDevTools: () => selfAsTreeNode.current,
6189
- getOthers_forDevTools: () => others_forDevTools.current,
6190
- // prettier-ignore
6191
- simulate: {
6192
- // These exist only for our E2E testing app
6193
- explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
6194
- rawSend: (data) => managedSocket.send(data)
7378
+ currentUserIdStore,
7379
+ resolveMentionSuggestions: clientOptions.resolveMentionSuggestions,
7380
+ cacheStore,
7381
+ usersStore,
7382
+ roomsInfoStore,
7383
+ getRoomIds() {
7384
+ return Array.from(roomsById.keys());
6195
7385
  }
6196
- },
6197
- id: config.roomId,
6198
- subscribe: makeClassicSubscribeFn(events),
6199
- connect: () => managedSocket.connect(),
6200
- reconnect: () => managedSocket.reconnect(),
6201
- disconnect: () => managedSocket.disconnect(),
6202
- destroy: () => {
6203
- uninstallBgTabSpy();
6204
- managedSocket.destroy();
6205
- },
6206
- // Presence
6207
- updatePresence,
6208
- updateYDoc,
6209
- broadcastEvent,
6210
- // Storage
6211
- batch,
6212
- history: {
6213
- undo,
6214
- redo,
6215
- canUndo,
6216
- canRedo,
6217
- clear,
6218
- pause: pauseHistory,
6219
- resume: resumeHistory
6220
- },
6221
- fetchYDoc,
6222
- getStorage,
6223
- getStorageSnapshot,
6224
- getStorageStatus,
6225
- events,
6226
- // Core
6227
- getStatus: () => managedSocket.getStatus(),
6228
- getConnectionState: () => managedSocket.getLegacyStatus(),
6229
- getSelf: () => self.current,
6230
- // Presence
6231
- getPresence: () => context.myPresence.current,
6232
- getOthers: () => context.others.current,
6233
- ...commentsApi
7386
+ }
6234
7387
  },
6235
- // Explictly make the __internal field non-enumerable, to avoid aggressive
6236
- // freezing when used with Immer
6237
- "__internal",
6238
- { enumerable: false }
7388
+ kInternal,
7389
+ {
7390
+ enumerable: false
7391
+ }
6239
7392
  );
6240
7393
  }
6241
- function makeClassicSubscribeFn(events) {
6242
- function subscribeToLiveStructureDeeply(node, callback) {
6243
- return events.storage.subscribe((updates) => {
6244
- const relatedUpdates = updates.filter(
6245
- (update) => isSameNodeOrChildOf(update.node, node)
6246
- );
6247
- if (relatedUpdates.length > 0) {
6248
- callback(relatedUpdates);
6249
- }
6250
- });
7394
+ var NotificationsApiError = class extends Error {
7395
+ constructor(message, status, details) {
7396
+ super(message);
7397
+ this.message = message;
7398
+ this.status = status;
7399
+ this.details = details;
6251
7400
  }
6252
- function subscribeToLiveStructureShallowly(node, callback) {
6253
- return events.storage.subscribe((updates) => {
6254
- for (const update of updates) {
6255
- if (update.node._id === node._id) {
6256
- callback(update.node);
6257
- }
6258
- }
6259
- });
7401
+ };
7402
+ function checkBounds(option, value, min, max, recommendedMin) {
7403
+ if (typeof value !== "number" || value < min || max !== void 0 && value > max) {
7404
+ throw new Error(
7405
+ max !== void 0 ? `${option} should be between ${recommendedMin ?? min} and ${max}.` : `${option} should be at least ${recommendedMin ?? min}.`
7406
+ );
6260
7407
  }
6261
- function subscribe(first, second, options) {
6262
- if (typeof first === "string" && isRoomEventName(first)) {
6263
- if (typeof second !== "function") {
6264
- throw new Error("Second argument must be a callback function");
7408
+ return value;
7409
+ }
7410
+ function getBackgroundKeepAliveTimeout(value) {
7411
+ if (value === void 0) return void 0;
7412
+ return checkBounds(
7413
+ "backgroundKeepAliveTimeout",
7414
+ value,
7415
+ MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT
7416
+ );
7417
+ }
7418
+ function getThrottle(value) {
7419
+ return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
7420
+ }
7421
+ function getLostConnectionTimeout(value) {
7422
+ return checkBounds(
7423
+ "lostConnectionTimeout",
7424
+ value,
7425
+ MIN_LOST_CONNECTION_TIMEOUT,
7426
+ MAX_LOST_CONNECTION_TIMEOUT,
7427
+ RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
7428
+ );
7429
+ }
7430
+ function createDevelopmentWarning(condition, ...args) {
7431
+ let hasWarned = false;
7432
+ if (process.env.NODE_ENV !== "production") {
7433
+ return () => {
7434
+ if (!hasWarned && (typeof condition === "function" ? condition() : condition)) {
7435
+ warn(...args);
7436
+ hasWarned = true;
6265
7437
  }
6266
- const callback = second;
6267
- switch (first) {
6268
- case "event":
6269
- return events.customEvent.subscribe(
6270
- callback
6271
- );
6272
- case "my-presence":
6273
- return events.myPresence.subscribe(callback);
6274
- case "others": {
6275
- const cb = callback;
6276
- return events.others.subscribe((event) => {
6277
- const { others, ...internalEvent } = event;
6278
- return cb(others, internalEvent);
6279
- });
6280
- }
6281
- case "error":
6282
- return events.error.subscribe(callback);
6283
- case "connection": {
6284
- const cb = callback;
6285
- return events.status.subscribe(
6286
- (status) => cb(newToLegacyStatus(status))
6287
- );
6288
- }
6289
- case "status":
6290
- return events.status.subscribe(callback);
6291
- case "lost-connection":
6292
- return events.lostConnection.subscribe(
6293
- callback
6294
- );
6295
- case "history":
6296
- return events.history.subscribe(callback);
6297
- case "storage-status":
6298
- return events.storageStatus.subscribe(
6299
- callback
6300
- );
6301
- default:
6302
- return assertNever(
6303
- first,
6304
- `"${String(first)}" is not a valid event name`
6305
- );
7438
+ };
7439
+ } else {
7440
+ return () => {
7441
+ };
7442
+ }
7443
+ }
7444
+
7445
+ // src/comments/comment-body.ts
7446
+ function isCommentBodyParagraph(element) {
7447
+ return "type" in element && element.type === "mention";
7448
+ }
7449
+ function isCommentBodyText(element) {
7450
+ return "text" in element && typeof element.text === "string";
7451
+ }
7452
+ function isCommentBodyMention(element) {
7453
+ return "type" in element && element.type === "mention";
7454
+ }
7455
+ function isCommentBodyLink(element) {
7456
+ return "type" in element && element.type === "link";
7457
+ }
7458
+ var commentBodyElementsGuards = {
7459
+ paragraph: isCommentBodyParagraph,
7460
+ text: isCommentBodyText,
7461
+ link: isCommentBodyLink,
7462
+ mention: isCommentBodyMention
7463
+ };
7464
+ var commentBodyElementsTypes = {
7465
+ paragraph: "block",
7466
+ text: "inline",
7467
+ link: "inline",
7468
+ mention: "inline"
7469
+ };
7470
+ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
7471
+ if (!body || !body?.content) {
7472
+ return;
7473
+ }
7474
+ const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
7475
+ const type = element ? commentBodyElementsTypes[element] : "all";
7476
+ const guard = element ? commentBodyElementsGuards[element] : () => true;
7477
+ const visitor = typeof elementOrVisitor === "function" ? elementOrVisitor : possiblyVisitor;
7478
+ for (const block of body.content) {
7479
+ if (type === "all" || type === "block") {
7480
+ if (guard(block)) {
7481
+ visitor?.(block);
6306
7482
  }
6307
7483
  }
6308
- if (second === void 0 || typeof first === "function") {
6309
- if (typeof first === "function") {
6310
- const storageCallback = first;
6311
- return events.storage.subscribe(storageCallback);
6312
- } else {
6313
- throw new Error("Please specify a listener callback");
7484
+ if (type === "all" || type === "inline") {
7485
+ for (const inline of block.children) {
7486
+ if (guard(inline)) {
7487
+ visitor?.(inline);
7488
+ }
6314
7489
  }
6315
7490
  }
6316
- if (isLiveNode(first)) {
6317
- const node = first;
6318
- if (options?.isDeep) {
6319
- const storageCallback = second;
6320
- return subscribeToLiveStructureDeeply(node, storageCallback);
6321
- } else {
6322
- const nodeCallback = second;
6323
- return subscribeToLiveStructureShallowly(node, nodeCallback);
6324
- }
7491
+ }
7492
+ }
7493
+ function getMentionedIdsFromCommentBody(body) {
7494
+ const mentionedIds = /* @__PURE__ */ new Set();
7495
+ traverseCommentBody(
7496
+ body,
7497
+ "mention",
7498
+ (mention) => mentionedIds.add(mention.id)
7499
+ );
7500
+ return Array.from(mentionedIds);
7501
+ }
7502
+ async function resolveUsersInCommentBody(body, resolveUsers) {
7503
+ const resolvedUsers = /* @__PURE__ */ new Map();
7504
+ if (!resolveUsers) {
7505
+ return resolvedUsers;
7506
+ }
7507
+ const userIds = getMentionedIdsFromCommentBody(body);
7508
+ const users = await resolveUsers({
7509
+ userIds
7510
+ });
7511
+ for (const [index, userId] of userIds.entries()) {
7512
+ const user = users?.[index];
7513
+ if (user) {
7514
+ resolvedUsers.set(userId, user);
6325
7515
  }
6326
- throw new Error(
6327
- `${String(first)} is not a value that can be subscribed to.`
6328
- );
6329
7516
  }
6330
- return subscribe;
7517
+ return resolvedUsers;
6331
7518
  }
6332
- function isRoomEventName(value) {
6333
- return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "history" || value === "status" || value === "storage-status" || value === "lost-connection" || value === "connection";
7519
+ var htmlEscapables = {
7520
+ "&": "&amp;",
7521
+ "<": "&lt;",
7522
+ ">": "&gt;",
7523
+ '"': "&quot;",
7524
+ "'": "&#39;"
7525
+ };
7526
+ var htmlEscapablesRegex = new RegExp(
7527
+ Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
7528
+ "g"
7529
+ );
7530
+ function htmlSafe(value) {
7531
+ return new HtmlSafeString([String(value)], []);
6334
7532
  }
6335
- function makeAuthDelegateForRoom(roomId, authManager) {
6336
- return async () => {
6337
- return authManager.getAuthValue("room:read", roomId);
6338
- };
7533
+ function joinHtml(strings) {
7534
+ if (strings.length <= 0) {
7535
+ return new HtmlSafeString([""], []);
7536
+ }
7537
+ return new HtmlSafeString(
7538
+ ["", ...Array(strings.length - 1).fill(""), ""],
7539
+ strings
7540
+ );
6339
7541
  }
6340
- function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
6341
- return (authValue) => {
6342
- const ws = WebSocketPolyfill ?? (typeof WebSocket === "undefined" ? void 0 : WebSocket);
6343
- if (ws === void 0) {
6344
- throw new StopRetrying(
6345
- "To use Liveblocks client in a non-DOM environment, you need to provide a WebSocket polyfill."
6346
- );
6347
- }
6348
- const url = new URL(baseUrl);
6349
- url.protocol = url.protocol === "http:" ? "ws" : "wss";
6350
- url.pathname = "/v7";
6351
- url.searchParams.set("roomId", roomId);
6352
- if (authValue.type === "secret") {
6353
- url.searchParams.set("tok", authValue.token.raw);
6354
- } else if (authValue.type === "public") {
6355
- url.searchParams.set("pubkey", authValue.publicApiKey);
6356
- } else {
6357
- return assertNever(authValue, "Unhandled case");
6358
- }
6359
- url.searchParams.set("version", PKG_VERSION || "dev");
6360
- return new ws(url.toString());
6361
- };
7542
+ function escapeHtml(value) {
7543
+ if (value instanceof HtmlSafeString) {
7544
+ return value.toString();
7545
+ }
7546
+ if (Array.isArray(value)) {
7547
+ return joinHtml(value).toString();
7548
+ }
7549
+ return String(value).replace(
7550
+ htmlEscapablesRegex,
7551
+ (character) => htmlEscapables[character]
7552
+ );
6362
7553
  }
6363
-
6364
- // src/client.ts
6365
- var MIN_THROTTLE = 16;
6366
- var MAX_THROTTLE = 1e3;
6367
- var DEFAULT_THROTTLE = 100;
6368
- var MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT = 15e3;
6369
- var MIN_LOST_CONNECTION_TIMEOUT = 200;
6370
- var RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1e3;
6371
- var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
6372
- var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
6373
- function getBaseUrlFromClientOptions(clientOptions) {
6374
- if ("liveblocksServer" in clientOptions) {
6375
- throw new Error("Client option no longer supported");
7554
+ var HtmlSafeString = class {
7555
+ constructor(strings, values) {
7556
+ this._strings = strings;
7557
+ this._values = values;
6376
7558
  }
6377
- if (typeof clientOptions.baseUrl === "string" && clientOptions.baseUrl.startsWith("http")) {
6378
- return clientOptions.baseUrl;
6379
- } else {
6380
- return DEFAULT_BASE_URL;
7559
+ toString() {
7560
+ return this._strings.reduce((result, str, i) => {
7561
+ return result + escapeHtml(nn(this._values[i - 1])) + str;
7562
+ });
6381
7563
  }
7564
+ };
7565
+ function html(strings, ...values) {
7566
+ return new HtmlSafeString(strings, values);
6382
7567
  }
6383
- function createClient(options) {
6384
- const clientOptions = options;
6385
- const throttleDelay = getThrottle(clientOptions.throttle ?? DEFAULT_THROTTLE);
6386
- const lostConnectionTimeout = getLostConnectionTimeout(
6387
- clientOptions.lostConnectionTimeout ?? DEFAULT_LOST_CONNECTION_TIMEOUT
7568
+ var markdownEscapables = {
7569
+ _: "\\_",
7570
+ "*": "\\*",
7571
+ "#": "\\#",
7572
+ "`": "\\`",
7573
+ "~": "\\~",
7574
+ "!": "\\!",
7575
+ "|": "\\|",
7576
+ "(": "\\(",
7577
+ ")": "\\)",
7578
+ "{": "\\{",
7579
+ "}": "\\}",
7580
+ "[": "\\[",
7581
+ "]": "\\]"
7582
+ };
7583
+ var markdownEscapablesRegex = new RegExp(
7584
+ Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
7585
+ "g"
7586
+ );
7587
+ function joinMarkdown(strings) {
7588
+ if (strings.length <= 0) {
7589
+ return new MarkdownSafeString([""], []);
7590
+ }
7591
+ return new MarkdownSafeString(
7592
+ ["", ...Array(strings.length - 1).fill(""), ""],
7593
+ strings
6388
7594
  );
6389
- const backgroundKeepAliveTimeout = getBackgroundKeepAliveTimeout(
6390
- clientOptions.backgroundKeepAliveTimeout
7595
+ }
7596
+ function escapeMarkdown(value) {
7597
+ if (value instanceof MarkdownSafeString) {
7598
+ return value.toString();
7599
+ }
7600
+ if (Array.isArray(value)) {
7601
+ return joinMarkdown(value).toString();
7602
+ }
7603
+ return String(value).replace(
7604
+ markdownEscapablesRegex,
7605
+ (character) => markdownEscapables[character]
6391
7606
  );
6392
- const authManager = createAuthManager(options);
6393
- const roomsById = /* @__PURE__ */ new Map();
6394
- function teardownRoom(room) {
6395
- unlinkDevTools(room.id);
6396
- roomsById.delete(room.id);
6397
- room.destroy();
7607
+ }
7608
+ var MarkdownSafeString = class {
7609
+ constructor(strings, values) {
7610
+ this._strings = strings;
7611
+ this._values = values;
6398
7612
  }
6399
- function leaseRoom(info) {
6400
- const leave = () => {
6401
- const self = leave;
6402
- if (!info.unsubs.delete(self)) {
6403
- warn(
6404
- "This leave function was already called. Calling it more than once has no effect."
6405
- );
6406
- } else {
6407
- if (info.unsubs.size === 0) {
6408
- teardownRoom(info.room);
6409
- }
6410
- }
6411
- };
6412
- info.unsubs.add(leave);
6413
- return {
6414
- room: info.room,
6415
- leave
6416
- };
7613
+ toString() {
7614
+ return this._strings.reduce((result, str, i) => {
7615
+ return result + escapeMarkdown(nn(this._values[i - 1])) + str;
7616
+ });
6417
7617
  }
6418
- function enterRoom(roomId, options2) {
6419
- const existing = roomsById.get(roomId);
6420
- if (existing !== void 0) {
6421
- return leaseRoom(existing);
6422
- }
6423
- deprecateIf(
6424
- options2.initialPresence === null || options2.initialPresence === void 0,
6425
- "Please provide an initial presence value for the current user when entering the room."
6426
- );
6427
- const baseUrl = getBaseUrlFromClientOptions(clientOptions);
6428
- const newRoom = createRoom(
6429
- {
6430
- initialPresence: options2.initialPresence ?? {},
6431
- initialStorage: options2.initialStorage
6432
- },
6433
- {
6434
- roomId,
6435
- throttleDelay,
6436
- lostConnectionTimeout,
6437
- backgroundKeepAliveTimeout,
6438
- polyfills: clientOptions.polyfills,
6439
- delegates: clientOptions.mockedDelegates ?? {
6440
- createSocket: makeCreateSocketDelegateForRoom(
6441
- roomId,
6442
- baseUrl,
6443
- clientOptions.polyfills?.WebSocket
6444
- ),
6445
- authenticate: makeAuthDelegateForRoom(roomId, authManager)
6446
- },
6447
- enableDebugLogging: clientOptions.enableDebugLogging,
6448
- unstable_batchedUpdates: options2?.unstable_batchedUpdates,
6449
- baseUrl,
6450
- unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP
6451
- }
6452
- );
6453
- const newRoomInfo = {
6454
- room: newRoom,
6455
- unsubs: /* @__PURE__ */ new Set()
6456
- };
6457
- roomsById.set(roomId, newRoomInfo);
6458
- setupDevTools(() => Array.from(roomsById.keys()));
6459
- linkDevTools(roomId, newRoom);
6460
- const shouldConnect = options2.autoConnect ?? options2.shouldInitiallyConnect ?? true;
6461
- if (shouldConnect) {
6462
- if (typeof atob === "undefined") {
6463
- if (clientOptions.polyfills?.atob === void 0) {
6464
- throw new Error(
6465
- "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"
6466
- );
6467
- }
6468
- global.atob = clientOptions.polyfills.atob;
6469
- }
6470
- newRoom.connect();
6471
- }
6472
- return leaseRoom(newRoomInfo);
7618
+ };
7619
+ function markdown(strings, ...values) {
7620
+ return new MarkdownSafeString(strings, values);
7621
+ }
7622
+ function toAbsoluteUrl(url) {
7623
+ if (url.startsWith("http://") || url.startsWith("https://")) {
7624
+ return url;
7625
+ } else if (url.startsWith("www.")) {
7626
+ return "https://" + url;
6473
7627
  }
6474
- function enter(roomId, options2) {
6475
- const { room, leave: _ } = enterRoom(roomId, options2);
6476
- return room;
7628
+ return;
7629
+ }
7630
+ var stringifyCommentBodyPlainElements = {
7631
+ paragraph: ({ children }) => children,
7632
+ text: ({ element }) => element.text,
7633
+ link: ({ element }) => element.url,
7634
+ mention: ({ element, user }) => {
7635
+ return `@${user?.name ?? element.id}`;
6477
7636
  }
6478
- function getRoom(roomId) {
6479
- const room = roomsById.get(roomId)?.room;
6480
- return room ? room : null;
7637
+ };
7638
+ var stringifyCommentBodyHtmlElements = {
7639
+ paragraph: ({ children }) => {
7640
+ return children ? html`<p>${htmlSafe(children)}</p>` : children;
7641
+ },
7642
+ text: ({ element }) => {
7643
+ let children = element.text;
7644
+ if (!children) {
7645
+ return children;
7646
+ }
7647
+ if (element.bold) {
7648
+ children = html`<strong>${children}</strong>`;
7649
+ }
7650
+ if (element.italic) {
7651
+ children = html`<em>${children}</em>`;
7652
+ }
7653
+ if (element.strikethrough) {
7654
+ children = html`<s>${children}</s>`;
7655
+ }
7656
+ if (element.code) {
7657
+ children = html`<code>${children}</code>`;
7658
+ }
7659
+ return children;
7660
+ },
7661
+ link: ({ element, href }) => {
7662
+ return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
7663
+ },
7664
+ mention: ({ element, user }) => {
7665
+ return html`<span data-mention>@${user?.name ?? element.id}</span>`;
6481
7666
  }
6482
- function forceLeave(roomId) {
6483
- const unsubs = roomsById.get(roomId)?.unsubs ?? /* @__PURE__ */ new Set();
6484
- for (const unsub of unsubs) {
6485
- unsub();
7667
+ };
7668
+ var stringifyCommentBodyMarkdownElements = {
7669
+ paragraph: ({ children }) => {
7670
+ return children;
7671
+ },
7672
+ text: ({ element }) => {
7673
+ let children = element.text;
7674
+ if (!children) {
7675
+ return children;
6486
7676
  }
6487
- }
6488
- function logout() {
6489
- authManager.reset();
6490
- for (const { room } of roomsById.values()) {
6491
- if (!isIdle(room.getStatus())) {
6492
- room.reconnect();
6493
- }
7677
+ if (element.bold) {
7678
+ children = markdown`**${children}**`;
7679
+ }
7680
+ if (element.italic) {
7681
+ children = markdown`_${children}_`;
7682
+ }
7683
+ if (element.strikethrough) {
7684
+ children = markdown`~~${children}~~`;
7685
+ }
7686
+ if (element.code) {
7687
+ children = markdown`\`${children}\``;
6494
7688
  }
7689
+ return children;
7690
+ },
7691
+ link: ({ element, href }) => {
7692
+ return markdown`[${element.url}](${href})`;
7693
+ },
7694
+ mention: ({ element, user }) => {
7695
+ return markdown`@${user?.name ?? element.id}`;
6495
7696
  }
6496
- return {
6497
- logout,
6498
- // Old, deprecated APIs
6499
- enter,
6500
- getRoom,
6501
- leave: forceLeave,
6502
- // New, preferred API
6503
- enterRoom
7697
+ };
7698
+ async function stringifyCommentBody(body, options) {
7699
+ const format = options?.format ?? "plain";
7700
+ const separator = options?.separator ?? (format === "markdown" ? "\n\n" : "\n");
7701
+ const elements = {
7702
+ ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
7703
+ ...options?.elements
6504
7704
  };
6505
- }
6506
- function checkBounds(option, value, min, max, recommendedMin) {
6507
- if (typeof value !== "number" || value < min || max !== void 0 && value > max) {
6508
- throw new Error(
6509
- max !== void 0 ? `${option} should be between ${recommendedMin ?? min} and ${max}.` : `${option} should be at least ${recommendedMin ?? min}.`
6510
- );
6511
- }
6512
- return value;
6513
- }
6514
- function getBackgroundKeepAliveTimeout(value) {
6515
- if (value === void 0)
6516
- return void 0;
6517
- return checkBounds(
6518
- "backgroundKeepAliveTimeout",
6519
- value,
6520
- MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT
6521
- );
6522
- }
6523
- function getThrottle(value) {
6524
- return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
6525
- }
6526
- function getLostConnectionTimeout(value) {
6527
- return checkBounds(
6528
- "lostConnectionTimeout",
6529
- value,
6530
- MIN_LOST_CONNECTION_TIMEOUT,
6531
- MAX_LOST_CONNECTION_TIMEOUT,
6532
- RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
7705
+ const resolvedUsers = await resolveUsersInCommentBody(
7706
+ body,
7707
+ options?.resolveUsers
6533
7708
  );
7709
+ const blocks = body.content.flatMap((block, blockIndex) => {
7710
+ switch (block.type) {
7711
+ case "paragraph": {
7712
+ const inlines = block.children.flatMap((inline, inlineIndex) => {
7713
+ if (isCommentBodyMention(inline)) {
7714
+ return inline.id ? [
7715
+ elements.mention(
7716
+ {
7717
+ element: inline,
7718
+ user: resolvedUsers.get(inline.id)
7719
+ },
7720
+ inlineIndex
7721
+ )
7722
+ ] : [];
7723
+ }
7724
+ if (isCommentBodyLink(inline)) {
7725
+ return [
7726
+ elements.link(
7727
+ {
7728
+ element: inline,
7729
+ href: toAbsoluteUrl(inline.url) ?? inline.url
7730
+ },
7731
+ inlineIndex
7732
+ )
7733
+ ];
7734
+ }
7735
+ if (isCommentBodyText(inline)) {
7736
+ return [elements.text({ element: inline }, inlineIndex)];
7737
+ }
7738
+ return [];
7739
+ });
7740
+ return [
7741
+ elements.paragraph(
7742
+ { element: block, children: inlines.join("") },
7743
+ blockIndex
7744
+ )
7745
+ ];
7746
+ }
7747
+ default:
7748
+ return [];
7749
+ }
7750
+ });
7751
+ return blocks.join(separator);
6534
7752
  }
6535
7753
 
6536
7754
  // src/crdts/utils.ts
@@ -6864,161 +8082,6 @@ function legacy_patchImmutableNode(state, path, update) {
6864
8082
  }
6865
8083
  }
6866
8084
 
6867
- // src/lib/shallow.ts
6868
- function shallowArray(xs, ys) {
6869
- if (xs.length !== ys.length) {
6870
- return false;
6871
- }
6872
- for (let i = 0; i < xs.length; i++) {
6873
- if (!Object.is(xs[i], ys[i])) {
6874
- return false;
6875
- }
6876
- }
6877
- return true;
6878
- }
6879
- function shallowObj(objA, objB) {
6880
- if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null || Object.prototype.toString.call(objA) !== "[object Object]" || Object.prototype.toString.call(objB) !== "[object Object]") {
6881
- return false;
6882
- }
6883
- const keysA = Object.keys(objA);
6884
- if (keysA.length !== Object.keys(objB).length) {
6885
- return false;
6886
- }
6887
- return keysA.every(
6888
- (key) => Object.prototype.hasOwnProperty.call(objB, key) && Object.is(objA[key], objB[key])
6889
- );
6890
- }
6891
- function shallow(a, b) {
6892
- if (Object.is(a, b)) {
6893
- return true;
6894
- }
6895
- const isArrayA = Array.isArray(a);
6896
- const isArrayB = Array.isArray(b);
6897
- if (isArrayA || isArrayB) {
6898
- if (!isArrayA || !isArrayB) {
6899
- return false;
6900
- }
6901
- return shallowArray(a, b);
6902
- }
6903
- return shallowObj(a, b);
6904
- }
6905
-
6906
- // src/lib/AsyncCache.ts
6907
- var noop = () => {
6908
- };
6909
- function isShallowEqual(a, b) {
6910
- if (a.isLoading !== b.isLoading || a.data === void 0 !== (b.data === void 0) || a.error === void 0 !== (b.error === void 0)) {
6911
- return false;
6912
- } else {
6913
- return shallow(a.data, b.data) && shallow(a.error, b.error);
6914
- }
6915
- }
6916
- function createCacheItem(key, asyncFunction, options) {
6917
- const $asyncFunction = async () => asyncFunction(key);
6918
- const context = {
6919
- isInvalid: true
6920
- };
6921
- let state = { isLoading: false };
6922
- let previousState = { isLoading: false };
6923
- const eventSource2 = makeEventSource();
6924
- function notify() {
6925
- const isEqual = options?.isStateEqual ?? isShallowEqual;
6926
- if (!isEqual(previousState, state)) {
6927
- previousState = state;
6928
- eventSource2.notify(state);
6929
- }
6930
- }
6931
- async function resolve() {
6932
- if (!context.promise) {
6933
- return;
6934
- }
6935
- try {
6936
- const data = await context.promise;
6937
- context.isInvalid = false;
6938
- state = {
6939
- isLoading: false,
6940
- data
6941
- };
6942
- } catch (error3) {
6943
- state = {
6944
- isLoading: false,
6945
- data: state.data,
6946
- error: error3
6947
- };
6948
- }
6949
- context.promise = void 0;
6950
- notify();
6951
- }
6952
- async function revalidate() {
6953
- context.isInvalid = true;
6954
- return get();
6955
- }
6956
- async function get() {
6957
- if (context.isInvalid) {
6958
- if (!context.promise) {
6959
- context.isInvalid = true;
6960
- context.promise = $asyncFunction();
6961
- state = { isLoading: true, data: state.data };
6962
- notify();
6963
- }
6964
- await resolve();
6965
- }
6966
- return getState();
6967
- }
6968
- function getState() {
6969
- return state;
6970
- }
6971
- return {
6972
- ...eventSource2.observable,
6973
- get,
6974
- getState,
6975
- revalidate
6976
- };
6977
- }
6978
- function createAsyncCache(asyncFunction, options) {
6979
- const cache = /* @__PURE__ */ new Map();
6980
- function create(key) {
6981
- let cacheItem = cache.get(key);
6982
- if (cacheItem) {
6983
- return cacheItem;
6984
- }
6985
- cacheItem = createCacheItem(key, asyncFunction, options);
6986
- cache.set(key, cacheItem);
6987
- return cacheItem;
6988
- }
6989
- function get(key) {
6990
- return create(key).get();
6991
- }
6992
- function getState(key) {
6993
- return cache.get(key)?.getState();
6994
- }
6995
- function revalidate(key) {
6996
- return create(key).revalidate();
6997
- }
6998
- function subscribe(key, callback) {
6999
- return create(key).subscribe(callback) ?? noop;
7000
- }
7001
- function subscribeOnce(key, callback) {
7002
- return create(key).subscribeOnce(callback) ?? noop;
7003
- }
7004
- function has(key) {
7005
- return cache.has(key);
7006
- }
7007
- function clear() {
7008
- cache.clear();
7009
- }
7010
- return {
7011
- create,
7012
- get,
7013
- getState,
7014
- revalidate,
7015
- subscribe,
7016
- subscribeOnce,
7017
- has,
7018
- clear
7019
- };
7020
- }
7021
-
7022
8085
  // src/lib/Poller.ts
7023
8086
  function makePoller(callback) {
7024
8087
  let context = {
@@ -7108,19 +8171,43 @@ function makePoller(callback) {
7108
8171
  };
7109
8172
  }
7110
8173
 
7111
- // src/lib/stringify.ts
7112
- function stringify(object, ...args) {
7113
- if (typeof object !== "object" || object === null || Array.isArray(object)) {
7114
- return JSON.stringify(object, ...args);
8174
+ // src/lib/shallow.ts
8175
+ function shallowArray(xs, ys) {
8176
+ if (xs.length !== ys.length) {
8177
+ return false;
7115
8178
  }
7116
- const sortedObject = Object.keys(object).sort().reduce(
7117
- (sortedObject2, key) => {
7118
- sortedObject2[key] = object[key];
7119
- return sortedObject2;
7120
- },
7121
- {}
8179
+ for (let i = 0; i < xs.length; i++) {
8180
+ if (!Object.is(xs[i], ys[i])) {
8181
+ return false;
8182
+ }
8183
+ }
8184
+ return true;
8185
+ }
8186
+ function shallowObj(objA, objB) {
8187
+ if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null || Object.prototype.toString.call(objA) !== "[object Object]" || Object.prototype.toString.call(objB) !== "[object Object]") {
8188
+ return false;
8189
+ }
8190
+ const keysA = Object.keys(objA);
8191
+ if (keysA.length !== Object.keys(objB).length) {
8192
+ return false;
8193
+ }
8194
+ return keysA.every(
8195
+ (key) => Object.prototype.hasOwnProperty.call(objB, key) && Object.is(objA[key], objB[key])
7122
8196
  );
7123
- return JSON.stringify(sortedObject, ...args);
8197
+ }
8198
+ function shallow(a, b) {
8199
+ if (Object.is(a, b)) {
8200
+ return true;
8201
+ }
8202
+ const isArrayA = Array.isArray(a);
8203
+ const isArrayB = Array.isArray(b);
8204
+ if (isArrayA || isArrayB) {
8205
+ if (!isArrayA || !isArrayB) {
8206
+ return false;
8207
+ }
8208
+ return shallowArray(a, b);
8209
+ }
8210
+ return shallowObj(a, b);
7124
8211
  }
7125
8212
 
7126
8213
  // src/index.ts
@@ -7132,9 +8219,13 @@ export {
7132
8219
  LiveList,
7133
8220
  LiveMap,
7134
8221
  LiveObject,
8222
+ NotificationsApiError,
7135
8223
  OpCode,
7136
8224
  ServerMsgCode,
7137
8225
  WebsocketCloseCodes,
8226
+ ackOp,
8227
+ addReaction,
8228
+ applyOptimisticUpdates,
7138
8229
  asPos,
7139
8230
  assert,
7140
8231
  assertNever,
@@ -7143,10 +8234,10 @@ export {
7143
8234
  fancy_console_exports as console,
7144
8235
  convertToCommentData,
7145
8236
  convertToCommentUserReaction,
8237
+ convertToInboxNotificationData,
7146
8238
  convertToThreadData,
7147
- createAsyncCache,
7148
8239
  createClient,
7149
- createCommentsApi,
8240
+ deleteComment,
7150
8241
  deprecate,
7151
8242
  deprecateIf,
7152
8243
  detectDupes,
@@ -7160,20 +8251,24 @@ export {
7160
8251
  isLiveNode,
7161
8252
  isPlainObject,
7162
8253
  isRootCrdt,
8254
+ kInternal,
7163
8255
  legacy_patchImmutableObject,
7164
8256
  lsonToJson,
7165
8257
  makeEventSource,
7166
8258
  makePoller,
7167
8259
  makePosition,
7168
8260
  nn,
8261
+ objectToQuery,
7169
8262
  patchLiveObjectKey,
7170
8263
  raise,
8264
+ removeReaction,
7171
8265
  shallow,
7172
8266
  stringify,
7173
8267
  stringifyCommentBody,
7174
8268
  throwUsageError,
7175
8269
  toPlainLson,
7176
8270
  tryParseJson,
8271
+ upsertComment,
7177
8272
  withTimeout
7178
8273
  };
7179
8274
  //# sourceMappingURL=index.mjs.map