@liveblocks/core 1.9.8-pre1 → 1.10.0-beta2

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.9.8-pre1";
9
+ var PKG_VERSION = "1.10.0-beta2";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -652,7 +652,6 @@ var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
652
652
 
653
653
  // src/types/IWebSocket.ts
654
654
  var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
655
- WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_NORMAL"] = 1e3] = "CLOSE_NORMAL";
656
655
  WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
657
656
  WebsocketCloseCodes2[WebsocketCloseCodes2["UNEXPECTED_CONDITION"] = 1011] = "UNEXPECTED_CONDITION";
658
657
  WebsocketCloseCodes2[WebsocketCloseCodes2["TRY_AGAIN_LATER"] = 1013] = "TRY_AGAIN_LATER";
@@ -662,7 +661,6 @@ var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
662
661
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
663
662
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
664
663
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
665
- WebsocketCloseCodes2[WebsocketCloseCodes2["KICKED"] = 4100] = "KICKED";
666
664
  WebsocketCloseCodes2[WebsocketCloseCodes2["TOKEN_EXPIRED"] = 4109] = "TOKEN_EXPIRED";
667
665
  WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
668
666
  return WebsocketCloseCodes2;
@@ -1327,7 +1325,7 @@ function createAuthManager(authOptions) {
1327
1325
  }
1328
1326
  return false;
1329
1327
  }
1330
- function getCachedToken(requestedScope, roomId) {
1328
+ function getCachedToken(roomOptions) {
1331
1329
  const now = Math.ceil(Date.now() / 1e3);
1332
1330
  for (let i = tokens.length - 1; i >= 0; i--) {
1333
1331
  const token = tokens[i];
@@ -1340,8 +1338,11 @@ function createAuthManager(authOptions) {
1340
1338
  if (token.parsed.k === "id" /* ID_TOKEN */) {
1341
1339
  return token;
1342
1340
  } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
1341
+ if (!roomOptions) {
1342
+ return token;
1343
+ }
1343
1344
  for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
1344
- if (resource.includes("*") && roomId.startsWith(resource.replace("*", "")) || roomId === resource && hasCorrespondingScopes(requestedScope, scopes)) {
1345
+ if (resource.includes("*") && roomOptions.roomId.startsWith(resource.replace("*", "")) || roomOptions.roomId === resource && hasCorrespondingScopes(roomOptions.requestedScope, scopes)) {
1345
1346
  return token;
1346
1347
  }
1347
1348
  }
@@ -1390,18 +1391,27 @@ function createAuthManager(authOptions) {
1390
1391
  "Unexpected authentication type. Must be private or custom."
1391
1392
  );
1392
1393
  }
1393
- async function getAuthValue(requestedScope, roomId) {
1394
+ async function getAuthValue(roomOptions) {
1394
1395
  if (authentication.type === "public") {
1395
1396
  return { type: "public", publicApiKey: authentication.publicApiKey };
1396
1397
  }
1397
- const cachedToken = getCachedToken(requestedScope, roomId);
1398
+ const cachedToken = getCachedToken(roomOptions);
1398
1399
  if (cachedToken !== void 0) {
1399
1400
  return { type: "secret", token: cachedToken };
1400
1401
  }
1401
- let currentPromise = requestPromises.get(roomId);
1402
- if (currentPromise === void 0) {
1403
- currentPromise = makeAuthRequest(roomId);
1404
- requestPromises.set(roomId, currentPromise);
1402
+ let currentPromise;
1403
+ if (roomOptions) {
1404
+ currentPromise = requestPromises.get(roomOptions.roomId);
1405
+ if (currentPromise === void 0) {
1406
+ currentPromise = makeAuthRequest(roomOptions.roomId);
1407
+ requestPromises.set(roomOptions.roomId, currentPromise);
1408
+ }
1409
+ } else {
1410
+ currentPromise = requestPromises.get("liveblocks-user-token");
1411
+ if (currentPromise === void 0) {
1412
+ currentPromise = makeAuthRequest();
1413
+ requestPromises.set("liveblocks-user-token", currentPromise);
1414
+ }
1405
1415
  }
1406
1416
  try {
1407
1417
  const token = await currentPromise;
@@ -1414,7 +1424,11 @@ function createAuthManager(authOptions) {
1414
1424
  }
1415
1425
  return { type: "secret", token };
1416
1426
  } finally {
1417
- requestPromises.delete(roomId);
1427
+ if (roomOptions) {
1428
+ requestPromises.delete(roomOptions.roomId);
1429
+ } else {
1430
+ requestPromises.delete("liveblocks-user-token");
1431
+ }
1418
1432
  }
1419
1433
  }
1420
1434
  return {
@@ -1503,6 +1517,9 @@ async function fetchAuthEndpoint(fetch2, endpoint, body) {
1503
1517
  // src/constants.ts
1504
1518
  var DEFAULT_BASE_URL = "https://api.liveblocks.io";
1505
1519
 
1520
+ // src/internal.ts
1521
+ var kInternal = Symbol();
1522
+
1506
1523
  // src/devtools/bridge.ts
1507
1524
  var _bridgeActive = false;
1508
1525
  function activateBridge(allowed) {
@@ -1632,7 +1649,7 @@ function partialSyncStorage(room) {
1632
1649
  }
1633
1650
  }
1634
1651
  function partialSyncMe(room) {
1635
- const me = room.__internal.getSelf_forDevTools();
1652
+ const me = room[kInternal].getSelf_forDevTools();
1636
1653
  if (me) {
1637
1654
  sendToPanel({
1638
1655
  msg: "room::sync::partial",
@@ -1642,7 +1659,7 @@ function partialSyncMe(room) {
1642
1659
  }
1643
1660
  }
1644
1661
  function partialSyncOthers(room) {
1645
- const others = room.__internal.getOthers_forDevTools();
1662
+ const others = room[kInternal].getOthers_forDevTools();
1646
1663
  if (others) {
1647
1664
  sendToPanel({
1648
1665
  msg: "room::sync::partial",
@@ -1653,8 +1670,8 @@ function partialSyncOthers(room) {
1653
1670
  }
1654
1671
  function fullSync(room) {
1655
1672
  const root = room.getStorageSnapshot();
1656
- const me = room.__internal.getSelf_forDevTools();
1657
- const others = room.__internal.getOthers_forDevTools();
1673
+ const me = room[kInternal].getSelf_forDevTools();
1674
+ const others = room[kInternal].getOthers_forDevTools();
1658
1675
  room.fetchYDoc("");
1659
1676
  sendToPanel({
1660
1677
  msg: "room::sync::full",
@@ -1713,6 +1730,185 @@ function unlinkDevTools(roomId) {
1713
1730
  });
1714
1731
  }
1715
1732
 
1733
+ // src/lib/stringify.ts
1734
+ function stringify(object, ...args) {
1735
+ if (typeof object !== "object" || object === null || Array.isArray(object)) {
1736
+ return JSON.stringify(object, ...args);
1737
+ }
1738
+ const sortedObject = Object.keys(object).sort().reduce(
1739
+ (sortedObject2, key) => {
1740
+ sortedObject2[key] = object[key];
1741
+ return sortedObject2;
1742
+ },
1743
+ {}
1744
+ );
1745
+ return JSON.stringify(sortedObject, ...args);
1746
+ }
1747
+
1748
+ // src/lib/batch.ts
1749
+ var DEFAULT_SIZE = 50;
1750
+ var DEFAULT_DELAY = 100;
1751
+ var noop = () => {
1752
+ };
1753
+ var BatchCall = class {
1754
+ constructor(args) {
1755
+ this.resolve = noop;
1756
+ this.reject = noop;
1757
+ this.promise = new Promise(noop);
1758
+ this.args = args;
1759
+ }
1760
+ };
1761
+ var Batch = class {
1762
+ constructor(callback, options) {
1763
+ this.queue = [];
1764
+ this.error = false;
1765
+ this.callback = callback;
1766
+ this.size = options?.size ?? DEFAULT_SIZE;
1767
+ this.delay = options?.delay ?? DEFAULT_DELAY;
1768
+ }
1769
+ clearDelayTimeout() {
1770
+ if (this.delayTimeoutId !== void 0) {
1771
+ clearTimeout(this.delayTimeoutId);
1772
+ this.delayTimeoutId = void 0;
1773
+ }
1774
+ }
1775
+ schedule() {
1776
+ if (this.queue.length === this.size) {
1777
+ void this.flush();
1778
+ } else if (this.queue.length === 1) {
1779
+ this.clearDelayTimeout();
1780
+ this.delayTimeoutId = setTimeout(() => void this.flush(), this.delay);
1781
+ }
1782
+ }
1783
+ async flush() {
1784
+ if (this.queue.length === 0) {
1785
+ return;
1786
+ }
1787
+ const calls = this.queue.splice(0);
1788
+ const args = calls.map((call) => call.args);
1789
+ try {
1790
+ const results = await this.callback(args);
1791
+ this.error = false;
1792
+ calls.forEach((call, index) => {
1793
+ const result = results?.[index];
1794
+ if (result instanceof Error) {
1795
+ call.reject(result);
1796
+ } else if (result !== void 0) {
1797
+ call.resolve(result);
1798
+ } else {
1799
+ if (Array.isArray(results)) {
1800
+ call.reject(
1801
+ new Error(
1802
+ `Batch callback must return an array of the same length as the number of calls in the batch. Expected ${calls.length}, but got ${results.length}.`
1803
+ )
1804
+ );
1805
+ } else {
1806
+ call.reject(new Error("Batch callback must return an array."));
1807
+ }
1808
+ }
1809
+ });
1810
+ } catch (error3) {
1811
+ this.error = true;
1812
+ calls.forEach((call) => {
1813
+ call.reject(error3);
1814
+ });
1815
+ }
1816
+ }
1817
+ get(...args) {
1818
+ const existingCall = this.queue.find(
1819
+ (call2) => stringify(call2.args) === stringify(args)
1820
+ );
1821
+ if (existingCall) {
1822
+ return existingCall.promise;
1823
+ }
1824
+ const call = new BatchCall(args);
1825
+ call.promise = new Promise((resolve, reject) => {
1826
+ call.resolve = resolve;
1827
+ call.reject = reject;
1828
+ });
1829
+ this.queue.push(call);
1830
+ this.schedule();
1831
+ return call.promise;
1832
+ }
1833
+ clear() {
1834
+ this.queue = [];
1835
+ this.error = false;
1836
+ this.clearDelayTimeout();
1837
+ }
1838
+ };
1839
+ function createBatchStore(callback, options) {
1840
+ const batch = new Batch(callback, options);
1841
+ const cache = /* @__PURE__ */ new Map();
1842
+ const eventSource2 = makeEventSource();
1843
+ function getCacheKey(args) {
1844
+ return stringify(args);
1845
+ }
1846
+ function setStateAndNotify(cacheKey, state) {
1847
+ if (state) {
1848
+ cache.set(cacheKey, state);
1849
+ } else {
1850
+ cache.delete(cacheKey);
1851
+ }
1852
+ eventSource2.notify(state);
1853
+ }
1854
+ async function get(...args) {
1855
+ const cacheKey = getCacheKey(args);
1856
+ if (cache.has(cacheKey)) {
1857
+ return;
1858
+ }
1859
+ try {
1860
+ setStateAndNotify(cacheKey, { isLoading: true });
1861
+ const result = await batch.get(...args);
1862
+ setStateAndNotify(cacheKey, { isLoading: false, data: result });
1863
+ } catch (error3) {
1864
+ setStateAndNotify(cacheKey, {
1865
+ isLoading: false,
1866
+ error: error3
1867
+ });
1868
+ }
1869
+ }
1870
+ function getState(...args) {
1871
+ const cacheKey = getCacheKey(args);
1872
+ return cache.get(cacheKey);
1873
+ }
1874
+ return {
1875
+ ...eventSource2,
1876
+ get,
1877
+ getState
1878
+ };
1879
+ }
1880
+
1881
+ // src/lib/create-store.ts
1882
+ function createStore(initialState) {
1883
+ let state = initialState;
1884
+ const subscribers = /* @__PURE__ */ new Set();
1885
+ function get() {
1886
+ return state;
1887
+ }
1888
+ function set(callback) {
1889
+ const newState = callback(state);
1890
+ if (state === newState) {
1891
+ return;
1892
+ }
1893
+ state = newState;
1894
+ for (const subscriber of subscribers) {
1895
+ subscriber(state);
1896
+ }
1897
+ }
1898
+ function subscribe(callback) {
1899
+ subscribers.add(callback);
1900
+ callback(state);
1901
+ return () => {
1902
+ subscribers.delete(callback);
1903
+ };
1904
+ }
1905
+ return {
1906
+ get,
1907
+ set,
1908
+ subscribe
1909
+ };
1910
+ }
1911
+
1716
1912
  // src/lib/deprecation.ts
1717
1913
  var _emittedDeprecationWarnings = /* @__PURE__ */ new Set();
1718
1914
  function deprecate(message, key = message) {
@@ -1746,1016 +1942,612 @@ function errorIf(condition, message) {
1746
1942
  }
1747
1943
  }
1748
1944
 
1749
- // src/comments/comment-body.ts
1750
- function isCommentBodyParagraph(element) {
1751
- return "type" in element && element.type === "mention";
1945
+ // src/convert-plain-data.ts
1946
+ function convertToCommentData(data) {
1947
+ const editedAt = data.editedAt ? new Date(data.editedAt) : void 0;
1948
+ const createdAt = new Date(data.createdAt);
1949
+ const reactions = data.reactions.map((reaction) => ({
1950
+ ...reaction,
1951
+ createdAt: new Date(reaction.createdAt)
1952
+ }));
1953
+ if (data.body) {
1954
+ return {
1955
+ ...data,
1956
+ reactions,
1957
+ createdAt,
1958
+ editedAt
1959
+ };
1960
+ } else {
1961
+ const deletedAt = new Date(data.deletedAt);
1962
+ return {
1963
+ ...data,
1964
+ reactions,
1965
+ createdAt,
1966
+ editedAt,
1967
+ deletedAt
1968
+ };
1969
+ }
1752
1970
  }
1753
- function isCommentBodyText(element) {
1754
- return "text" in element && typeof element.text === "string";
1971
+ function convertToThreadData(data) {
1972
+ const updatedAt = data.updatedAt ? new Date(data.updatedAt) : void 0;
1973
+ const createdAt = new Date(data.createdAt);
1974
+ const comments = data.comments.map(
1975
+ (comment) => convertToCommentData(comment)
1976
+ );
1977
+ return {
1978
+ ...data,
1979
+ createdAt,
1980
+ updatedAt,
1981
+ comments
1982
+ };
1755
1983
  }
1756
- function isCommentBodyMention(element) {
1757
- return "type" in element && element.type === "mention";
1984
+ function convertToCommentUserReaction(data) {
1985
+ return {
1986
+ ...data,
1987
+ createdAt: new Date(data.createdAt)
1988
+ };
1758
1989
  }
1759
- function isCommentBodyLink(element) {
1760
- return "type" in element && element.type === "link";
1990
+ function convertToInboxNotificationData(data) {
1991
+ const notifiedAt = new Date(data.notifiedAt);
1992
+ const readAt = data.readAt ? new Date(data.readAt) : null;
1993
+ return {
1994
+ ...data,
1995
+ notifiedAt,
1996
+ readAt
1997
+ };
1761
1998
  }
1762
- var commentBodyElementsGuards = {
1763
- paragraph: isCommentBodyParagraph,
1764
- text: isCommentBodyText,
1765
- link: isCommentBodyLink,
1766
- mention: isCommentBodyMention
1767
- };
1768
- var commentBodyElementsTypes = {
1769
- paragraph: "block",
1770
- text: "inline",
1771
- link: "inline",
1772
- mention: "inline"
1773
- };
1774
- function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
1775
- if (!body || !body?.content) {
1776
- return;
1777
- }
1778
- const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
1779
- const type = element ? commentBodyElementsTypes[element] : "all";
1780
- const guard = element ? commentBodyElementsGuards[element] : () => true;
1781
- const visitor = typeof elementOrVisitor === "function" ? elementOrVisitor : possiblyVisitor;
1782
- for (const block of body.content) {
1783
- if (type === "all" || type === "block") {
1784
- if (guard(block)) {
1785
- visitor?.(block);
1786
- }
1999
+
2000
+ // src/notifications.ts
2001
+ var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY = 50;
2002
+ function createInboxNotificationsApi({
2003
+ baseUrl,
2004
+ authManager,
2005
+ currentUserIdStore,
2006
+ fetcher
2007
+ }) {
2008
+ async function fetchJson(endpoint, options) {
2009
+ const authValue = await authManager.getAuthValue();
2010
+ if (authValue.type === "secret" && authValue.token.parsed.k === "acc" /* ACCESS_TOKEN */) {
2011
+ const userId = authValue.token.parsed.uid;
2012
+ currentUserIdStore.set(() => userId);
1787
2013
  }
1788
- if (type === "all" || type === "inline") {
1789
- for (const inline of block.children) {
1790
- if (guard(inline)) {
1791
- visitor?.(inline);
2014
+ const url = new URL(`/v2/c${endpoint}`, baseUrl);
2015
+ const response = await fetcher(url.toString(), {
2016
+ ...options,
2017
+ headers: {
2018
+ ...options?.headers,
2019
+ Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
2020
+ }
2021
+ });
2022
+ if (!response.ok) {
2023
+ if (response.status >= 400 && response.status < 600) {
2024
+ let error3;
2025
+ try {
2026
+ const errorBody = await response.json();
2027
+ error3 = new NotificationsApiError(
2028
+ errorBody.message,
2029
+ response.status,
2030
+ errorBody
2031
+ );
2032
+ } catch {
2033
+ error3 = new NotificationsApiError(
2034
+ response.statusText,
2035
+ response.status
2036
+ );
1792
2037
  }
2038
+ throw error3;
1793
2039
  }
1794
2040
  }
2041
+ let body;
2042
+ try {
2043
+ body = await response.json();
2044
+ } catch {
2045
+ body = {};
2046
+ }
2047
+ return body;
1795
2048
  }
1796
- }
1797
- function getMentionedIdsFromCommentBody(body) {
1798
- const mentionedIds = /* @__PURE__ */ new Set();
1799
- traverseCommentBody(
1800
- body,
1801
- "mention",
1802
- (mention) => mentionedIds.add(mention.id)
1803
- );
1804
- return Array.from(mentionedIds);
1805
- }
1806
- async function resolveUsersInCommentBody(body, resolveUsers) {
1807
- const resolvedUsers = /* @__PURE__ */ new Map();
1808
- if (!resolveUsers) {
1809
- return resolvedUsers;
2049
+ async function getInboxNotifications(options) {
2050
+ const queryParams = toURLSearchParams({ limit: options?.limit });
2051
+ const json = await fetchJson(`/inbox-notifications?${queryParams.toString()}`);
2052
+ return {
2053
+ threads: json.threads.map((thread) => convertToThreadData(thread)),
2054
+ inboxNotifications: json.inboxNotifications.map(
2055
+ (notification) => convertToInboxNotificationData(notification)
2056
+ )
2057
+ };
1810
2058
  }
1811
- const userIds = getMentionedIdsFromCommentBody(body);
1812
- const users = await resolveUsers({
1813
- userIds
1814
- });
1815
- for (const [index, userId] of userIds.entries()) {
1816
- const user = users?.[index];
1817
- if (user) {
1818
- resolvedUsers.set(userId, user);
1819
- }
2059
+ async function getUnreadInboxNotificationsCount() {
2060
+ const { count } = await fetchJson("/inbox-notifications/count");
2061
+ return count;
1820
2062
  }
1821
- return resolvedUsers;
1822
- }
1823
- var htmlEscapables = {
1824
- "&": "&amp;",
1825
- "<": "&lt;",
1826
- ">": "&gt;",
1827
- '"': "&quot;",
1828
- "'": "&#39;"
1829
- };
1830
- var htmlEscapablesRegex = new RegExp(
1831
- Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
1832
- "g"
1833
- );
1834
- function htmlSafe(value) {
1835
- return new HtmlSafeString([String(value)], []);
1836
- }
1837
- function joinHtml(strings) {
1838
- if (strings.length <= 0) {
1839
- return new HtmlSafeString([""], []);
2063
+ async function markAllInboxNotificationsAsRead() {
2064
+ await fetchJson("/inbox-notifications/read", {
2065
+ method: "POST",
2066
+ headers: {
2067
+ "Content-Type": "application/json"
2068
+ },
2069
+ body: JSON.stringify({ inboxNotificationIds: "all" })
2070
+ });
1840
2071
  }
1841
- return new HtmlSafeString(
1842
- ["", ...Array(strings.length - 1).fill(""), ""],
1843
- strings
2072
+ async function markInboxNotificationsAsRead(inboxNotificationIds) {
2073
+ await fetchJson("/inbox-notifications/read", {
2074
+ method: "POST",
2075
+ headers: {
2076
+ "Content-Type": "application/json"
2077
+ },
2078
+ body: JSON.stringify({ inboxNotificationIds })
2079
+ });
2080
+ }
2081
+ const batchedMarkInboxNotificationsAsRead = new Batch(
2082
+ async (batchedInboxNotificationIds) => {
2083
+ const inboxNotificationIds = batchedInboxNotificationIds.flat();
2084
+ await markInboxNotificationsAsRead(inboxNotificationIds);
2085
+ return inboxNotificationIds;
2086
+ },
2087
+ { delay: MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY }
1844
2088
  );
1845
- }
1846
- function escapeHtml(value) {
1847
- if (value instanceof HtmlSafeString) {
1848
- return value.toString();
2089
+ async function markInboxNotificationAsRead(inboxNotificationId) {
2090
+ await batchedMarkInboxNotificationsAsRead.get(inboxNotificationId);
1849
2091
  }
1850
- if (Array.isArray(value)) {
1851
- return joinHtml(value).toString();
1852
- }
1853
- return String(value).replace(
1854
- htmlEscapablesRegex,
1855
- (character) => htmlEscapables[character]
1856
- );
2092
+ return {
2093
+ getInboxNotifications,
2094
+ getUnreadInboxNotificationsCount,
2095
+ markAllInboxNotificationsAsRead,
2096
+ markInboxNotificationAsRead
2097
+ };
1857
2098
  }
1858
- var HtmlSafeString = class {
1859
- constructor(strings, values) {
1860
- this._strings = strings;
1861
- this._values = values;
1862
- }
1863
- toString() {
1864
- return this._strings.reduce((result, str, i) => {
1865
- return result + escapeHtml(nn(this._values[i - 1])) + str;
1866
- });
2099
+ function toURLSearchParams(params) {
2100
+ const result = new URLSearchParams();
2101
+ for (const [key, value] of Object.entries(params)) {
2102
+ if (value !== void 0 && value !== null) {
2103
+ result.set(key, value.toString());
2104
+ }
1867
2105
  }
1868
- };
1869
- function html(strings, ...values) {
1870
- return new HtmlSafeString(strings, values);
2106
+ return result;
1871
2107
  }
1872
- var markdownEscapables = {
1873
- _: "\\_",
1874
- "*": "\\*",
1875
- "#": "\\#",
1876
- "`": "\\`",
1877
- "~": "\\~",
1878
- "!": "\\!",
1879
- "|": "\\|",
1880
- "(": "\\(",
1881
- ")": "\\)",
1882
- "{": "\\{",
1883
- "}": "\\}",
1884
- "[": "\\[",
1885
- "]": "\\]"
1886
- };
1887
- var markdownEscapablesRegex = new RegExp(
1888
- Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
1889
- "g"
1890
- );
1891
- function joinMarkdown(strings) {
1892
- if (strings.length <= 0) {
1893
- return new MarkdownSafeString([""], []);
2108
+
2109
+ // src/lib/position.ts
2110
+ var MIN_CODE = 32;
2111
+ var MAX_CODE = 126;
2112
+ var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
2113
+ var ZERO = nthDigit(0);
2114
+ var ONE = nthDigit(1);
2115
+ var ZERO_NINE = ZERO + nthDigit(-1);
2116
+ function nthDigit(n) {
2117
+ const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
2118
+ if (code < MIN_CODE || code > MAX_CODE) {
2119
+ throw new Error(`Invalid n value: ${n}`);
1894
2120
  }
1895
- return new MarkdownSafeString(
1896
- ["", ...Array(strings.length - 1).fill(""), ""],
1897
- strings
1898
- );
2121
+ return String.fromCharCode(code);
1899
2122
  }
1900
- function escapeMarkdown(value) {
1901
- if (value instanceof MarkdownSafeString) {
1902
- return value.toString();
1903
- }
1904
- if (Array.isArray(value)) {
1905
- return joinMarkdown(value).toString();
2123
+ function makePosition(x, y) {
2124
+ if (x !== void 0 && y !== void 0) {
2125
+ return between(x, y);
2126
+ } else if (x !== void 0) {
2127
+ return after(x);
2128
+ } else if (y !== void 0) {
2129
+ return before(y);
2130
+ } else {
2131
+ return ONE;
1906
2132
  }
1907
- return String(value).replace(
1908
- markdownEscapablesRegex,
1909
- (character) => markdownEscapables[character]
1910
- );
1911
2133
  }
1912
- var MarkdownSafeString = class {
1913
- constructor(strings, values) {
1914
- this._strings = strings;
1915
- this._values = values;
1916
- }
1917
- toString() {
1918
- return this._strings.reduce((result, str, i) => {
1919
- return result + escapeMarkdown(nn(this._values[i - 1])) + str;
1920
- });
2134
+ function before(pos) {
2135
+ const lastIndex = pos.length - 1;
2136
+ for (let i = 0; i <= lastIndex; i++) {
2137
+ const code = pos.charCodeAt(i);
2138
+ if (code <= MIN_CODE) {
2139
+ continue;
2140
+ }
2141
+ if (i === lastIndex) {
2142
+ if (code === MIN_CODE + 1) {
2143
+ return pos.substring(0, i) + ZERO_NINE;
2144
+ } else {
2145
+ return pos.substring(0, i) + String.fromCharCode(code - 1);
2146
+ }
2147
+ } else {
2148
+ return pos.substring(0, i + 1);
2149
+ }
1921
2150
  }
1922
- };
1923
- function markdown(strings, ...values) {
1924
- return new MarkdownSafeString(strings, values);
2151
+ return ONE;
1925
2152
  }
1926
- function toAbsoluteUrl(url) {
1927
- if (url.startsWith("http://") || url.startsWith("https://")) {
1928
- return url;
1929
- } else if (url.startsWith("www.")) {
1930
- return "https://" + url;
2153
+ function after(pos) {
2154
+ for (let i = 0; i <= pos.length - 1; i++) {
2155
+ const code = pos.charCodeAt(i);
2156
+ if (code >= MAX_CODE) {
2157
+ continue;
2158
+ }
2159
+ return pos.substring(0, i) + String.fromCharCode(code + 1);
1931
2160
  }
1932
- return;
2161
+ return pos + ONE;
1933
2162
  }
1934
- var stringifyCommentBodyPlainElements = {
1935
- paragraph: ({ children }) => children,
1936
- text: ({ element }) => element.text,
1937
- link: ({ element }) => element.url,
1938
- mention: ({ element, user }) => {
1939
- return `@${user?.name ?? element.id}`;
2163
+ function between(lo, hi) {
2164
+ if (lo < hi) {
2165
+ return _between(lo, hi);
2166
+ } else if (lo > hi) {
2167
+ return _between(hi, lo);
2168
+ } else {
2169
+ throw new Error("Cannot compute value between two equal positions");
1940
2170
  }
1941
- };
1942
- var stringifyCommentBodyHtmlElements = {
1943
- paragraph: ({ children }) => {
1944
- return children ? html`<p>${htmlSafe(children)}</p>` : children;
1945
- },
1946
- text: ({ element }) => {
1947
- let children = element.text;
1948
- if (!children) {
1949
- return children;
1950
- }
1951
- if (element.bold) {
1952
- children = html`<strong>${children}</strong>`;
1953
- }
1954
- if (element.italic) {
1955
- children = html`<em>${children}</em>`;
1956
- }
1957
- if (element.strikethrough) {
1958
- children = html`<s>${children}</s>`;
2171
+ }
2172
+ function _between(lo, hi) {
2173
+ let index = 0;
2174
+ const loLen = lo.length;
2175
+ const hiLen = hi.length;
2176
+ while (true) {
2177
+ const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
2178
+ const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
2179
+ if (loCode === hiCode) {
2180
+ index++;
2181
+ continue;
1959
2182
  }
1960
- if (element.code) {
1961
- children = html`<code>${children}</code>`;
2183
+ if (hiCode - loCode === 1) {
2184
+ const size = index + 1;
2185
+ let prefix = lo.substring(0, size);
2186
+ if (prefix.length < size) {
2187
+ prefix += ZERO.repeat(size - prefix.length);
2188
+ }
2189
+ const suffix = lo.substring(size);
2190
+ const nines = "";
2191
+ return prefix + _between(suffix, nines);
2192
+ } else {
2193
+ return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
1962
2194
  }
1963
- return children;
1964
- },
1965
- link: ({ element, href }) => {
1966
- return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
1967
- },
1968
- mention: ({ element, user }) => {
1969
- return html`<span data-mention>@${user?.name ?? element.id}</span>`;
1970
2195
  }
1971
- };
1972
- var stringifyCommentBodyMarkdownElements = {
1973
- paragraph: ({ children }) => {
1974
- return children;
1975
- },
1976
- text: ({ element }) => {
1977
- let children = element.text;
1978
- if (!children) {
1979
- return children;
1980
- }
1981
- if (element.bold) {
1982
- children = markdown`**${children}**`;
1983
- }
1984
- if (element.italic) {
1985
- children = markdown`_${children}_`;
1986
- }
1987
- if (element.strikethrough) {
1988
- children = markdown`~~${children}~~`;
1989
- }
1990
- if (element.code) {
1991
- children = markdown`\`${children}\``;
2196
+ }
2197
+ function takeN(pos, n) {
2198
+ return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
2199
+ }
2200
+ var MIN_NON_ZERO_CODE = MIN_CODE + 1;
2201
+ function isPos(str) {
2202
+ if (str === "") {
2203
+ return false;
2204
+ }
2205
+ const lastIdx = str.length - 1;
2206
+ const last = str.charCodeAt(lastIdx);
2207
+ if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
2208
+ return false;
2209
+ }
2210
+ for (let i = 0; i < lastIdx; i++) {
2211
+ const code = str.charCodeAt(i);
2212
+ if (code < MIN_CODE || code > MAX_CODE) {
2213
+ return false;
1992
2214
  }
1993
- return children;
1994
- },
1995
- link: ({ element, href }) => {
1996
- return markdown`[${element.url}](${href})`;
1997
- },
1998
- mention: ({ element, user }) => {
1999
- return markdown`@${user?.name ?? element.id}`;
2000
2215
  }
2001
- };
2002
- async function stringifyCommentBody(body, options) {
2003
- const format = options?.format ?? "plain";
2004
- const separator = options?.separator ?? (format === "markdown" ? "\n\n" : "\n");
2005
- const elements = {
2006
- ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
2007
- ...options?.elements
2008
- };
2009
- const resolvedUsers = await resolveUsersInCommentBody(
2010
- body,
2011
- options?.resolveUsers
2012
- );
2013
- const blocks = body.content.flatMap((block, blockIndex) => {
2014
- switch (block.type) {
2015
- case "paragraph": {
2016
- const inlines = block.children.flatMap((inline, inlineIndex) => {
2017
- if (isCommentBodyMention(inline)) {
2018
- return inline.id ? [
2019
- elements.mention(
2020
- {
2021
- element: inline,
2022
- user: resolvedUsers.get(inline.id)
2023
- },
2024
- inlineIndex
2025
- )
2026
- ] : [];
2027
- }
2028
- if (isCommentBodyLink(inline)) {
2029
- return [
2030
- elements.link(
2031
- {
2032
- element: inline,
2033
- href: toAbsoluteUrl(inline.url) ?? inline.url
2034
- },
2035
- inlineIndex
2036
- )
2037
- ];
2038
- }
2039
- if (isCommentBodyText(inline)) {
2040
- return [elements.text({ element: inline }, inlineIndex)];
2041
- }
2042
- return [];
2043
- });
2044
- return [
2045
- elements.paragraph(
2046
- { element: block, children: inlines.join("") },
2047
- blockIndex
2048
- )
2049
- ];
2050
- }
2051
- default:
2052
- return [];
2053
- }
2054
- });
2055
- return blocks.join(separator);
2216
+ return true;
2056
2217
  }
2057
- function convertToCommentData(data) {
2058
- const editedAt = data.editedAt ? new Date(data.editedAt) : void 0;
2059
- const createdAt = new Date(data.createdAt);
2060
- const reactions = data.reactions.map((reaction) => ({
2061
- ...reaction,
2062
- createdAt: new Date(reaction.createdAt)
2063
- }));
2064
- if (data.body) {
2065
- return {
2066
- ...data,
2067
- reactions,
2068
- createdAt,
2069
- editedAt
2070
- };
2071
- } else {
2072
- const deletedAt = new Date(data.deletedAt);
2073
- return {
2074
- ...data,
2075
- reactions,
2076
- createdAt,
2077
- editedAt,
2078
- deletedAt
2079
- };
2218
+ function convertToPos(str) {
2219
+ const codes = [];
2220
+ for (let i = 0; i < str.length; i++) {
2221
+ const code = str.charCodeAt(i);
2222
+ codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
2080
2223
  }
2081
- }
2082
- function convertToThreadData(data) {
2083
- const updatedAt = data.updatedAt ? new Date(data.updatedAt) : void 0;
2084
- const createdAt = new Date(data.createdAt);
2085
- const comments = data.comments.map(
2086
- (comment) => convertToCommentData(comment)
2224
+ while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
2225
+ codes.length--;
2226
+ }
2227
+ return codes.length > 0 ? String.fromCharCode(...codes) : (
2228
+ // Edge case: the str was a 0-only string, which is invalid. Default back to .1
2229
+ ONE
2087
2230
  );
2088
- return {
2089
- ...data,
2090
- createdAt,
2091
- updatedAt,
2092
- comments
2093
- };
2094
2231
  }
2095
- function convertToCommentUserReaction(data) {
2232
+ function asPos(str) {
2233
+ return isPos(str) ? str : convertToPos(str);
2234
+ }
2235
+
2236
+ // src/protocol/Op.ts
2237
+ var OpCode = /* @__PURE__ */ ((OpCode2) => {
2238
+ OpCode2[OpCode2["INIT"] = 0] = "INIT";
2239
+ OpCode2[OpCode2["SET_PARENT_KEY"] = 1] = "SET_PARENT_KEY";
2240
+ OpCode2[OpCode2["CREATE_LIST"] = 2] = "CREATE_LIST";
2241
+ OpCode2[OpCode2["UPDATE_OBJECT"] = 3] = "UPDATE_OBJECT";
2242
+ OpCode2[OpCode2["CREATE_OBJECT"] = 4] = "CREATE_OBJECT";
2243
+ OpCode2[OpCode2["DELETE_CRDT"] = 5] = "DELETE_CRDT";
2244
+ OpCode2[OpCode2["DELETE_OBJECT_KEY"] = 6] = "DELETE_OBJECT_KEY";
2245
+ OpCode2[OpCode2["CREATE_MAP"] = 7] = "CREATE_MAP";
2246
+ OpCode2[OpCode2["CREATE_REGISTER"] = 8] = "CREATE_REGISTER";
2247
+ return OpCode2;
2248
+ })(OpCode || {});
2249
+ function ackOp(opId) {
2096
2250
  return {
2097
- ...data,
2098
- createdAt: new Date(data.createdAt)
2251
+ type: 5 /* DELETE_CRDT */,
2252
+ id: "ACK",
2253
+ // (H)ACK
2254
+ opId
2099
2255
  };
2100
2256
  }
2257
+ function isAckOp(op) {
2258
+ return op.type === 5 /* DELETE_CRDT */ && op.id === "ACK";
2259
+ }
2101
2260
 
2102
- // src/comments/index.ts
2103
- function getAuthBearerHeaderFromAuthValue(authValue) {
2104
- if (authValue.type === "public") {
2105
- return authValue.publicApiKey;
2106
- } else {
2107
- return authValue.token.raw;
2108
- }
2261
+ // src/crdts/AbstractCrdt.ts
2262
+ function crdtAsLiveNode(value) {
2263
+ return value;
2109
2264
  }
2110
- var CommentsApiError = class extends Error {
2111
- constructor(message, status, details) {
2112
- super(message);
2113
- this.message = message;
2114
- this.status = status;
2115
- this.details = details;
2265
+ function HasParent(node, key, pos = asPos(key)) {
2266
+ return Object.freeze({ type: "HasParent", node, key, pos });
2267
+ }
2268
+ var NoParent = Object.freeze({ type: "NoParent" });
2269
+ function Orphaned(oldKey, oldPos = asPos(oldKey)) {
2270
+ return Object.freeze({ type: "Orphaned", oldKey, oldPos });
2271
+ }
2272
+ var AbstractCrdt = class {
2273
+ constructor() {
2274
+ /** @internal */
2275
+ this._parent = NoParent;
2116
2276
  }
2117
- };
2118
- function createCommentsApi(roomId, getAuthValue, config) {
2119
- async function fetchJson(endpoint, options) {
2120
- const response = await fetchApi(roomId, endpoint, options);
2121
- if (!response.ok) {
2122
- if (response.status >= 400 && response.status < 600) {
2123
- let error3;
2124
- try {
2125
- const errorBody = await response.json();
2126
- error3 = new CommentsApiError(
2127
- errorBody.message,
2128
- response.status,
2129
- errorBody
2130
- );
2131
- } catch {
2132
- error3 = new CommentsApiError(response.statusText, response.status);
2133
- }
2134
- throw error3;
2135
- }
2277
+ /** @internal */
2278
+ _getParentKeyOrThrow() {
2279
+ switch (this.parent.type) {
2280
+ case "HasParent":
2281
+ return this.parent.key;
2282
+ case "NoParent":
2283
+ throw new Error("Parent key is missing");
2284
+ case "Orphaned":
2285
+ return this.parent.oldKey;
2286
+ default:
2287
+ return assertNever(this.parent, "Unknown state");
2136
2288
  }
2137
- let body;
2138
- try {
2139
- body = await response.json();
2140
- } catch {
2141
- body = {};
2289
+ }
2290
+ /** @internal */
2291
+ get _parentPos() {
2292
+ switch (this.parent.type) {
2293
+ case "HasParent":
2294
+ return this.parent.pos;
2295
+ case "NoParent":
2296
+ throw new Error("Parent key is missing");
2297
+ case "Orphaned":
2298
+ return this.parent.oldPos;
2299
+ default:
2300
+ return assertNever(this.parent, "Unknown state");
2142
2301
  }
2143
- return body;
2144
2302
  }
2145
- async function fetchApi(roomId2, endpoint, options) {
2146
- const authValue = await getAuthValue();
2147
- const url = new URL(
2148
- `/v2/c/rooms/${encodeURIComponent(roomId2)}${endpoint}`,
2149
- config.baseUrl
2150
- );
2151
- return await fetch(url.toString(), {
2152
- ...options,
2153
- headers: {
2154
- ...options?.headers,
2155
- Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
2156
- }
2157
- });
2303
+ /** @internal */
2304
+ get _pool() {
2305
+ return this.__pool;
2158
2306
  }
2159
- async function getThreads(options) {
2160
- const response = await fetchApi(roomId, "/threads/search", {
2161
- body: JSON.stringify({
2162
- ...options?.query?.metadata && { metadata: options.query.metadata }
2163
- }),
2164
- headers: {
2165
- "Content-Type": "application/json"
2166
- },
2167
- method: "POST"
2168
- });
2169
- if (response.ok) {
2170
- const json = await response.json();
2171
- return json.data.map((thread) => convertToThreadData(thread));
2172
- } else if (response.status === 404) {
2173
- return [];
2174
- } else {
2175
- throw new Error("There was an error while getting threads.");
2176
- }
2307
+ get roomId() {
2308
+ return this.__pool ? this.__pool.roomId : null;
2177
2309
  }
2178
- async function createThread({
2179
- metadata,
2180
- body,
2181
- commentId,
2182
- threadId
2183
- }) {
2184
- const thread = await fetchJson(
2185
- "/threads",
2186
- {
2187
- method: "POST",
2188
- headers: {
2189
- "Content-Type": "application/json"
2190
- },
2191
- body: JSON.stringify({
2192
- id: threadId,
2193
- comment: {
2194
- id: commentId,
2195
- body
2196
- },
2197
- metadata
2198
- })
2199
- }
2200
- );
2201
- return convertToThreadData(thread);
2310
+ /** @internal */
2311
+ get _id() {
2312
+ return this.__id;
2202
2313
  }
2203
- async function editThreadMetadata({
2204
- metadata,
2205
- threadId
2206
- }) {
2207
- return await fetchJson(
2208
- `/threads/${encodeURIComponent(threadId)}/metadata`,
2209
- {
2210
- method: "POST",
2211
- headers: {
2212
- "Content-Type": "application/json"
2213
- },
2214
- body: JSON.stringify(metadata)
2215
- }
2216
- );
2314
+ /** @internal */
2315
+ get parent() {
2316
+ return this._parent;
2217
2317
  }
2218
- async function createComment({
2219
- threadId,
2220
- commentId,
2221
- body
2222
- }) {
2223
- const comment = await fetchJson(
2224
- `/threads/${encodeURIComponent(threadId)}/comments`,
2225
- {
2226
- method: "POST",
2227
- headers: {
2228
- "Content-Type": "application/json"
2229
- },
2230
- body: JSON.stringify({
2231
- id: commentId,
2232
- body
2233
- })
2234
- }
2235
- );
2236
- return convertToCommentData(comment);
2318
+ /** @internal */
2319
+ get _parentKey() {
2320
+ switch (this.parent.type) {
2321
+ case "HasParent":
2322
+ return this.parent.key;
2323
+ case "NoParent":
2324
+ return null;
2325
+ case "Orphaned":
2326
+ return this.parent.oldKey;
2327
+ default:
2328
+ return assertNever(this.parent, "Unknown state");
2329
+ }
2237
2330
  }
2238
- async function editComment({
2239
- threadId,
2240
- commentId,
2241
- body
2242
- }) {
2243
- const comment = await fetchJson(
2244
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2245
- commentId
2246
- )}`,
2247
- {
2248
- method: "POST",
2249
- headers: {
2250
- "Content-Type": "application/json"
2251
- },
2252
- body: JSON.stringify({
2253
- body
2254
- })
2331
+ /** @internal */
2332
+ _apply(op, _isLocal) {
2333
+ switch (op.type) {
2334
+ case 5 /* DELETE_CRDT */: {
2335
+ if (this.parent.type === "HasParent") {
2336
+ return this.parent.node._detachChild(crdtAsLiveNode(this));
2337
+ }
2338
+ return { modified: false };
2255
2339
  }
2256
- );
2257
- return convertToCommentData(comment);
2340
+ }
2341
+ return { modified: false };
2258
2342
  }
2259
- async function deleteComment({
2260
- threadId,
2261
- commentId
2262
- }) {
2263
- await fetchJson(
2264
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2265
- commentId
2266
- )}`,
2267
- {
2268
- method: "DELETE"
2343
+ /** @internal */
2344
+ _setParentLink(newParentNode, newParentKey) {
2345
+ switch (this.parent.type) {
2346
+ case "HasParent":
2347
+ if (this.parent.node !== newParentNode) {
2348
+ throw new Error("Cannot set parent: node already has a parent");
2349
+ } else {
2350
+ this._parent = HasParent(newParentNode, newParentKey);
2351
+ return;
2352
+ }
2353
+ case "Orphaned":
2354
+ case "NoParent": {
2355
+ this._parent = HasParent(newParentNode, newParentKey);
2356
+ return;
2269
2357
  }
2270
- );
2358
+ default:
2359
+ return assertNever(this.parent, "Unknown state");
2360
+ }
2271
2361
  }
2272
- async function addReaction({
2273
- threadId,
2274
- commentId,
2275
- emoji
2276
- }) {
2277
- const reaction = await fetchJson(
2278
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2279
- commentId
2280
- )}/reactions`,
2281
- {
2282
- method: "POST",
2283
- headers: {
2284
- "Content-Type": "application/json"
2285
- },
2286
- body: JSON.stringify({ emoji })
2287
- }
2288
- );
2289
- return convertToCommentUserReaction(reaction);
2362
+ /** @internal */
2363
+ _attach(id, pool) {
2364
+ if (this.__id || this.__pool) {
2365
+ throw new Error("Cannot attach node: already attached");
2366
+ }
2367
+ pool.addNode(id, crdtAsLiveNode(this));
2368
+ this.__id = id;
2369
+ this.__pool = pool;
2290
2370
  }
2291
- async function removeReaction({
2292
- threadId,
2293
- commentId,
2294
- emoji
2295
- }) {
2296
- await fetchJson(
2297
- `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
2298
- commentId
2299
- )}/reactions/${encodeURIComponent(emoji)}`,
2300
- {
2301
- method: "DELETE"
2371
+ /** @internal */
2372
+ _detach() {
2373
+ if (this.__pool && this.__id) {
2374
+ this.__pool.deleteNode(this.__id);
2375
+ }
2376
+ switch (this.parent.type) {
2377
+ case "HasParent": {
2378
+ this._parent = Orphaned(this.parent.key, this.parent.pos);
2379
+ break;
2302
2380
  }
2303
- );
2304
- }
2305
- return {
2306
- getThreads,
2307
- createThread,
2308
- editThreadMetadata,
2309
- createComment,
2310
- editComment,
2311
- deleteComment,
2312
- addReaction,
2313
- removeReaction
2314
- };
2315
- }
2316
-
2317
- // src/lib/position.ts
2318
- var MIN_CODE = 32;
2319
- var MAX_CODE = 126;
2320
- var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
2321
- var ZERO = nthDigit(0);
2322
- var ONE = nthDigit(1);
2323
- var ZERO_NINE = ZERO + nthDigit(-1);
2324
- function nthDigit(n) {
2325
- const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
2326
- if (code < MIN_CODE || code > MAX_CODE) {
2327
- throw new Error(`Invalid n value: ${n}`);
2328
- }
2329
- return String.fromCharCode(code);
2330
- }
2331
- function makePosition(x, y) {
2332
- if (x !== void 0 && y !== void 0) {
2333
- return between(x, y);
2334
- } else if (x !== void 0) {
2335
- return after(x);
2336
- } else if (y !== void 0) {
2337
- return before(y);
2338
- } else {
2339
- return ONE;
2340
- }
2341
- }
2342
- function before(pos) {
2343
- const lastIndex = pos.length - 1;
2344
- for (let i = 0; i <= lastIndex; i++) {
2345
- const code = pos.charCodeAt(i);
2346
- if (code <= MIN_CODE) {
2347
- continue;
2381
+ case "NoParent": {
2382
+ this._parent = NoParent;
2383
+ break;
2384
+ }
2385
+ case "Orphaned": {
2386
+ break;
2387
+ }
2388
+ default:
2389
+ assertNever(this.parent, "Unknown state");
2348
2390
  }
2349
- if (i === lastIndex) {
2350
- if (code === MIN_CODE + 1) {
2351
- return pos.substring(0, i) + ZERO_NINE;
2352
- } else {
2353
- return pos.substring(0, i) + String.fromCharCode(code - 1);
2391
+ this.__pool = void 0;
2392
+ }
2393
+ /**
2394
+ * @internal
2395
+ *
2396
+ * Clear the Immutable cache, so that the next call to `.toImmutable()` will
2397
+ * recompute the equivalent Immutable value again. Call this after every
2398
+ * mutation to the Live node.
2399
+ */
2400
+ invalidate() {
2401
+ if (this._cachedImmutable !== void 0 || this._cachedTreeNode !== void 0) {
2402
+ this._cachedImmutable = void 0;
2403
+ this._cachedTreeNode = void 0;
2404
+ if (this.parent.type === "HasParent") {
2405
+ this.parent.node.invalidate();
2354
2406
  }
2355
- } else {
2356
- return pos.substring(0, i + 1);
2357
2407
  }
2358
2408
  }
2359
- return ONE;
2360
- }
2361
- function after(pos) {
2362
- for (let i = 0; i <= pos.length - 1; i++) {
2363
- const code = pos.charCodeAt(i);
2364
- if (code >= MAX_CODE) {
2365
- continue;
2409
+ /**
2410
+ * @internal
2411
+ *
2412
+ * Return an snapshot of this Live tree for use in DevTools.
2413
+ */
2414
+ toTreeNode(key) {
2415
+ if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
2416
+ this._cachedTreeNodeKey = key;
2417
+ this._cachedTreeNode = this._toTreeNode(key);
2366
2418
  }
2367
- return pos.substring(0, i) + String.fromCharCode(code + 1);
2419
+ return this._cachedTreeNode;
2368
2420
  }
2369
- return pos + ONE;
2370
- }
2371
- function between(lo, hi) {
2372
- if (lo < hi) {
2373
- return _between(lo, hi);
2374
- } else if (lo > hi) {
2375
- return _between(hi, lo);
2376
- } else {
2377
- throw new Error("Cannot compute value between two equal positions");
2421
+ /**
2422
+ * Return an immutable snapshot of this Live node and its children.
2423
+ */
2424
+ toImmutable() {
2425
+ if (this._cachedImmutable === void 0) {
2426
+ this._cachedImmutable = this._toImmutable();
2427
+ }
2428
+ return this._cachedImmutable;
2378
2429
  }
2430
+ };
2431
+
2432
+ // src/protocol/SerializedCrdt.ts
2433
+ var CrdtType = /* @__PURE__ */ ((CrdtType2) => {
2434
+ CrdtType2[CrdtType2["OBJECT"] = 0] = "OBJECT";
2435
+ CrdtType2[CrdtType2["LIST"] = 1] = "LIST";
2436
+ CrdtType2[CrdtType2["MAP"] = 2] = "MAP";
2437
+ CrdtType2[CrdtType2["REGISTER"] = 3] = "REGISTER";
2438
+ return CrdtType2;
2439
+ })(CrdtType || {});
2440
+ function isRootCrdt(crdt) {
2441
+ return crdt.type === 0 /* OBJECT */ && !isChildCrdt(crdt);
2379
2442
  }
2380
- function _between(lo, hi) {
2381
- let index = 0;
2382
- const loLen = lo.length;
2383
- const hiLen = hi.length;
2384
- while (true) {
2385
- const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
2386
- const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
2387
- if (loCode === hiCode) {
2388
- index++;
2389
- continue;
2390
- }
2391
- if (hiCode - loCode === 1) {
2392
- const size = index + 1;
2393
- let prefix = lo.substring(0, size);
2394
- if (prefix.length < size) {
2395
- prefix += ZERO.repeat(size - prefix.length);
2396
- }
2397
- const suffix = lo.substring(size);
2398
- const nines = "";
2399
- return prefix + _between(suffix, nines);
2400
- } else {
2401
- return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
2402
- }
2403
- }
2404
- }
2405
- function takeN(pos, n) {
2406
- return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
2407
- }
2408
- var MIN_NON_ZERO_CODE = MIN_CODE + 1;
2409
- function isPos(str) {
2410
- if (str === "") {
2411
- return false;
2412
- }
2413
- const lastIdx = str.length - 1;
2414
- const last = str.charCodeAt(lastIdx);
2415
- if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
2416
- return false;
2417
- }
2418
- for (let i = 0; i < lastIdx; i++) {
2419
- const code = str.charCodeAt(i);
2420
- if (code < MIN_CODE || code > MAX_CODE) {
2421
- return false;
2422
- }
2423
- }
2424
- return true;
2425
- }
2426
- function convertToPos(str) {
2427
- const codes = [];
2428
- for (let i = 0; i < str.length; i++) {
2429
- const code = str.charCodeAt(i);
2430
- codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
2431
- }
2432
- while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
2433
- codes.length--;
2434
- }
2435
- return codes.length > 0 ? String.fromCharCode(...codes) : (
2436
- // Edge case: the str was a 0-only string, which is invalid. Default back to .1
2437
- ONE
2438
- );
2439
- }
2440
- function asPos(str) {
2441
- return isPos(str) ? str : convertToPos(str);
2443
+ function isChildCrdt(crdt) {
2444
+ return crdt.parentId !== void 0 && crdt.parentKey !== void 0;
2442
2445
  }
2443
2446
 
2444
- // src/protocol/Op.ts
2445
- var OpCode = /* @__PURE__ */ ((OpCode2) => {
2446
- OpCode2[OpCode2["INIT"] = 0] = "INIT";
2447
- OpCode2[OpCode2["SET_PARENT_KEY"] = 1] = "SET_PARENT_KEY";
2448
- OpCode2[OpCode2["CREATE_LIST"] = 2] = "CREATE_LIST";
2449
- OpCode2[OpCode2["UPDATE_OBJECT"] = 3] = "UPDATE_OBJECT";
2450
- OpCode2[OpCode2["CREATE_OBJECT"] = 4] = "CREATE_OBJECT";
2451
- OpCode2[OpCode2["DELETE_CRDT"] = 5] = "DELETE_CRDT";
2452
- OpCode2[OpCode2["DELETE_OBJECT_KEY"] = 6] = "DELETE_OBJECT_KEY";
2453
- OpCode2[OpCode2["CREATE_MAP"] = 7] = "CREATE_MAP";
2454
- OpCode2[OpCode2["CREATE_REGISTER"] = 8] = "CREATE_REGISTER";
2455
- return OpCode2;
2456
- })(OpCode || {});
2457
- function ackOp(opId) {
2458
- return {
2459
- type: 5 /* DELETE_CRDT */,
2460
- id: "ACK",
2461
- // (H)ACK
2462
- opId
2463
- };
2464
- }
2465
- function isAckOp(op) {
2466
- return op.type === 5 /* DELETE_CRDT */ && op.id === "ACK";
2447
+ // src/lib/nanoid.ts
2448
+ function nanoid(length = 7) {
2449
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,./;[]~!@#$%&*()_+=-";
2450
+ const len = alphabet.length;
2451
+ return Array.from(
2452
+ { length },
2453
+ () => alphabet.charAt(Math.floor(Math.random() * len))
2454
+ ).join("");
2467
2455
  }
2468
2456
 
2469
- // src/crdts/AbstractCrdt.ts
2470
- function crdtAsLiveNode(value) {
2471
- return value;
2472
- }
2473
- function HasParent(node, key, pos = asPos(key)) {
2474
- return Object.freeze({ type: "HasParent", node, key, pos });
2475
- }
2476
- var NoParent = Object.freeze({ type: "NoParent" });
2477
- function Orphaned(oldKey, oldPos = asPos(oldKey)) {
2478
- return Object.freeze({ type: "Orphaned", oldKey, oldPos });
2479
- }
2480
- var AbstractCrdt = class {
2481
- constructor() {
2482
- /** @internal */
2483
- this._parent = NoParent;
2457
+ // src/crdts/LiveRegister.ts
2458
+ var LiveRegister = class _LiveRegister extends AbstractCrdt {
2459
+ constructor(data) {
2460
+ super();
2461
+ this._data = data;
2462
+ }
2463
+ get data() {
2464
+ return this._data;
2484
2465
  }
2485
2466
  /** @internal */
2486
- _getParentKeyOrThrow() {
2487
- switch (this.parent.type) {
2488
- case "HasParent":
2489
- return this.parent.key;
2490
- case "NoParent":
2491
- throw new Error("Parent key is missing");
2492
- case "Orphaned":
2493
- return this.parent.oldKey;
2494
- default:
2495
- return assertNever(this.parent, "Unknown state");
2496
- }
2467
+ static _deserialize([id, item], _parentToChildren, pool) {
2468
+ const register = new _LiveRegister(item.data);
2469
+ register._attach(id, pool);
2470
+ return register;
2497
2471
  }
2498
2472
  /** @internal */
2499
- get _parentPos() {
2500
- switch (this.parent.type) {
2501
- case "HasParent":
2502
- return this.parent.pos;
2503
- case "NoParent":
2504
- throw new Error("Parent key is missing");
2505
- case "Orphaned":
2506
- return this.parent.oldPos;
2507
- default:
2508
- return assertNever(this.parent, "Unknown state");
2473
+ _toOps(parentId, parentKey, pool) {
2474
+ if (this._id === void 0) {
2475
+ throw new Error(
2476
+ "Cannot serialize register if parentId or parentKey is undefined"
2477
+ );
2509
2478
  }
2479
+ return [
2480
+ {
2481
+ type: 8 /* CREATE_REGISTER */,
2482
+ opId: pool?.generateOpId(),
2483
+ id: this._id,
2484
+ parentId,
2485
+ parentKey,
2486
+ data: this.data
2487
+ }
2488
+ ];
2510
2489
  }
2511
2490
  /** @internal */
2512
- get _pool() {
2513
- return this.__pool;
2514
- }
2515
- get roomId() {
2516
- return this.__pool ? this.__pool.roomId : null;
2491
+ _serialize() {
2492
+ if (this.parent.type !== "HasParent") {
2493
+ throw new Error("Cannot serialize LiveRegister if parent is missing");
2494
+ }
2495
+ return {
2496
+ type: 3 /* REGISTER */,
2497
+ parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
2498
+ parentKey: this.parent.key,
2499
+ data: this.data
2500
+ };
2517
2501
  }
2518
2502
  /** @internal */
2519
- get _id() {
2520
- return this.__id;
2503
+ _attachChild(_op) {
2504
+ throw new Error("Method not implemented.");
2521
2505
  }
2522
2506
  /** @internal */
2523
- get parent() {
2524
- return this._parent;
2507
+ _detachChild(_crdt) {
2508
+ throw new Error("Method not implemented.");
2525
2509
  }
2526
2510
  /** @internal */
2527
- get _parentKey() {
2528
- switch (this.parent.type) {
2529
- case "HasParent":
2530
- return this.parent.key;
2531
- case "NoParent":
2532
- return null;
2533
- case "Orphaned":
2534
- return this.parent.oldKey;
2535
- default:
2536
- return assertNever(this.parent, "Unknown state");
2537
- }
2511
+ _apply(op, isLocal) {
2512
+ return super._apply(op, isLocal);
2538
2513
  }
2539
2514
  /** @internal */
2540
- _apply(op, _isLocal) {
2541
- switch (op.type) {
2542
- case 5 /* DELETE_CRDT */: {
2543
- if (this.parent.type === "HasParent") {
2544
- return this.parent.node._detachChild(crdtAsLiveNode(this));
2545
- }
2546
- return { modified: false };
2547
- }
2548
- }
2549
- return { modified: false };
2515
+ _toTreeNode(key) {
2516
+ return {
2517
+ type: "Json",
2518
+ id: this._id ?? nanoid(),
2519
+ key,
2520
+ payload: this._data
2521
+ };
2550
2522
  }
2551
2523
  /** @internal */
2552
- _setParentLink(newParentNode, newParentKey) {
2553
- switch (this.parent.type) {
2554
- case "HasParent":
2555
- if (this.parent.node !== newParentNode) {
2556
- throw new Error("Cannot set parent: node already has a parent");
2557
- } else {
2558
- this._parent = HasParent(newParentNode, newParentKey);
2559
- return;
2560
- }
2561
- case "Orphaned":
2562
- case "NoParent": {
2563
- this._parent = HasParent(newParentNode, newParentKey);
2564
- return;
2565
- }
2566
- default:
2567
- return assertNever(this.parent, "Unknown state");
2568
- }
2569
- }
2570
- /** @internal */
2571
- _attach(id, pool) {
2572
- if (this.__id || this.__pool) {
2573
- throw new Error("Cannot attach node: already attached");
2574
- }
2575
- pool.addNode(id, crdtAsLiveNode(this));
2576
- this.__id = id;
2577
- this.__pool = pool;
2578
- }
2579
- /** @internal */
2580
- _detach() {
2581
- if (this.__pool && this.__id) {
2582
- this.__pool.deleteNode(this.__id);
2583
- }
2584
- switch (this.parent.type) {
2585
- case "HasParent": {
2586
- this._parent = Orphaned(this.parent.key, this.parent.pos);
2587
- break;
2588
- }
2589
- case "NoParent": {
2590
- this._parent = NoParent;
2591
- break;
2592
- }
2593
- case "Orphaned": {
2594
- break;
2595
- }
2596
- default:
2597
- assertNever(this.parent, "Unknown state");
2598
- }
2599
- this.__pool = void 0;
2600
- }
2601
- /**
2602
- * @internal
2603
- *
2604
- * Clear the Immutable cache, so that the next call to `.toImmutable()` will
2605
- * recompute the equivalent Immutable value again. Call this after every
2606
- * mutation to the Live node.
2607
- */
2608
- invalidate() {
2609
- if (this._cachedImmutable !== void 0 || this._cachedTreeNode !== void 0) {
2610
- this._cachedImmutable = void 0;
2611
- this._cachedTreeNode = void 0;
2612
- if (this.parent.type === "HasParent") {
2613
- this.parent.node.invalidate();
2614
- }
2615
- }
2616
- }
2617
- /**
2618
- * @internal
2619
- *
2620
- * Return an snapshot of this Live tree for use in DevTools.
2621
- */
2622
- toTreeNode(key) {
2623
- if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
2624
- this._cachedTreeNodeKey = key;
2625
- this._cachedTreeNode = this._toTreeNode(key);
2626
- }
2627
- return this._cachedTreeNode;
2628
- }
2629
- /**
2630
- * Return an immutable snapshot of this Live node and its children.
2631
- */
2632
- toImmutable() {
2633
- if (this._cachedImmutable === void 0) {
2634
- this._cachedImmutable = this._toImmutable();
2635
- }
2636
- return this._cachedImmutable;
2637
- }
2638
- };
2639
-
2640
- // src/protocol/SerializedCrdt.ts
2641
- var CrdtType = /* @__PURE__ */ ((CrdtType2) => {
2642
- CrdtType2[CrdtType2["OBJECT"] = 0] = "OBJECT";
2643
- CrdtType2[CrdtType2["LIST"] = 1] = "LIST";
2644
- CrdtType2[CrdtType2["MAP"] = 2] = "MAP";
2645
- CrdtType2[CrdtType2["REGISTER"] = 3] = "REGISTER";
2646
- return CrdtType2;
2647
- })(CrdtType || {});
2648
- function isRootCrdt(crdt) {
2649
- return crdt.type === 0 /* OBJECT */ && !isChildCrdt(crdt);
2650
- }
2651
- function isChildCrdt(crdt) {
2652
- return crdt.parentId !== void 0 && crdt.parentKey !== void 0;
2653
- }
2654
-
2655
- // src/lib/nanoid.ts
2656
- function nanoid(length = 7) {
2657
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,./;[]~!@#$%&*()_+=-";
2658
- const len = alphabet.length;
2659
- return Array.from(
2660
- { length },
2661
- () => alphabet.charAt(Math.floor(Math.random() * len))
2662
- ).join("");
2663
- }
2664
-
2665
- // src/crdts/LiveRegister.ts
2666
- var LiveRegister = class _LiveRegister extends AbstractCrdt {
2667
- constructor(data) {
2668
- super();
2669
- this._data = data;
2670
- }
2671
- get data() {
2672
- return this._data;
2673
- }
2674
- /** @internal */
2675
- static _deserialize([id, item], _parentToChildren, pool) {
2676
- const register = new _LiveRegister(item.data);
2677
- register._attach(id, pool);
2678
- return register;
2679
- }
2680
- /** @internal */
2681
- _toOps(parentId, parentKey, pool) {
2682
- if (this._id === void 0) {
2683
- throw new Error(
2684
- "Cannot serialize register if parentId or parentKey is undefined"
2685
- );
2686
- }
2687
- return [
2688
- {
2689
- type: 8 /* CREATE_REGISTER */,
2690
- opId: pool?.generateOpId(),
2691
- id: this._id,
2692
- parentId,
2693
- parentKey,
2694
- data: this.data
2695
- }
2696
- ];
2697
- }
2698
- /** @internal */
2699
- _serialize() {
2700
- if (this.parent.type !== "HasParent") {
2701
- throw new Error("Cannot serialize LiveRegister if parent is missing");
2702
- }
2703
- return {
2704
- type: 3 /* REGISTER */,
2705
- parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
2706
- parentKey: this.parent.key,
2707
- data: this.data
2708
- };
2709
- }
2710
- /** @internal */
2711
- _attachChild(_op) {
2712
- throw new Error("Method not implemented.");
2713
- }
2714
- /** @internal */
2715
- _detachChild(_crdt) {
2716
- throw new Error("Method not implemented.");
2717
- }
2718
- /** @internal */
2719
- _apply(op, isLocal) {
2720
- return super._apply(op, isLocal);
2721
- }
2722
- /** @internal */
2723
- _toTreeNode(key) {
2724
- return {
2725
- type: "Json",
2726
- id: this._id ?? nanoid(),
2727
- key,
2728
- payload: this._data
2729
- };
2730
- }
2731
- /** @internal */
2732
- _toImmutable() {
2733
- return this._data;
2734
- }
2735
- clone() {
2736
- return deepClone(this.data);
2737
- }
2738
- };
2739
-
2740
- // src/crdts/LiveList.ts
2741
- function compareNodePosition(itemA, itemB) {
2742
- const posA = itemA._parentPos;
2743
- const posB = itemB._parentPos;
2744
- return posA === posB ? 0 : posA < posB ? -1 : 1;
2745
- }
2746
- var LiveList = class _LiveList extends AbstractCrdt {
2747
- constructor(items = []) {
2748
- super();
2749
- this._items = [];
2750
- this._implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
2751
- this._unacknowledgedSets = /* @__PURE__ */ new Map();
2752
- let position = void 0;
2753
- for (const item of items) {
2754
- const newPosition = makePosition(position);
2755
- const node = lsonToLiveNode(item);
2756
- node._setParentLink(this, newPosition);
2757
- this._items.push(node);
2758
- position = newPosition;
2524
+ _toImmutable() {
2525
+ return this._data;
2526
+ }
2527
+ clone() {
2528
+ return deepClone(this.data);
2529
+ }
2530
+ };
2531
+
2532
+ // src/crdts/LiveList.ts
2533
+ function compareNodePosition(itemA, itemB) {
2534
+ const posA = itemA._parentPos;
2535
+ const posB = itemB._parentPos;
2536
+ return posA === posB ? 0 : posA < posB ? -1 : 1;
2537
+ }
2538
+ var LiveList = class _LiveList extends AbstractCrdt {
2539
+ constructor(items = []) {
2540
+ super();
2541
+ this._items = [];
2542
+ this._implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
2543
+ this._unacknowledgedSets = /* @__PURE__ */ new Map();
2544
+ let position = void 0;
2545
+ for (const item of items) {
2546
+ const newPosition = makePosition(position);
2547
+ const node = lsonToLiveNode(item);
2548
+ node._setParentLink(this, newPosition);
2549
+ this._items.push(node);
2550
+ position = newPosition;
2759
2551
  }
2760
2552
  }
2761
2553
  /** @internal */
@@ -5158,54 +4950,272 @@ function installBackgroundTabSpy() {
5158
4950
  };
5159
4951
  return [inBackgroundSince, unsub];
5160
4952
  }
5161
- function createRoom(options, config) {
5162
- const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
5163
- const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
5164
- const [inBackgroundSince, uninstallBgTabSpy] = installBackgroundTabSpy();
5165
- const delegates = {
5166
- ...config.delegates,
5167
- // A connection is allowed to go into "zombie state" only if all of the
5168
- // following conditions apply:
5169
- //
5170
- // - The `backgroundKeepAliveTimeout` client option is configured
5171
- // - The browser window has been in the background for at least
5172
- // `backgroundKeepAliveTimeout` milliseconds
5173
- // - There are no pending changes
5174
- //
5175
- canZombie() {
5176
- return config.backgroundKeepAliveTimeout !== void 0 && inBackgroundSince.current !== null && Date.now() > inBackgroundSince.current + config.backgroundKeepAliveTimeout && getStorageStatus() !== "synchronizing";
5177
- }
5178
- };
5179
- const managedSocket = new ManagedSocket(
5180
- delegates,
5181
- config.enableDebugLogging
5182
- );
5183
- const context = {
5184
- buffer: {
5185
- flushTimerID: void 0,
5186
- lastFlushedAt: 0,
5187
- presenceUpdates: (
5188
- // Queue up the initial presence message as a Full Presence™ update
5189
- {
5190
- type: "full",
5191
- data: initialPresence
4953
+ var CommentsApiError = class extends Error {
4954
+ constructor(message, status, details) {
4955
+ super(message);
4956
+ this.message = message;
4957
+ this.status = status;
4958
+ this.details = details;
4959
+ }
4960
+ };
4961
+ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
4962
+ async function fetchCommentsApi(endpoint, options) {
4963
+ const authValue = await getAuthValue();
4964
+ return fetchClientApi(roomId, endpoint, authValue, options);
4965
+ }
4966
+ async function fetchJson(endpoint, options) {
4967
+ const response = await fetchCommentsApi(endpoint, options);
4968
+ if (!response.ok) {
4969
+ if (response.status >= 400 && response.status < 600) {
4970
+ let error3;
4971
+ try {
4972
+ const errorBody = await response.json();
4973
+ error3 = new CommentsApiError(
4974
+ errorBody.message,
4975
+ response.status,
4976
+ errorBody
4977
+ );
4978
+ } catch {
4979
+ error3 = new CommentsApiError(response.statusText, response.status);
5192
4980
  }
5193
- ),
5194
- messages: [],
5195
- storageOperations: []
5196
- },
5197
- staticSessionInfo: new ValueRef(null),
5198
- dynamicSessionInfo: new ValueRef(null),
5199
- myPresence: new PatchableRef(initialPresence),
5200
- others: new OthersRef(),
5201
- initialStorage,
5202
- idFactory: null,
5203
- // Storage
5204
- clock: 0,
5205
- opClock: 0,
5206
- nodes: /* @__PURE__ */ new Map(),
5207
- root: void 0,
5208
- undoStack: [],
4981
+ throw error3;
4982
+ }
4983
+ }
4984
+ let body;
4985
+ try {
4986
+ body = await response.json();
4987
+ } catch {
4988
+ body = {};
4989
+ }
4990
+ return body;
4991
+ }
4992
+ async function getThreads(options) {
4993
+ const response = await fetchCommentsApi("/threads/search", {
4994
+ body: JSON.stringify({
4995
+ ...options?.query?.metadata && { metadata: options.query.metadata }
4996
+ }),
4997
+ headers: {
4998
+ "Content-Type": "application/json"
4999
+ },
5000
+ method: "POST"
5001
+ });
5002
+ if (response.ok) {
5003
+ const json = await response.json();
5004
+ return {
5005
+ threads: json.data.map((thread) => convertToThreadData(thread)),
5006
+ inboxNotifications: json.inboxNotifications.map(
5007
+ (notification) => convertToInboxNotificationData(notification)
5008
+ )
5009
+ };
5010
+ } else if (response.status === 404) {
5011
+ return { threads: [], inboxNotifications: [] };
5012
+ } else {
5013
+ throw new Error("There was an error while getting threads.");
5014
+ }
5015
+ }
5016
+ async function getThread({ threadId }) {
5017
+ const response = await fetchCommentsApi(
5018
+ `/thread-with-notification/${threadId}`
5019
+ );
5020
+ if (response.ok) {
5021
+ const json = await response.json();
5022
+ return {
5023
+ thread: convertToThreadData(json.thread),
5024
+ inboxNotification: json.inboxNotification ? convertToInboxNotificationData(json.inboxNotification) : void 0
5025
+ };
5026
+ } else if (response.status === 404) {
5027
+ return;
5028
+ } else {
5029
+ throw new Error(`There was an error while getting thread ${threadId}.`);
5030
+ }
5031
+ }
5032
+ async function createThread({
5033
+ metadata,
5034
+ body,
5035
+ commentId,
5036
+ threadId
5037
+ }) {
5038
+ const thread = await fetchJson(
5039
+ "/threads",
5040
+ {
5041
+ method: "POST",
5042
+ headers: {
5043
+ "Content-Type": "application/json"
5044
+ },
5045
+ body: JSON.stringify({
5046
+ id: threadId,
5047
+ comment: {
5048
+ id: commentId,
5049
+ body
5050
+ },
5051
+ metadata
5052
+ })
5053
+ }
5054
+ );
5055
+ return convertToThreadData(thread);
5056
+ }
5057
+ async function editThreadMetadata({
5058
+ metadata,
5059
+ threadId
5060
+ }) {
5061
+ return await fetchJson(
5062
+ `/threads/${encodeURIComponent(threadId)}/metadata`,
5063
+ {
5064
+ method: "POST",
5065
+ headers: {
5066
+ "Content-Type": "application/json"
5067
+ },
5068
+ body: JSON.stringify(metadata)
5069
+ }
5070
+ );
5071
+ }
5072
+ async function createComment({
5073
+ threadId,
5074
+ commentId,
5075
+ body
5076
+ }) {
5077
+ const comment = await fetchJson(
5078
+ `/threads/${encodeURIComponent(threadId)}/comments`,
5079
+ {
5080
+ method: "POST",
5081
+ headers: {
5082
+ "Content-Type": "application/json"
5083
+ },
5084
+ body: JSON.stringify({
5085
+ id: commentId,
5086
+ body
5087
+ })
5088
+ }
5089
+ );
5090
+ return convertToCommentData(comment);
5091
+ }
5092
+ async function editComment({
5093
+ threadId,
5094
+ commentId,
5095
+ body
5096
+ }) {
5097
+ const comment = await fetchJson(
5098
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5099
+ commentId
5100
+ )}`,
5101
+ {
5102
+ method: "POST",
5103
+ headers: {
5104
+ "Content-Type": "application/json"
5105
+ },
5106
+ body: JSON.stringify({
5107
+ body
5108
+ })
5109
+ }
5110
+ );
5111
+ return convertToCommentData(comment);
5112
+ }
5113
+ async function deleteComment({
5114
+ threadId,
5115
+ commentId
5116
+ }) {
5117
+ await fetchJson(
5118
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5119
+ commentId
5120
+ )}`,
5121
+ {
5122
+ method: "DELETE"
5123
+ }
5124
+ );
5125
+ }
5126
+ async function addReaction({
5127
+ threadId,
5128
+ commentId,
5129
+ emoji
5130
+ }) {
5131
+ const reaction = await fetchJson(
5132
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5133
+ commentId
5134
+ )}/reactions`,
5135
+ {
5136
+ method: "POST",
5137
+ headers: {
5138
+ "Content-Type": "application/json"
5139
+ },
5140
+ body: JSON.stringify({ emoji })
5141
+ }
5142
+ );
5143
+ return convertToCommentUserReaction(reaction);
5144
+ }
5145
+ async function removeReaction({
5146
+ threadId,
5147
+ commentId,
5148
+ emoji
5149
+ }) {
5150
+ await fetchJson(
5151
+ `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
5152
+ commentId
5153
+ )}/reactions/${encodeURIComponent(emoji)}`,
5154
+ {
5155
+ method: "DELETE"
5156
+ }
5157
+ );
5158
+ }
5159
+ return {
5160
+ getThreads,
5161
+ getThread,
5162
+ createThread,
5163
+ editThreadMetadata,
5164
+ createComment,
5165
+ editComment,
5166
+ deleteComment,
5167
+ addReaction,
5168
+ removeReaction
5169
+ };
5170
+ }
5171
+ function createRoom(options, config) {
5172
+ const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
5173
+ const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
5174
+ const [inBackgroundSince, uninstallBgTabSpy] = installBackgroundTabSpy();
5175
+ const delegates = {
5176
+ ...config.delegates,
5177
+ // A connection is allowed to go into "zombie state" only if all of the
5178
+ // following conditions apply:
5179
+ //
5180
+ // - The `backgroundKeepAliveTimeout` client option is configured
5181
+ // - The browser window has been in the background for at least
5182
+ // `backgroundKeepAliveTimeout` milliseconds
5183
+ // - There are no pending changes
5184
+ //
5185
+ canZombie() {
5186
+ return config.backgroundKeepAliveTimeout !== void 0 && inBackgroundSince.current !== null && Date.now() > inBackgroundSince.current + config.backgroundKeepAliveTimeout && getStorageStatus() !== "synchronizing";
5187
+ }
5188
+ };
5189
+ const managedSocket = new ManagedSocket(
5190
+ delegates,
5191
+ config.enableDebugLogging
5192
+ );
5193
+ const context = {
5194
+ buffer: {
5195
+ flushTimerID: void 0,
5196
+ lastFlushedAt: 0,
5197
+ presenceUpdates: (
5198
+ // Queue up the initial presence message as a Full Presence™ update
5199
+ {
5200
+ type: "full",
5201
+ data: initialPresence
5202
+ }
5203
+ ),
5204
+ messages: [],
5205
+ storageOperations: []
5206
+ },
5207
+ staticSessionInfo: new ValueRef(null),
5208
+ dynamicSessionInfo: new ValueRef(null),
5209
+ myPresence: new PatchableRef(initialPresence),
5210
+ others: new OthersRef(),
5211
+ initialStorage,
5212
+ idFactory: null,
5213
+ // Storage
5214
+ clock: 0,
5215
+ opClock: 0,
5216
+ nodes: /* @__PURE__ */ new Map(),
5217
+ root: void 0,
5218
+ undoStack: [],
5209
5219
  redoStack: [],
5210
5220
  pausedHistory: null,
5211
5221
  activeBatch: null,
@@ -5219,7 +5229,7 @@ function createRoom(options, config) {
5219
5229
  function onStatusDidChange(newStatus) {
5220
5230
  const authValue = managedSocket.authValue;
5221
5231
  if (authValue !== null) {
5222
- const tokenKey = authValue.type === "secret" ? authValue.token.raw : authValue.publicApiKey;
5232
+ const tokenKey = getAuthBearerHeaderFromAuthValue(authValue);
5223
5233
  if (tokenKey !== lastTokenKey) {
5224
5234
  lastTokenKey = tokenKey;
5225
5235
  if (authValue.type === "secret") {
@@ -5373,18 +5383,26 @@ function createRoom(options, config) {
5373
5383
  ydoc: makeEventSource(),
5374
5384
  comments: makeEventSource()
5375
5385
  };
5376
- async function streamFetch(authTokenOrPublicApiKey, roomId) {
5386
+ async function fetchClientApi(roomId, endpoint, authValue, options2) {
5377
5387
  const url = new URL(
5378
- `/v2/c/rooms/${encodeURIComponent(roomId)}/storage`,
5388
+ `/v2/c/rooms/${encodeURIComponent(roomId)}${endpoint}`,
5379
5389
  config.baseUrl
5380
- ).toString();
5390
+ );
5381
5391
  const fetcher = config.polyfills?.fetch || /* istanbul ignore next */
5382
5392
  fetch;
5383
- return fetcher(url.toString(), {
5393
+ return await fetcher(url.toString(), {
5394
+ ...options2,
5395
+ headers: {
5396
+ ...options2?.headers,
5397
+ Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
5398
+ }
5399
+ });
5400
+ }
5401
+ async function streamFetch(authValue, roomId) {
5402
+ return fetchClientApi(roomId, "/storage", authValue, {
5384
5403
  method: "GET",
5385
5404
  headers: {
5386
- "Content-Type": "application/json",
5387
- Authorization: `Bearer ${authTokenOrPublicApiKey}`
5405
+ "Content-Type": "application/json"
5388
5406
  }
5389
5407
  });
5390
5408
  }
@@ -5392,18 +5410,10 @@ function createRoom(options, config) {
5392
5410
  if (!managedSocket.authValue) {
5393
5411
  throw new Error("Not authorized");
5394
5412
  }
5395
- const authTokenOrPublicApiKey = managedSocket.authValue.type === "public" ? managedSocket.authValue.publicApiKey : managedSocket.authValue.token.raw;
5396
- const url = new URL(
5397
- `/v2/c/rooms/${encodeURIComponent(config.roomId)}${endpoint}`,
5398
- config.baseUrl
5399
- ).toString();
5400
- const fetcher = config.polyfills?.fetch || /* istanbul ignore next */
5401
- fetch;
5402
- return fetcher(url, {
5413
+ return fetchClientApi(config.roomId, endpoint, managedSocket.authValue, {
5403
5414
  method: "POST",
5404
5415
  headers: {
5405
- "Content-Type": "application/json",
5406
- Authorization: `Bearer ${authTokenOrPublicApiKey}`
5416
+ "Content-Type": "application/json"
5407
5417
  },
5408
5418
  body: JSON.stringify(body)
5409
5419
  });
@@ -6017,10 +6027,7 @@ ${Array.from(traces).join("\n\n")}`
6017
6027
  if (!managedSocket.authValue) {
6018
6028
  return;
6019
6029
  }
6020
- const result = await streamFetch(
6021
- managedSocket.authValue.type === "public" ? managedSocket.authValue.publicApiKey : managedSocket.authValue.token.raw,
6022
- config.roomId
6023
- );
6030
+ const result = await streamFetch(managedSocket.authValue, config.roomId);
6024
6031
  const items = await result.json();
6025
6032
  processInitialStorage({ type: 200 /* INITIAL_STORAGE_STATE */, items });
6026
6033
  }
@@ -6206,27 +6213,71 @@ ${Array.from(traces).join("\n\n")}`
6206
6213
  ydoc: eventHub.ydoc.observable,
6207
6214
  comments: eventHub.comments.observable
6208
6215
  };
6209
- const commentsApi = createCommentsApi(config.roomId, delegates.authenticate, {
6210
- baseUrl: config.baseUrl
6211
- });
6212
- return Object.defineProperty(
6213
- {
6214
- /* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
6215
- __internal: {
6216
- get presenceBuffer() {
6217
- return deepClone(context.buffer.presenceUpdates?.data ?? null);
6218
- },
6219
- // prettier-ignore
6220
- get undoStack() {
6221
- return deepClone(context.undoStack);
6222
- },
6223
- // prettier-ignore
6224
- get nodeCount() {
6225
- return context.nodes.size;
6226
- },
6227
- // prettier-ignore
6228
- // Support for the Liveblocks browser extension
6229
- getSelf_forDevTools: () => selfAsTreeNode.current,
6216
+ const commentsApi = createCommentsApi(
6217
+ config.roomId,
6218
+ delegates.authenticate,
6219
+ fetchClientApi
6220
+ );
6221
+ async function fetchJson(endpoint, options2) {
6222
+ const authValue = await delegates.authenticate();
6223
+ const response = await fetchClientApi(
6224
+ config.roomId,
6225
+ endpoint,
6226
+ authValue,
6227
+ options2
6228
+ );
6229
+ if (!response.ok) {
6230
+ if (response.status >= 400 && response.status < 600) {
6231
+ let errorMessage = "";
6232
+ try {
6233
+ const errorBody = await response.json();
6234
+ errorMessage = errorBody.message;
6235
+ } catch (error3) {
6236
+ errorMessage = response.statusText;
6237
+ }
6238
+ throw new Error(
6239
+ `Request failed with status ${response.status}: ${errorMessage}`
6240
+ );
6241
+ }
6242
+ }
6243
+ let body;
6244
+ try {
6245
+ body = await response.json();
6246
+ } catch {
6247
+ body = {};
6248
+ }
6249
+ return body;
6250
+ }
6251
+ function getRoomNotificationSettings() {
6252
+ return fetchJson("/notification-settings");
6253
+ }
6254
+ function updateRoomNotificationSettings(settings) {
6255
+ return fetchJson("/notification-settings", {
6256
+ method: "POST",
6257
+ body: JSON.stringify(settings),
6258
+ headers: {
6259
+ "Content-Type": "application/json"
6260
+ }
6261
+ });
6262
+ }
6263
+ return Object.defineProperty(
6264
+ {
6265
+ /* NOTE: Exposing internals here only to allow testing implementation details in unit tests */
6266
+ [kInternal]: {
6267
+ get presenceBuffer() {
6268
+ return deepClone(context.buffer.presenceUpdates?.data ?? null);
6269
+ },
6270
+ // prettier-ignore
6271
+ get undoStack() {
6272
+ return deepClone(context.undoStack);
6273
+ },
6274
+ // prettier-ignore
6275
+ get nodeCount() {
6276
+ return context.nodes.size;
6277
+ },
6278
+ // prettier-ignore
6279
+ // Support for the Liveblocks browser extension
6280
+ getSelf_forDevTools: () => selfAsTreeNode.current,
6230
6281
  getOthers_forDevTools: () => others_forDevTools.current,
6231
6282
  // prettier-ignore
6232
6283
  simulate: {
@@ -6271,11 +6322,15 @@ ${Array.from(traces).join("\n\n")}`
6271
6322
  // Presence
6272
6323
  getPresence: () => context.myPresence.current,
6273
6324
  getOthers: () => context.others.current,
6274
- ...commentsApi
6325
+ // Comments
6326
+ ...commentsApi,
6327
+ // Notifications
6328
+ getRoomNotificationSettings,
6329
+ updateRoomNotificationSettings
6275
6330
  },
6276
- // Explictly make the __internal field non-enumerable, to avoid aggressive
6331
+ // Explictly make the internal field non-enumerable, to avoid aggressive
6277
6332
  // freezing when used with Immer
6278
- "__internal",
6333
+ kInternal,
6279
6334
  { enumerable: false }
6280
6335
  );
6281
6336
  }
@@ -6375,7 +6430,7 @@ function isRoomEventName(value) {
6375
6430
  }
6376
6431
  function makeAuthDelegateForRoom(roomId, authManager) {
6377
6432
  return async () => {
6378
- return authManager.getAuthValue("room:read", roomId);
6433
+ return authManager.getAuthValue({ requestedScope: "room:read", roomId });
6379
6434
  };
6380
6435
  }
6381
6436
  function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
@@ -6402,6 +6457,327 @@ function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
6402
6457
  };
6403
6458
  }
6404
6459
 
6460
+ // src/store.ts
6461
+ function createClientStore() {
6462
+ const store = createStore({
6463
+ threads: {},
6464
+ queries: {},
6465
+ optimisticUpdates: [],
6466
+ inboxNotifications: {},
6467
+ notificationSettings: {}
6468
+ });
6469
+ function mergeThreads(existingThreads, newThreads) {
6470
+ const updatedThreads = { ...existingThreads };
6471
+ Object.entries(newThreads).forEach(([id, thread]) => {
6472
+ const existingThread = updatedThreads[id];
6473
+ if (existingThread) {
6474
+ const result = compareThreads(existingThread, thread);
6475
+ if (result === 1)
6476
+ return;
6477
+ }
6478
+ updatedThreads[id] = thread;
6479
+ });
6480
+ return updatedThreads;
6481
+ }
6482
+ function mergeNotifications(existingInboxNotifications, newInboxNotifications) {
6483
+ const inboxNotifications = Object.values({
6484
+ ...existingInboxNotifications,
6485
+ ...newInboxNotifications
6486
+ });
6487
+ return Object.fromEntries(
6488
+ inboxNotifications.map((notification) => [notification.id, notification])
6489
+ );
6490
+ }
6491
+ return {
6492
+ ...store,
6493
+ deleteThread(threadId) {
6494
+ store.set((state) => {
6495
+ return {
6496
+ ...state,
6497
+ threads: deleteKeyImmutable(state.threads, threadId),
6498
+ inboxNotifications: Object.fromEntries(
6499
+ Object.entries(state.inboxNotifications).filter(
6500
+ ([_id, notification]) => notification.threadId !== threadId
6501
+ )
6502
+ )
6503
+ };
6504
+ });
6505
+ },
6506
+ updateThreadAndNotification(thread, inboxNotification) {
6507
+ store.set((state) => {
6508
+ const existingThread = state.threads[thread.id];
6509
+ return {
6510
+ ...state,
6511
+ threads: existingThread === void 0 || compareThreads(thread, existingThread) === 1 ? { ...state.threads, [thread.id]: thread } : state.threads,
6512
+ inboxNotifications: inboxNotification === void 0 ? state.inboxNotifications : {
6513
+ ...state.inboxNotifications,
6514
+ [inboxNotification.id]: inboxNotification
6515
+ }
6516
+ };
6517
+ });
6518
+ },
6519
+ updateThreadsAndNotifications(threads, inboxNotifications, queryKey) {
6520
+ store.set((state) => ({
6521
+ ...state,
6522
+ threads: mergeThreads(
6523
+ state.threads,
6524
+ Object.fromEntries(threads.map((thread) => [thread.id, thread]))
6525
+ ),
6526
+ inboxNotifications: mergeNotifications(
6527
+ state.inboxNotifications,
6528
+ Object.fromEntries(
6529
+ inboxNotifications.map((notification) => [
6530
+ notification.id,
6531
+ notification
6532
+ ])
6533
+ )
6534
+ ),
6535
+ queries: queryKey !== void 0 ? {
6536
+ ...state.queries,
6537
+ [queryKey]: {
6538
+ isLoading: false
6539
+ }
6540
+ } : state.queries
6541
+ }));
6542
+ },
6543
+ pushOptimisticUpdate(optimisticUpdate) {
6544
+ store.set((state) => ({
6545
+ ...state,
6546
+ optimisticUpdates: [...state.optimisticUpdates, optimisticUpdate]
6547
+ }));
6548
+ },
6549
+ setQueryState(queryKey, queryState) {
6550
+ store.set((state) => ({
6551
+ ...state,
6552
+ queries: {
6553
+ ...state.queries,
6554
+ [queryKey]: queryState
6555
+ }
6556
+ }));
6557
+ }
6558
+ };
6559
+ }
6560
+ function deleteKeyImmutable(record, key) {
6561
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
6562
+ const { [key]: _toDelete, ...rest } = record;
6563
+ return rest;
6564
+ }
6565
+ return record;
6566
+ }
6567
+ function compareThreads(thread1, thread2) {
6568
+ if (thread1.updatedAt && thread2.updatedAt) {
6569
+ return thread1.updatedAt > thread2.updatedAt ? 1 : thread1.updatedAt < thread2.updatedAt ? -1 : 0;
6570
+ } else if (thread1.updatedAt || thread2.updatedAt) {
6571
+ return thread1.updatedAt ? 1 : -1;
6572
+ }
6573
+ if (thread1.createdAt > thread2.createdAt) {
6574
+ return 1;
6575
+ } else if (thread1.createdAt < thread2.createdAt) {
6576
+ return -1;
6577
+ }
6578
+ return 0;
6579
+ }
6580
+ function applyOptimisticUpdates(state) {
6581
+ const result = {
6582
+ threads: {
6583
+ ...state.threads
6584
+ },
6585
+ inboxNotifications: {
6586
+ ...state.inboxNotifications
6587
+ },
6588
+ notificationSettings: {
6589
+ ...state.notificationSettings
6590
+ }
6591
+ };
6592
+ for (const optimisticUpdate of state.optimisticUpdates) {
6593
+ switch (optimisticUpdate.type) {
6594
+ case "create-thread": {
6595
+ result.threads[optimisticUpdate.thread.id] = optimisticUpdate.thread;
6596
+ break;
6597
+ }
6598
+ case "edit-thread-metadata": {
6599
+ const thread = result.threads[optimisticUpdate.threadId];
6600
+ if (thread === void 0) {
6601
+ break;
6602
+ }
6603
+ result.threads[thread.id] = {
6604
+ ...thread,
6605
+ metadata: {
6606
+ ...thread.metadata,
6607
+ ...optimisticUpdate.metadata
6608
+ }
6609
+ };
6610
+ break;
6611
+ }
6612
+ case "create-comment": {
6613
+ const thread = result.threads[optimisticUpdate.comment.threadId];
6614
+ if (thread === void 0) {
6615
+ break;
6616
+ }
6617
+ result.threads[thread.id] = {
6618
+ ...thread,
6619
+ comments: [...thread.comments, optimisticUpdate.comment]
6620
+ // TODO: Handle replace comment
6621
+ };
6622
+ if (!optimisticUpdate.inboxNotificationId) {
6623
+ break;
6624
+ }
6625
+ const inboxNotification = result.inboxNotifications[optimisticUpdate.inboxNotificationId];
6626
+ result.inboxNotifications[optimisticUpdate.inboxNotificationId] = {
6627
+ ...inboxNotification,
6628
+ notifiedAt: optimisticUpdate.comment.createdAt,
6629
+ readAt: optimisticUpdate.comment.createdAt
6630
+ };
6631
+ break;
6632
+ }
6633
+ case "edit-comment": {
6634
+ const thread = result.threads[optimisticUpdate.threadId];
6635
+ if (thread === void 0) {
6636
+ break;
6637
+ }
6638
+ result.threads[thread.id] = {
6639
+ ...thread,
6640
+ comments: thread.comments.map(
6641
+ (comment) => comment.id === optimisticUpdate.commentId ? {
6642
+ ...comment,
6643
+ editedAt: optimisticUpdate.editedAt,
6644
+ body: optimisticUpdate.body
6645
+ } : comment
6646
+ )
6647
+ };
6648
+ break;
6649
+ }
6650
+ case "delete-comment": {
6651
+ const thread = result.threads[optimisticUpdate.threadId];
6652
+ if (thread === void 0) {
6653
+ break;
6654
+ }
6655
+ result.threads[thread.id] = {
6656
+ ...thread,
6657
+ comments: thread.comments.map(
6658
+ (comment) => comment.id === optimisticUpdate.commentId ? {
6659
+ ...comment,
6660
+ deletedAt: optimisticUpdate.deletedAt,
6661
+ body: void 0
6662
+ } : comment
6663
+ )
6664
+ };
6665
+ if (!result.threads[thread.id].comments.some(
6666
+ (comment) => comment.deletedAt === void 0
6667
+ )) {
6668
+ delete result.threads[thread.id];
6669
+ }
6670
+ break;
6671
+ }
6672
+ case "add-reaction": {
6673
+ const thread = result.threads[optimisticUpdate.threadId];
6674
+ if (thread === void 0) {
6675
+ break;
6676
+ }
6677
+ result.threads[thread.id] = {
6678
+ ...thread,
6679
+ comments: thread.comments.map((comment) => {
6680
+ if (comment.id === optimisticUpdate.commentId) {
6681
+ if (comment.reactions.some(
6682
+ (reaction) => reaction.emoji === optimisticUpdate.emoji
6683
+ )) {
6684
+ return {
6685
+ ...comment,
6686
+ reactions: comment.reactions.map(
6687
+ (reaction) => reaction.emoji === optimisticUpdate.emoji ? {
6688
+ ...reaction,
6689
+ users: [
6690
+ ...reaction.users,
6691
+ { id: optimisticUpdate.userId }
6692
+ ]
6693
+ } : reaction
6694
+ )
6695
+ };
6696
+ } else {
6697
+ return {
6698
+ ...comment,
6699
+ reactions: [
6700
+ ...comment.reactions,
6701
+ {
6702
+ emoji: optimisticUpdate.emoji,
6703
+ createdAt: optimisticUpdate.createdAt,
6704
+ users: [{ id: optimisticUpdate.userId }]
6705
+ }
6706
+ ]
6707
+ };
6708
+ }
6709
+ } else {
6710
+ return comment;
6711
+ }
6712
+ })
6713
+ };
6714
+ break;
6715
+ }
6716
+ case "remove-reaction": {
6717
+ const thread = result.threads[optimisticUpdate.threadId];
6718
+ if (thread === void 0) {
6719
+ break;
6720
+ }
6721
+ result.threads[thread.id] = {
6722
+ ...thread,
6723
+ comments: thread.comments.map((comment) => {
6724
+ if (comment.id !== optimisticUpdate.commentId) {
6725
+ return comment;
6726
+ }
6727
+ const reactionIndex = comment.reactions.findIndex(
6728
+ (reaction) => reaction.emoji === optimisticUpdate.emoji
6729
+ );
6730
+ let reactions = comment.reactions;
6731
+ if (reactionIndex >= 0 && comment.reactions[reactionIndex].users.some(
6732
+ (user) => user.id === optimisticUpdate.userId
6733
+ )) {
6734
+ if (comment.reactions[reactionIndex].users.length <= 1) {
6735
+ reactions = [...comment.reactions];
6736
+ reactions.splice(reactionIndex, 1);
6737
+ } else {
6738
+ reactions[reactionIndex] = {
6739
+ ...reactions[reactionIndex],
6740
+ users: reactions[reactionIndex].users.filter(
6741
+ (user) => user.id !== optimisticUpdate.userId
6742
+ )
6743
+ };
6744
+ }
6745
+ }
6746
+ return {
6747
+ ...comment,
6748
+ reactions
6749
+ };
6750
+ })
6751
+ };
6752
+ break;
6753
+ }
6754
+ case "mark-inbox-notification-as-read": {
6755
+ result.inboxNotifications[optimisticUpdate.inboxNotificationId] = {
6756
+ ...state.inboxNotifications[optimisticUpdate.inboxNotificationId],
6757
+ readAt: optimisticUpdate.readAt
6758
+ };
6759
+ break;
6760
+ }
6761
+ case "mark-inbox-notifications-as-read": {
6762
+ for (const id in result.inboxNotifications) {
6763
+ result.inboxNotifications[id] = {
6764
+ ...result.inboxNotifications[id],
6765
+ readAt: optimisticUpdate.readAt
6766
+ };
6767
+ }
6768
+ break;
6769
+ }
6770
+ case "update-notification-settings": {
6771
+ result.notificationSettings[optimisticUpdate.roomId] = {
6772
+ ...result.notificationSettings[optimisticUpdate.roomId],
6773
+ ...optimisticUpdate.settings
6774
+ };
6775
+ }
6776
+ }
6777
+ }
6778
+ return result;
6779
+ }
6780
+
6405
6781
  // src/client.ts
6406
6782
  var MIN_THROTTLE = 16;
6407
6783
  var MAX_THROTTLE = 1e3;
@@ -6411,6 +6787,8 @@ var MIN_LOST_CONNECTION_TIMEOUT = 200;
6411
6787
  var RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1e3;
6412
6788
  var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
6413
6789
  var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
6790
+ var RESOLVE_USERS_BATCH_DELAY = 50;
6791
+ var RESOLVE_ROOMS_INFO_BATCH_DELAY = 50;
6414
6792
  function getBaseUrlFromClientOptions(clientOptions) {
6415
6793
  if ("liveblocksServer" in clientOptions) {
6416
6794
  throw new Error("Client option no longer supported");
@@ -6421,6 +6799,13 @@ function getBaseUrlFromClientOptions(clientOptions) {
6421
6799
  return DEFAULT_BASE_URL;
6422
6800
  }
6423
6801
  }
6802
+ function getAuthBearerHeaderFromAuthValue(authValue) {
6803
+ if (authValue.type === "public") {
6804
+ return authValue.publicApiKey;
6805
+ } else {
6806
+ return authValue.token.raw;
6807
+ }
6808
+ }
6424
6809
  function createClient(options) {
6425
6810
  const clientOptions = options;
6426
6811
  const throttleDelay = getThrottle(clientOptions.throttle ?? DEFAULT_THROTTLE);
@@ -6430,6 +6815,7 @@ function createClient(options) {
6430
6815
  const backgroundKeepAliveTimeout = getBackgroundKeepAliveTimeout(
6431
6816
  clientOptions.backgroundKeepAliveTimeout
6432
6817
  );
6818
+ const baseUrl = getBaseUrlFromClientOptions(clientOptions);
6433
6819
  const authManager = createAuthManager(options);
6434
6820
  const roomsById = /* @__PURE__ */ new Map();
6435
6821
  function teardownRoom(room) {
@@ -6465,7 +6851,6 @@ function createClient(options) {
6465
6851
  options2.initialPresence === null || options2.initialPresence === void 0,
6466
6852
  "Please provide an initial presence value for the current user when entering the room."
6467
6853
  );
6468
- const baseUrl = getBaseUrlFromClientOptions(clientOptions);
6469
6854
  const newRoom = createRoom(
6470
6855
  {
6471
6856
  initialPresence: options2.initialPresence ?? {},
@@ -6521,58 +6906,450 @@ function createClient(options) {
6521
6906
  const room = roomsById.get(roomId)?.room;
6522
6907
  return room ? room : null;
6523
6908
  }
6524
- function forceLeave(roomId) {
6525
- const unsubs = roomsById.get(roomId)?.unsubs ?? /* @__PURE__ */ new Set();
6526
- for (const unsub of unsubs) {
6527
- unsub();
6909
+ function forceLeave(roomId) {
6910
+ const unsubs = roomsById.get(roomId)?.unsubs ?? /* @__PURE__ */ new Set();
6911
+ for (const unsub of unsubs) {
6912
+ unsub();
6913
+ }
6914
+ }
6915
+ function logout() {
6916
+ authManager.reset();
6917
+ for (const { room } of roomsById.values()) {
6918
+ if (!isIdle(room.getStatus())) {
6919
+ room.reconnect();
6920
+ }
6921
+ }
6922
+ }
6923
+ const currentUserIdStore = createStore(null);
6924
+ const {
6925
+ getInboxNotifications,
6926
+ getUnreadInboxNotificationsCount,
6927
+ markAllInboxNotificationsAsRead,
6928
+ markInboxNotificationAsRead
6929
+ } = createInboxNotificationsApi({
6930
+ baseUrl,
6931
+ fetcher: clientOptions.polyfills?.fetch || /* istanbul ignore next */
6932
+ fetch,
6933
+ authManager,
6934
+ currentUserIdStore
6935
+ });
6936
+ const cacheStore = createClientStore();
6937
+ const resolveUsers = clientOptions.resolveUsers;
6938
+ const warnIfNoResolveUsers = createDevelopmentWarning(
6939
+ () => !resolveUsers,
6940
+ "Set the resolveUsers option in createClient to specify user info."
6941
+ );
6942
+ const usersStore = createBatchStore(
6943
+ async (batchedUserIds) => {
6944
+ const userIds = batchedUserIds.flat();
6945
+ const users = await resolveUsers?.({ userIds });
6946
+ warnIfNoResolveUsers();
6947
+ return users ?? userIds.map(() => void 0);
6948
+ },
6949
+ { delay: RESOLVE_USERS_BATCH_DELAY }
6950
+ );
6951
+ const resolveRoomsInfo = clientOptions.resolveRoomsInfo;
6952
+ const warnIfNoResolveRoomsInfo = createDevelopmentWarning(
6953
+ () => !resolveRoomsInfo,
6954
+ "Set the resolveRoomsInfo option in createClient to specify room info."
6955
+ );
6956
+ const roomsInfoStore = createBatchStore(
6957
+ async (batchedRoomIds) => {
6958
+ const roomIds = batchedRoomIds.flat();
6959
+ const roomsInfo = await resolveRoomsInfo?.({ roomIds });
6960
+ warnIfNoResolveRoomsInfo();
6961
+ return roomsInfo ?? roomIds.map(() => void 0);
6962
+ },
6963
+ { delay: RESOLVE_ROOMS_INFO_BATCH_DELAY }
6964
+ );
6965
+ return Object.defineProperty(
6966
+ {
6967
+ logout,
6968
+ // Old, deprecated APIs
6969
+ enter,
6970
+ getRoom,
6971
+ leave: forceLeave,
6972
+ // New, preferred API
6973
+ enterRoom,
6974
+ // Notifications API
6975
+ getInboxNotifications,
6976
+ getUnreadInboxNotificationsCount,
6977
+ markAllInboxNotificationsAsRead,
6978
+ markInboxNotificationAsRead,
6979
+ // Internal
6980
+ [kInternal]: {
6981
+ currentUserIdStore,
6982
+ resolveMentionSuggestions: clientOptions.resolveMentionSuggestions,
6983
+ cacheStore,
6984
+ usersStore,
6985
+ roomsInfoStore
6986
+ }
6987
+ },
6988
+ kInternal,
6989
+ {
6990
+ enumerable: false
6991
+ }
6992
+ );
6993
+ }
6994
+ var NotificationsApiError = class extends Error {
6995
+ constructor(message, status, details) {
6996
+ super(message);
6997
+ this.message = message;
6998
+ this.status = status;
6999
+ this.details = details;
7000
+ }
7001
+ };
7002
+ function checkBounds(option, value, min, max, recommendedMin) {
7003
+ if (typeof value !== "number" || value < min || max !== void 0 && value > max) {
7004
+ throw new Error(
7005
+ max !== void 0 ? `${option} should be between ${recommendedMin ?? min} and ${max}.` : `${option} should be at least ${recommendedMin ?? min}.`
7006
+ );
7007
+ }
7008
+ return value;
7009
+ }
7010
+ function getBackgroundKeepAliveTimeout(value) {
7011
+ if (value === void 0)
7012
+ return void 0;
7013
+ return checkBounds(
7014
+ "backgroundKeepAliveTimeout",
7015
+ value,
7016
+ MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT
7017
+ );
7018
+ }
7019
+ function getThrottle(value) {
7020
+ return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
7021
+ }
7022
+ function getLostConnectionTimeout(value) {
7023
+ return checkBounds(
7024
+ "lostConnectionTimeout",
7025
+ value,
7026
+ MIN_LOST_CONNECTION_TIMEOUT,
7027
+ MAX_LOST_CONNECTION_TIMEOUT,
7028
+ RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
7029
+ );
7030
+ }
7031
+ function createDevelopmentWarning(condition, ...args) {
7032
+ let hasWarned = false;
7033
+ if (process.env.NODE_ENV !== "production") {
7034
+ return () => {
7035
+ if (!hasWarned && (typeof condition === "function" ? condition() : condition)) {
7036
+ warn(...args);
7037
+ hasWarned = true;
7038
+ }
7039
+ };
7040
+ } else {
7041
+ return () => {
7042
+ };
7043
+ }
7044
+ }
7045
+
7046
+ // src/comments/comment-body.ts
7047
+ function isCommentBodyParagraph(element) {
7048
+ return "type" in element && element.type === "mention";
7049
+ }
7050
+ function isCommentBodyText(element) {
7051
+ return "text" in element && typeof element.text === "string";
7052
+ }
7053
+ function isCommentBodyMention(element) {
7054
+ return "type" in element && element.type === "mention";
7055
+ }
7056
+ function isCommentBodyLink(element) {
7057
+ return "type" in element && element.type === "link";
7058
+ }
7059
+ var commentBodyElementsGuards = {
7060
+ paragraph: isCommentBodyParagraph,
7061
+ text: isCommentBodyText,
7062
+ link: isCommentBodyLink,
7063
+ mention: isCommentBodyMention
7064
+ };
7065
+ var commentBodyElementsTypes = {
7066
+ paragraph: "block",
7067
+ text: "inline",
7068
+ link: "inline",
7069
+ mention: "inline"
7070
+ };
7071
+ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
7072
+ if (!body || !body?.content) {
7073
+ return;
7074
+ }
7075
+ const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
7076
+ const type = element ? commentBodyElementsTypes[element] : "all";
7077
+ const guard = element ? commentBodyElementsGuards[element] : () => true;
7078
+ const visitor = typeof elementOrVisitor === "function" ? elementOrVisitor : possiblyVisitor;
7079
+ for (const block of body.content) {
7080
+ if (type === "all" || type === "block") {
7081
+ if (guard(block)) {
7082
+ visitor?.(block);
7083
+ }
7084
+ }
7085
+ if (type === "all" || type === "inline") {
7086
+ for (const inline of block.children) {
7087
+ if (guard(inline)) {
7088
+ visitor?.(inline);
7089
+ }
7090
+ }
7091
+ }
7092
+ }
7093
+ }
7094
+ function getMentionedIdsFromCommentBody(body) {
7095
+ const mentionedIds = /* @__PURE__ */ new Set();
7096
+ traverseCommentBody(
7097
+ body,
7098
+ "mention",
7099
+ (mention) => mentionedIds.add(mention.id)
7100
+ );
7101
+ return Array.from(mentionedIds);
7102
+ }
7103
+ async function resolveUsersInCommentBody(body, resolveUsers) {
7104
+ const resolvedUsers = /* @__PURE__ */ new Map();
7105
+ if (!resolveUsers) {
7106
+ return resolvedUsers;
7107
+ }
7108
+ const userIds = getMentionedIdsFromCommentBody(body);
7109
+ const users = await resolveUsers({
7110
+ userIds
7111
+ });
7112
+ for (const [index, userId] of userIds.entries()) {
7113
+ const user = users?.[index];
7114
+ if (user) {
7115
+ resolvedUsers.set(userId, user);
7116
+ }
7117
+ }
7118
+ return resolvedUsers;
7119
+ }
7120
+ var htmlEscapables = {
7121
+ "&": "&amp;",
7122
+ "<": "&lt;",
7123
+ ">": "&gt;",
7124
+ '"': "&quot;",
7125
+ "'": "&#39;"
7126
+ };
7127
+ var htmlEscapablesRegex = new RegExp(
7128
+ Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
7129
+ "g"
7130
+ );
7131
+ function htmlSafe(value) {
7132
+ return new HtmlSafeString([String(value)], []);
7133
+ }
7134
+ function joinHtml(strings) {
7135
+ if (strings.length <= 0) {
7136
+ return new HtmlSafeString([""], []);
7137
+ }
7138
+ return new HtmlSafeString(
7139
+ ["", ...Array(strings.length - 1).fill(""), ""],
7140
+ strings
7141
+ );
7142
+ }
7143
+ function escapeHtml(value) {
7144
+ if (value instanceof HtmlSafeString) {
7145
+ return value.toString();
7146
+ }
7147
+ if (Array.isArray(value)) {
7148
+ return joinHtml(value).toString();
7149
+ }
7150
+ return String(value).replace(
7151
+ htmlEscapablesRegex,
7152
+ (character) => htmlEscapables[character]
7153
+ );
7154
+ }
7155
+ var HtmlSafeString = class {
7156
+ constructor(strings, values) {
7157
+ this._strings = strings;
7158
+ this._values = values;
7159
+ }
7160
+ toString() {
7161
+ return this._strings.reduce((result, str, i) => {
7162
+ return result + escapeHtml(nn(this._values[i - 1])) + str;
7163
+ });
7164
+ }
7165
+ };
7166
+ function html(strings, ...values) {
7167
+ return new HtmlSafeString(strings, values);
7168
+ }
7169
+ var markdownEscapables = {
7170
+ _: "\\_",
7171
+ "*": "\\*",
7172
+ "#": "\\#",
7173
+ "`": "\\`",
7174
+ "~": "\\~",
7175
+ "!": "\\!",
7176
+ "|": "\\|",
7177
+ "(": "\\(",
7178
+ ")": "\\)",
7179
+ "{": "\\{",
7180
+ "}": "\\}",
7181
+ "[": "\\[",
7182
+ "]": "\\]"
7183
+ };
7184
+ var markdownEscapablesRegex = new RegExp(
7185
+ Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
7186
+ "g"
7187
+ );
7188
+ function joinMarkdown(strings) {
7189
+ if (strings.length <= 0) {
7190
+ return new MarkdownSafeString([""], []);
7191
+ }
7192
+ return new MarkdownSafeString(
7193
+ ["", ...Array(strings.length - 1).fill(""), ""],
7194
+ strings
7195
+ );
7196
+ }
7197
+ function escapeMarkdown(value) {
7198
+ if (value instanceof MarkdownSafeString) {
7199
+ return value.toString();
7200
+ }
7201
+ if (Array.isArray(value)) {
7202
+ return joinMarkdown(value).toString();
7203
+ }
7204
+ return String(value).replace(
7205
+ markdownEscapablesRegex,
7206
+ (character) => markdownEscapables[character]
7207
+ );
7208
+ }
7209
+ var MarkdownSafeString = class {
7210
+ constructor(strings, values) {
7211
+ this._strings = strings;
7212
+ this._values = values;
7213
+ }
7214
+ toString() {
7215
+ return this._strings.reduce((result, str, i) => {
7216
+ return result + escapeMarkdown(nn(this._values[i - 1])) + str;
7217
+ });
7218
+ }
7219
+ };
7220
+ function markdown(strings, ...values) {
7221
+ return new MarkdownSafeString(strings, values);
7222
+ }
7223
+ function toAbsoluteUrl(url) {
7224
+ if (url.startsWith("http://") || url.startsWith("https://")) {
7225
+ return url;
7226
+ } else if (url.startsWith("www.")) {
7227
+ return "https://" + url;
7228
+ }
7229
+ return;
7230
+ }
7231
+ var stringifyCommentBodyPlainElements = {
7232
+ paragraph: ({ children }) => children,
7233
+ text: ({ element }) => element.text,
7234
+ link: ({ element }) => element.url,
7235
+ mention: ({ element, user }) => {
7236
+ return `@${user?.name ?? element.id}`;
7237
+ }
7238
+ };
7239
+ var stringifyCommentBodyHtmlElements = {
7240
+ paragraph: ({ children }) => {
7241
+ return children ? html`<p>${htmlSafe(children)}</p>` : children;
7242
+ },
7243
+ text: ({ element }) => {
7244
+ let children = element.text;
7245
+ if (!children) {
7246
+ return children;
7247
+ }
7248
+ if (element.bold) {
7249
+ children = html`<strong>${children}</strong>`;
7250
+ }
7251
+ if (element.italic) {
7252
+ children = html`<em>${children}</em>`;
7253
+ }
7254
+ if (element.strikethrough) {
7255
+ children = html`<s>${children}</s>`;
7256
+ }
7257
+ if (element.code) {
7258
+ children = html`<code>${children}</code>`;
7259
+ }
7260
+ return children;
7261
+ },
7262
+ link: ({ element, href }) => {
7263
+ return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
7264
+ },
7265
+ mention: ({ element, user }) => {
7266
+ return html`<span data-mention>@${user?.name ?? element.id}</span>`;
7267
+ }
7268
+ };
7269
+ var stringifyCommentBodyMarkdownElements = {
7270
+ paragraph: ({ children }) => {
7271
+ return children;
7272
+ },
7273
+ text: ({ element }) => {
7274
+ let children = element.text;
7275
+ if (!children) {
7276
+ return children;
6528
7277
  }
6529
- }
6530
- function logout() {
6531
- authManager.reset();
6532
- for (const { room } of roomsById.values()) {
6533
- if (!isIdle(room.getStatus())) {
6534
- room.reconnect();
6535
- }
7278
+ if (element.bold) {
7279
+ children = markdown`**${children}**`;
7280
+ }
7281
+ if (element.italic) {
7282
+ children = markdown`_${children}_`;
7283
+ }
7284
+ if (element.strikethrough) {
7285
+ children = markdown`~~${children}~~`;
6536
7286
  }
7287
+ if (element.code) {
7288
+ children = markdown`\`${children}\``;
7289
+ }
7290
+ return children;
7291
+ },
7292
+ link: ({ element, href }) => {
7293
+ return markdown`[${element.url}](${href})`;
7294
+ },
7295
+ mention: ({ element, user }) => {
7296
+ return markdown`@${user?.name ?? element.id}`;
6537
7297
  }
6538
- return {
6539
- logout,
6540
- // Old, deprecated APIs
6541
- enter,
6542
- getRoom,
6543
- leave: forceLeave,
6544
- // New, preferred API
6545
- enterRoom
7298
+ };
7299
+ async function stringifyCommentBody(body, options) {
7300
+ const format = options?.format ?? "plain";
7301
+ const separator = options?.separator ?? (format === "markdown" ? "\n\n" : "\n");
7302
+ const elements = {
7303
+ ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
7304
+ ...options?.elements
6546
7305
  };
6547
- }
6548
- function checkBounds(option, value, min, max, recommendedMin) {
6549
- if (typeof value !== "number" || value < min || max !== void 0 && value > max) {
6550
- throw new Error(
6551
- max !== void 0 ? `${option} should be between ${recommendedMin ?? min} and ${max}.` : `${option} should be at least ${recommendedMin ?? min}.`
6552
- );
6553
- }
6554
- return value;
6555
- }
6556
- function getBackgroundKeepAliveTimeout(value) {
6557
- if (value === void 0)
6558
- return void 0;
6559
- return checkBounds(
6560
- "backgroundKeepAliveTimeout",
6561
- value,
6562
- MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT
6563
- );
6564
- }
6565
- function getThrottle(value) {
6566
- return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
6567
- }
6568
- function getLostConnectionTimeout(value) {
6569
- return checkBounds(
6570
- "lostConnectionTimeout",
6571
- value,
6572
- MIN_LOST_CONNECTION_TIMEOUT,
6573
- MAX_LOST_CONNECTION_TIMEOUT,
6574
- RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
7306
+ const resolvedUsers = await resolveUsersInCommentBody(
7307
+ body,
7308
+ options?.resolveUsers
6575
7309
  );
7310
+ const blocks = body.content.flatMap((block, blockIndex) => {
7311
+ switch (block.type) {
7312
+ case "paragraph": {
7313
+ const inlines = block.children.flatMap((inline, inlineIndex) => {
7314
+ if (isCommentBodyMention(inline)) {
7315
+ return inline.id ? [
7316
+ elements.mention(
7317
+ {
7318
+ element: inline,
7319
+ user: resolvedUsers.get(inline.id)
7320
+ },
7321
+ inlineIndex
7322
+ )
7323
+ ] : [];
7324
+ }
7325
+ if (isCommentBodyLink(inline)) {
7326
+ return [
7327
+ elements.link(
7328
+ {
7329
+ element: inline,
7330
+ href: toAbsoluteUrl(inline.url) ?? inline.url
7331
+ },
7332
+ inlineIndex
7333
+ )
7334
+ ];
7335
+ }
7336
+ if (isCommentBodyText(inline)) {
7337
+ return [elements.text({ element: inline }, inlineIndex)];
7338
+ }
7339
+ return [];
7340
+ });
7341
+ return [
7342
+ elements.paragraph(
7343
+ { element: block, children: inlines.join("") },
7344
+ blockIndex
7345
+ )
7346
+ ];
7347
+ }
7348
+ default:
7349
+ return [];
7350
+ }
7351
+ });
7352
+ return blocks.join(separator);
6576
7353
  }
6577
7354
 
6578
7355
  // src/crdts/utils.ts
@@ -6906,161 +7683,6 @@ function legacy_patchImmutableNode(state, path, update) {
6906
7683
  }
6907
7684
  }
6908
7685
 
6909
- // src/lib/shallow.ts
6910
- function shallowArray(xs, ys) {
6911
- if (xs.length !== ys.length) {
6912
- return false;
6913
- }
6914
- for (let i = 0; i < xs.length; i++) {
6915
- if (!Object.is(xs[i], ys[i])) {
6916
- return false;
6917
- }
6918
- }
6919
- return true;
6920
- }
6921
- function shallowObj(objA, objB) {
6922
- 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]") {
6923
- return false;
6924
- }
6925
- const keysA = Object.keys(objA);
6926
- if (keysA.length !== Object.keys(objB).length) {
6927
- return false;
6928
- }
6929
- return keysA.every(
6930
- (key) => Object.prototype.hasOwnProperty.call(objB, key) && Object.is(objA[key], objB[key])
6931
- );
6932
- }
6933
- function shallow(a, b) {
6934
- if (Object.is(a, b)) {
6935
- return true;
6936
- }
6937
- const isArrayA = Array.isArray(a);
6938
- const isArrayB = Array.isArray(b);
6939
- if (isArrayA || isArrayB) {
6940
- if (!isArrayA || !isArrayB) {
6941
- return false;
6942
- }
6943
- return shallowArray(a, b);
6944
- }
6945
- return shallowObj(a, b);
6946
- }
6947
-
6948
- // src/lib/AsyncCache.ts
6949
- var noop = () => {
6950
- };
6951
- function isShallowEqual(a, b) {
6952
- if (a.isLoading !== b.isLoading || a.data === void 0 !== (b.data === void 0) || a.error === void 0 !== (b.error === void 0)) {
6953
- return false;
6954
- } else {
6955
- return shallow(a.data, b.data) && shallow(a.error, b.error);
6956
- }
6957
- }
6958
- function createCacheItem(key, asyncFunction, options) {
6959
- const $asyncFunction = async () => asyncFunction(key);
6960
- const context = {
6961
- isInvalid: true
6962
- };
6963
- let state = { isLoading: false };
6964
- let previousState = { isLoading: false };
6965
- const eventSource2 = makeEventSource();
6966
- function notify() {
6967
- const isEqual = options?.isStateEqual ?? isShallowEqual;
6968
- if (!isEqual(previousState, state)) {
6969
- previousState = state;
6970
- eventSource2.notify(state);
6971
- }
6972
- }
6973
- async function resolve() {
6974
- if (!context.promise) {
6975
- return;
6976
- }
6977
- try {
6978
- const data = await context.promise;
6979
- context.isInvalid = false;
6980
- state = {
6981
- isLoading: false,
6982
- data
6983
- };
6984
- } catch (error3) {
6985
- state = {
6986
- isLoading: false,
6987
- data: state.data,
6988
- error: error3
6989
- };
6990
- }
6991
- context.promise = void 0;
6992
- notify();
6993
- }
6994
- async function revalidate() {
6995
- context.isInvalid = true;
6996
- return get();
6997
- }
6998
- async function get() {
6999
- if (context.isInvalid) {
7000
- if (!context.promise) {
7001
- context.isInvalid = true;
7002
- context.promise = $asyncFunction();
7003
- state = { isLoading: true, data: state.data };
7004
- notify();
7005
- }
7006
- await resolve();
7007
- }
7008
- return getState();
7009
- }
7010
- function getState() {
7011
- return state;
7012
- }
7013
- return {
7014
- ...eventSource2.observable,
7015
- get,
7016
- getState,
7017
- revalidate
7018
- };
7019
- }
7020
- function createAsyncCache(asyncFunction, options) {
7021
- const cache = /* @__PURE__ */ new Map();
7022
- function create(key) {
7023
- let cacheItem = cache.get(key);
7024
- if (cacheItem) {
7025
- return cacheItem;
7026
- }
7027
- cacheItem = createCacheItem(key, asyncFunction, options);
7028
- cache.set(key, cacheItem);
7029
- return cacheItem;
7030
- }
7031
- function get(key) {
7032
- return create(key).get();
7033
- }
7034
- function getState(key) {
7035
- return cache.get(key)?.getState();
7036
- }
7037
- function revalidate(key) {
7038
- return create(key).revalidate();
7039
- }
7040
- function subscribe(key, callback) {
7041
- return create(key).subscribe(callback) ?? noop;
7042
- }
7043
- function subscribeOnce(key, callback) {
7044
- return create(key).subscribeOnce(callback) ?? noop;
7045
- }
7046
- function has(key) {
7047
- return cache.has(key);
7048
- }
7049
- function clear() {
7050
- cache.clear();
7051
- }
7052
- return {
7053
- create,
7054
- get,
7055
- getState,
7056
- revalidate,
7057
- subscribe,
7058
- subscribeOnce,
7059
- has,
7060
- clear
7061
- };
7062
- }
7063
-
7064
7686
  // src/lib/Poller.ts
7065
7687
  function makePoller(callback) {
7066
7688
  let context = {
@@ -7150,19 +7772,43 @@ function makePoller(callback) {
7150
7772
  };
7151
7773
  }
7152
7774
 
7153
- // src/lib/stringify.ts
7154
- function stringify(object, ...args) {
7155
- if (typeof object !== "object" || object === null || Array.isArray(object)) {
7156
- return JSON.stringify(object, ...args);
7775
+ // src/lib/shallow.ts
7776
+ function shallowArray(xs, ys) {
7777
+ if (xs.length !== ys.length) {
7778
+ return false;
7157
7779
  }
7158
- const sortedObject = Object.keys(object).sort().reduce(
7159
- (sortedObject2, key) => {
7160
- sortedObject2[key] = object[key];
7161
- return sortedObject2;
7162
- },
7163
- {}
7780
+ for (let i = 0; i < xs.length; i++) {
7781
+ if (!Object.is(xs[i], ys[i])) {
7782
+ return false;
7783
+ }
7784
+ }
7785
+ return true;
7786
+ }
7787
+ function shallowObj(objA, objB) {
7788
+ 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]") {
7789
+ return false;
7790
+ }
7791
+ const keysA = Object.keys(objA);
7792
+ if (keysA.length !== Object.keys(objB).length) {
7793
+ return false;
7794
+ }
7795
+ return keysA.every(
7796
+ (key) => Object.prototype.hasOwnProperty.call(objB, key) && Object.is(objA[key], objB[key])
7164
7797
  );
7165
- return JSON.stringify(sortedObject, ...args);
7798
+ }
7799
+ function shallow(a, b) {
7800
+ if (Object.is(a, b)) {
7801
+ return true;
7802
+ }
7803
+ const isArrayA = Array.isArray(a);
7804
+ const isArrayB = Array.isArray(b);
7805
+ if (isArrayA || isArrayB) {
7806
+ if (!isArrayA || !isArrayB) {
7807
+ return false;
7808
+ }
7809
+ return shallowArray(a, b);
7810
+ }
7811
+ return shallowObj(a, b);
7166
7812
  }
7167
7813
 
7168
7814
  // src/index.ts
@@ -7174,10 +7820,12 @@ export {
7174
7820
  LiveList,
7175
7821
  LiveMap,
7176
7822
  LiveObject,
7823
+ NotificationsApiError,
7177
7824
  OpCode,
7178
7825
  ServerMsgCode,
7179
7826
  WebsocketCloseCodes,
7180
7827
  ackOp,
7828
+ applyOptimisticUpdates,
7181
7829
  asPos,
7182
7830
  assert,
7183
7831
  assertNever,
@@ -7187,9 +7835,7 @@ export {
7187
7835
  convertToCommentData,
7188
7836
  convertToCommentUserReaction,
7189
7837
  convertToThreadData,
7190
- createAsyncCache,
7191
7838
  createClient,
7192
- createCommentsApi,
7193
7839
  deprecate,
7194
7840
  deprecateIf,
7195
7841
  detectDupes,
@@ -7203,6 +7849,7 @@ export {
7203
7849
  isLiveNode,
7204
7850
  isPlainObject,
7205
7851
  isRootCrdt,
7852
+ kInternal,
7206
7853
  legacy_patchImmutableObject,
7207
7854
  lsonToJson,
7208
7855
  makeEventSource,