@liveblocks/core 3.20.0-pre1 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "3.20.0-pre1";
9
+ var PKG_VERSION = "3.20.0";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -701,6 +701,20 @@ var SortedList = class _SortedList {
701
701
  get length() {
702
702
  return this.#data.length;
703
703
  }
704
+ /**
705
+ * Whether the given value is present, by identity. O(log n) plus the length
706
+ * of any run of items that share its sort key (normally 1). Bisects on the
707
+ * value's own key, so it only finds values sitting at their sorted position,
708
+ * which is true for any item currently in the list.
709
+ */
710
+ includes(value) {
711
+ for (let i = bisectRight(this.#data, value, this.#lt) - 1; i >= 0 && !this.#lt(this.#data[i], value); i--) {
712
+ if (this.#data[i] === value) {
713
+ return true;
714
+ }
715
+ }
716
+ return false;
717
+ }
704
718
  *filter(predicate) {
705
719
  for (const item of this.#data) {
706
720
  if (predicate(item)) {
@@ -1556,7 +1570,6 @@ function isUrl(string) {
1556
1570
  function createApiClient({
1557
1571
  baseUrl,
1558
1572
  authManager,
1559
- currentUserId,
1560
1573
  fetchPolyfill
1561
1574
  }) {
1562
1575
  const httpClient = new HttpClient(baseUrl, fetchPolyfill);
@@ -1564,8 +1577,9 @@ function createApiClient({
1564
1577
  const result = await httpClient.get(
1565
1578
  url`/v2/c/rooms/${options.roomId}/threads/delta`,
1566
1579
  await authManager.getAuthValue({
1567
- requestedScope: "comments:read",
1568
- roomId: options.roomId
1580
+ roomId: options.roomId,
1581
+ resource: "comments",
1582
+ access: "read"
1569
1583
  }),
1570
1584
  {
1571
1585
  since: options.since.toISOString()
@@ -1603,8 +1617,9 @@ function createApiClient({
1603
1617
  const result = await httpClient.get(
1604
1618
  url`/v2/c/rooms/${options.roomId}/threads`,
1605
1619
  await authManager.getAuthValue({
1606
- requestedScope: "comments:read",
1607
- roomId: options.roomId
1620
+ roomId: options.roomId,
1621
+ resource: "comments",
1622
+ access: "read"
1608
1623
  }),
1609
1624
  {
1610
1625
  cursor: options.cursor,
@@ -1648,8 +1663,9 @@ function createApiClient({
1648
1663
  const result = await httpClient.get(
1649
1664
  url`/v2/c/rooms/${options.roomId}/threads/comments/search`,
1650
1665
  await authManager.getAuthValue({
1651
- requestedScope: "comments:read",
1652
- roomId: options.roomId
1666
+ roomId: options.roomId,
1667
+ resource: "comments",
1668
+ access: "read"
1653
1669
  }),
1654
1670
  {
1655
1671
  text: options.query.text,
@@ -1670,8 +1686,9 @@ function createApiClient({
1670
1686
  const thread = await httpClient.post(
1671
1687
  url`/v2/c/rooms/${options.roomId}/threads`,
1672
1688
  await authManager.getAuthValue({
1673
- requestedScope: "comments:read",
1674
- roomId: options.roomId
1689
+ roomId: options.roomId,
1690
+ resource: "comments",
1691
+ access: "write"
1675
1692
  }),
1676
1693
  {
1677
1694
  id: threadId,
@@ -1690,8 +1707,9 @@ function createApiClient({
1690
1707
  await httpClient.delete(
1691
1708
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}`,
1692
1709
  await authManager.getAuthValue({
1693
- requestedScope: "comments:read",
1694
- roomId: options.roomId
1710
+ roomId: options.roomId,
1711
+ resource: "comments",
1712
+ access: "write"
1695
1713
  })
1696
1714
  );
1697
1715
  }
@@ -1699,8 +1717,9 @@ function createApiClient({
1699
1717
  const response = await httpClient.rawGet(
1700
1718
  url`/v2/c/rooms/${options.roomId}/thread-with-notification/${options.threadId}`,
1701
1719
  await authManager.getAuthValue({
1702
- requestedScope: "comments:read",
1703
- roomId: options.roomId
1720
+ roomId: options.roomId,
1721
+ resource: "comments",
1722
+ access: "read"
1704
1723
  })
1705
1724
  );
1706
1725
  if (response.ok) {
@@ -1726,8 +1745,9 @@ function createApiClient({
1726
1745
  return await httpClient.post(
1727
1746
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/metadata`,
1728
1747
  await authManager.getAuthValue({
1729
- requestedScope: "comments:read",
1730
- roomId: options.roomId
1748
+ roomId: options.roomId,
1749
+ resource: "comments",
1750
+ access: "write"
1731
1751
  }),
1732
1752
  options.metadata
1733
1753
  );
@@ -1736,8 +1756,9 @@ function createApiClient({
1736
1756
  return await httpClient.post(
1737
1757
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/metadata`,
1738
1758
  await authManager.getAuthValue({
1739
- requestedScope: "comments:read",
1740
- roomId: options.roomId
1759
+ roomId: options.roomId,
1760
+ resource: "comments",
1761
+ access: "write"
1741
1762
  }),
1742
1763
  options.metadata
1743
1764
  );
@@ -1747,8 +1768,9 @@ function createApiClient({
1747
1768
  const comment = await httpClient.post(
1748
1769
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments`,
1749
1770
  await authManager.getAuthValue({
1750
- requestedScope: "comments:read",
1751
- roomId: options.roomId
1771
+ roomId: options.roomId,
1772
+ resource: "comments",
1773
+ access: "write"
1752
1774
  }),
1753
1775
  {
1754
1776
  id: commentId,
@@ -1763,8 +1785,9 @@ function createApiClient({
1763
1785
  const comment = await httpClient.post(
1764
1786
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}`,
1765
1787
  await authManager.getAuthValue({
1766
- requestedScope: "comments:read",
1767
- roomId: options.roomId
1788
+ roomId: options.roomId,
1789
+ resource: "comments",
1790
+ access: "write"
1768
1791
  }),
1769
1792
  {
1770
1793
  body: options.body,
@@ -1778,8 +1801,9 @@ function createApiClient({
1778
1801
  await httpClient.delete(
1779
1802
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}`,
1780
1803
  await authManager.getAuthValue({
1781
- requestedScope: "comments:read",
1782
- roomId: options.roomId
1804
+ roomId: options.roomId,
1805
+ resource: "comments",
1806
+ access: "write"
1783
1807
  })
1784
1808
  );
1785
1809
  }
@@ -1787,8 +1811,9 @@ function createApiClient({
1787
1811
  const reaction = await httpClient.post(
1788
1812
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/reactions`,
1789
1813
  await authManager.getAuthValue({
1790
- requestedScope: "comments:read",
1791
- roomId: options.roomId
1814
+ roomId: options.roomId,
1815
+ resource: "comments",
1816
+ access: "write"
1792
1817
  }),
1793
1818
  { emoji: options.emoji }
1794
1819
  );
@@ -1798,8 +1823,9 @@ function createApiClient({
1798
1823
  await httpClient.delete(
1799
1824
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/reactions/${options.emoji}`,
1800
1825
  await authManager.getAuthValue({
1801
- requestedScope: "comments:read",
1802
- roomId: options.roomId
1826
+ roomId: options.roomId,
1827
+ resource: "comments",
1828
+ access: "write"
1803
1829
  })
1804
1830
  );
1805
1831
  }
@@ -1807,8 +1833,9 @@ function createApiClient({
1807
1833
  await httpClient.post(
1808
1834
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/mark-as-resolved`,
1809
1835
  await authManager.getAuthValue({
1810
- requestedScope: "comments:read",
1811
- roomId: options.roomId
1836
+ roomId: options.roomId,
1837
+ resource: "comments",
1838
+ access: "write"
1812
1839
  })
1813
1840
  );
1814
1841
  }
@@ -1816,8 +1843,9 @@ function createApiClient({
1816
1843
  await httpClient.post(
1817
1844
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/mark-as-unresolved`,
1818
1845
  await authManager.getAuthValue({
1819
- requestedScope: "comments:read",
1820
- roomId: options.roomId
1846
+ roomId: options.roomId,
1847
+ resource: "comments",
1848
+ access: "write"
1821
1849
  })
1822
1850
  );
1823
1851
  }
@@ -1825,8 +1853,9 @@ function createApiClient({
1825
1853
  const subscription = await httpClient.post(
1826
1854
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/subscribe`,
1827
1855
  await authManager.getAuthValue({
1828
- requestedScope: "comments:read",
1829
- roomId: options.roomId
1856
+ roomId: options.roomId,
1857
+ resource: "comments",
1858
+ access: "read"
1830
1859
  })
1831
1860
  );
1832
1861
  return convertToSubscriptionData(subscription);
@@ -1835,8 +1864,9 @@ function createApiClient({
1835
1864
  await httpClient.post(
1836
1865
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/unsubscribe`,
1837
1866
  await authManager.getAuthValue({
1838
- requestedScope: "comments:read",
1839
- roomId: options.roomId
1867
+ roomId: options.roomId,
1868
+ resource: "comments",
1869
+ access: "read"
1840
1870
  })
1841
1871
  );
1842
1872
  }
@@ -1892,8 +1922,9 @@ function createApiClient({
1892
1922
  async () => httpClient.putBlob(
1893
1923
  url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/upload/${encodeURIComponent(attachment.name)}`,
1894
1924
  await authManager.getAuthValue({
1895
- requestedScope: "comments:read",
1896
- roomId
1925
+ roomId,
1926
+ resource: "comments",
1927
+ access: "write"
1897
1928
  }),
1898
1929
  attachment.file,
1899
1930
  { fileSize: attachment.size },
@@ -1910,8 +1941,9 @@ function createApiClient({
1910
1941
  async () => httpClient.post(
1911
1942
  url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${encodeURIComponent(attachment.name)}`,
1912
1943
  await authManager.getAuthValue({
1913
- requestedScope: "comments:read",
1914
- roomId
1944
+ roomId,
1945
+ resource: "comments",
1946
+ access: "write"
1915
1947
  }),
1916
1948
  void 0,
1917
1949
  { signal: abortSignal },
@@ -1936,8 +1968,9 @@ function createApiClient({
1936
1968
  async () => httpClient.putBlob(
1937
1969
  url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${createMultiPartUpload.uploadId}/${String(partNumber)}`,
1938
1970
  await authManager.getAuthValue({
1939
- requestedScope: "comments:read",
1940
- roomId
1971
+ roomId,
1972
+ resource: "comments",
1973
+ access: "write"
1941
1974
  }),
1942
1975
  part,
1943
1976
  void 0,
@@ -1960,8 +1993,9 @@ function createApiClient({
1960
1993
  return httpClient.post(
1961
1994
  url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${uploadId}/complete`,
1962
1995
  await authManager.getAuthValue({
1963
- requestedScope: "comments:read",
1964
- roomId
1996
+ roomId,
1997
+ resource: "comments",
1998
+ access: "write"
1965
1999
  }),
1966
2000
  { parts: sortedUploadedParts },
1967
2001
  { signal: abortSignal }
@@ -1972,8 +2006,9 @@ function createApiClient({
1972
2006
  await httpClient.rawDelete(
1973
2007
  url`/v2/c/rooms/${roomId}/attachments/${attachment.id}/multipart/${uploadId}`,
1974
2008
  await authManager.getAuthValue({
1975
- requestedScope: "comments:read",
1976
- roomId
2009
+ roomId,
2010
+ resource: "comments",
2011
+ access: "write"
1977
2012
  })
1978
2013
  );
1979
2014
  } catch {
@@ -1990,8 +2025,9 @@ function createApiClient({
1990
2025
  const { urls } = await httpClient.post(
1991
2026
  url`/v2/c/rooms/${roomId}/attachments/presigned-urls`,
1992
2027
  await authManager.getAuthValue({
1993
- requestedScope: "comments:read",
1994
- roomId
2028
+ roomId,
2029
+ resource: "comments",
2030
+ access: "read"
1995
2031
  }),
1996
2032
  { attachmentIds }
1997
2033
  );
@@ -2010,109 +2046,13 @@ function createApiClient({
2010
2046
  const batch2 = getOrCreateAttachmentUrlsStore(options.roomId).batch;
2011
2047
  return batch2.get(options.attachmentId);
2012
2048
  }
2013
- async function uploadChatAttachment(options) {
2014
- const { chatId, attachment, signal } = options;
2015
- const userId = currentUserId.get();
2016
- if (userId === void 0) {
2017
- throw new Error("Attachment upload requires an authenticated user.");
2018
- }
2019
- const ATTACHMENT_PART_SIZE = 5 * 1024 * 1024;
2020
- if (options.attachment.file.size <= ATTACHMENT_PART_SIZE) {
2021
- await httpClient.putBlob(
2022
- url`/v2/c/chats/${chatId}/attachments/${attachment.id}/upload/${encodeURIComponent(attachment.file.name)}`,
2023
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2024
- attachment.file,
2025
- { fileSize: attachment.file.size },
2026
- { signal }
2027
- );
2028
- } else {
2029
- const multipartUpload = await httpClient.post(
2030
- url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${encodeURIComponent(attachment.file.name)}`,
2031
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2032
- void 0,
2033
- { signal },
2034
- { fileSize: attachment.file.size }
2035
- );
2036
- try {
2037
- const uploadedParts = [];
2038
- const parts = [];
2039
- let start = 0;
2040
- while (start < attachment.file.size) {
2041
- const end = Math.min(
2042
- start + ATTACHMENT_PART_SIZE,
2043
- attachment.file.size
2044
- );
2045
- parts.push({
2046
- number: parts.length + 1,
2047
- part: attachment.file.slice(start, end)
2048
- });
2049
- start = end;
2050
- }
2051
- uploadedParts.push(
2052
- ...await Promise.all(
2053
- parts.map(async ({ number, part }) => {
2054
- return await httpClient.putBlob(
2055
- url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${multipartUpload.uploadId}/${String(number)}`,
2056
- await authManager.getAuthValue({
2057
- requestedScope: "comments:read"
2058
- }),
2059
- part,
2060
- void 0,
2061
- { signal }
2062
- );
2063
- })
2064
- )
2065
- );
2066
- await httpClient.post(
2067
- url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${multipartUpload.uploadId}/complete`,
2068
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2069
- { parts: uploadedParts.sort((a, b) => a.number - b.number) },
2070
- { signal }
2071
- );
2072
- } catch (err) {
2073
- try {
2074
- await httpClient.delete(
2075
- url`/v2/c/chats/${chatId}/attachments/${attachment.id}/multipart/${multipartUpload.uploadId}`,
2076
- await authManager.getAuthValue({ requestedScope: "comments:read" })
2077
- );
2078
- } catch {
2079
- }
2080
- throw err;
2081
- }
2082
- }
2083
- }
2084
- const attachmentUrlsBatchStoresByChat = new DefaultMap((chatId) => {
2085
- const batch2 = new Batch(
2086
- async (batchedAttachmentIds) => {
2087
- const attachmentIds = batchedAttachmentIds.flat();
2088
- const { urls } = await httpClient.post(
2089
- url`/v2/c/chats/${chatId}/attachments/presigned-urls`,
2090
- await authManager.getAuthValue({
2091
- requestedScope: "comments:read"
2092
- }),
2093
- { attachmentIds }
2094
- );
2095
- return urls.map(
2096
- (url2) => url2 ?? new Error("There was an error while getting this attachment's URL")
2097
- );
2098
- },
2099
- { delay: 50 }
2100
- );
2101
- return createBatchStore(batch2);
2102
- });
2103
- function getOrCreateChatAttachmentUrlsStore(chatId) {
2104
- return attachmentUrlsBatchStoresByChat.getOrCreate(chatId);
2105
- }
2106
- function getChatAttachmentUrl(options) {
2107
- const batch2 = getOrCreateChatAttachmentUrlsStore(options.chatId).batch;
2108
- return batch2.get(options.attachmentId);
2109
- }
2110
2049
  async function getSubscriptionSettings(options) {
2111
2050
  return httpClient.get(
2112
2051
  url`/v2/c/rooms/${options.roomId}/subscription-settings`,
2113
2052
  await authManager.getAuthValue({
2114
- requestedScope: "comments:read",
2115
- roomId: options.roomId
2053
+ roomId: options.roomId,
2054
+ resource: "comments",
2055
+ access: "read"
2116
2056
  }),
2117
2057
  void 0,
2118
2058
  {
@@ -2124,8 +2064,9 @@ function createApiClient({
2124
2064
  return httpClient.post(
2125
2065
  url`/v2/c/rooms/${options.roomId}/subscription-settings`,
2126
2066
  await authManager.getAuthValue({
2127
- requestedScope: "comments:read",
2128
- roomId: options.roomId
2067
+ roomId: options.roomId,
2068
+ resource: "comments",
2069
+ access: "read"
2129
2070
  }),
2130
2071
  options.settings
2131
2072
  );
@@ -2137,8 +2078,9 @@ function createApiClient({
2137
2078
  await httpClient.post(
2138
2079
  url`/v2/c/rooms/${roomId}/inbox-notifications/read`,
2139
2080
  await authManager.getAuthValue({
2140
- requestedScope: "comments:read",
2141
- roomId
2081
+ roomId,
2082
+ resource: "comments",
2083
+ access: "read"
2142
2084
  }),
2143
2085
  { inboxNotificationIds }
2144
2086
  );
@@ -2158,8 +2100,9 @@ function createApiClient({
2158
2100
  await httpClient.rawPost(
2159
2101
  url`/v2/c/rooms/${options.roomId}/text-mentions`,
2160
2102
  await authManager.getAuthValue({
2161
- requestedScope: "comments:read",
2162
- roomId: options.roomId
2103
+ roomId: options.roomId,
2104
+ resource: "storage",
2105
+ access: "write"
2163
2106
  }),
2164
2107
  {
2165
2108
  userId: options.mention.kind === "user" ? options.mention.id : void 0,
@@ -2173,8 +2116,9 @@ function createApiClient({
2173
2116
  await httpClient.rawDelete(
2174
2117
  url`/v2/c/rooms/${options.roomId}/text-mentions/${options.mentionId}`,
2175
2118
  await authManager.getAuthValue({
2176
- requestedScope: "comments:read",
2177
- roomId: options.roomId
2119
+ roomId: options.roomId,
2120
+ resource: "storage",
2121
+ access: "write"
2178
2122
  })
2179
2123
  );
2180
2124
  }
@@ -2182,8 +2126,9 @@ function createApiClient({
2182
2126
  return httpClient.rawGet(
2183
2127
  url`/v2/c/rooms/${options.roomId}/y-version/${options.versionId}`,
2184
2128
  await authManager.getAuthValue({
2185
- requestedScope: "comments:read",
2186
- roomId: options.roomId
2129
+ roomId: options.roomId,
2130
+ resource: "storage",
2131
+ access: "read"
2187
2132
  })
2188
2133
  );
2189
2134
  }
@@ -2191,8 +2136,9 @@ function createApiClient({
2191
2136
  await httpClient.rawPost(
2192
2137
  url`/v2/c/rooms/${options.roomId}/version`,
2193
2138
  await authManager.getAuthValue({
2194
- requestedScope: "comments:read",
2195
- roomId: options.roomId
2139
+ roomId: options.roomId,
2140
+ resource: "storage",
2141
+ access: "write"
2196
2142
  })
2197
2143
  );
2198
2144
  }
@@ -2200,8 +2146,9 @@ function createApiClient({
2200
2146
  await httpClient.rawPost(
2201
2147
  url`/v2/c/rooms/${options.roomId}/text-metadata`,
2202
2148
  await authManager.getAuthValue({
2203
- requestedScope: "comments:read",
2204
- roomId: options.roomId
2149
+ roomId: options.roomId,
2150
+ resource: "storage",
2151
+ access: "read"
2205
2152
  }),
2206
2153
  {
2207
2154
  type: options.type,
@@ -2213,8 +2160,9 @@ function createApiClient({
2213
2160
  const result = await httpClient.post(
2214
2161
  url`/v2/c/rooms/${options.roomId}/ai/contextual-prompt`,
2215
2162
  await authManager.getAuthValue({
2216
- requestedScope: "room:read",
2217
- roomId: options.roomId
2163
+ roomId: options.roomId,
2164
+ resource: "storage",
2165
+ access: "read"
2218
2166
  }),
2219
2167
  {
2220
2168
  prompt: options.prompt,
@@ -2236,8 +2184,9 @@ function createApiClient({
2236
2184
  const result = await httpClient.get(
2237
2185
  url`/v2/c/rooms/${options.roomId}/versions`,
2238
2186
  await authManager.getAuthValue({
2239
- requestedScope: "comments:read",
2240
- roomId: options.roomId
2187
+ roomId: options.roomId,
2188
+ resource: "storage",
2189
+ access: "read"
2241
2190
  })
2242
2191
  );
2243
2192
  return {
@@ -2254,8 +2203,9 @@ function createApiClient({
2254
2203
  const result = await httpClient.get(
2255
2204
  url`/v2/c/rooms/${options.roomId}/versions/delta`,
2256
2205
  await authManager.getAuthValue({
2257
- requestedScope: "comments:read",
2258
- roomId: options.roomId
2206
+ roomId: options.roomId,
2207
+ resource: "storage",
2208
+ access: "read"
2259
2209
  }),
2260
2210
  { since: options.since.toISOString() },
2261
2211
  { signal: options.signal }
@@ -2274,8 +2224,9 @@ function createApiClient({
2274
2224
  const result = await httpClient.rawGet(
2275
2225
  url`/v2/c/rooms/${options.roomId}/storage`,
2276
2226
  await authManager.getAuthValue({
2277
- requestedScope: "room:read",
2278
- roomId: options.roomId
2227
+ roomId: options.roomId,
2228
+ resource: "storage",
2229
+ access: "read"
2279
2230
  })
2280
2231
  );
2281
2232
  return await result.json();
@@ -2288,7 +2239,7 @@ function createApiClient({
2288
2239
  }
2289
2240
  const json = await httpClient.get(
2290
2241
  url`/v2/c/inbox-notifications`,
2291
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2242
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2292
2243
  {
2293
2244
  cursor: options?.cursor,
2294
2245
  limit: PAGE_SIZE,
@@ -2314,7 +2265,7 @@ function createApiClient({
2314
2265
  }
2315
2266
  const json = await httpClient.get(
2316
2267
  url`/v2/c/inbox-notifications/delta`,
2317
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2268
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2318
2269
  { since: options.since.toISOString(), query },
2319
2270
  { signal: options.signal }
2320
2271
  );
@@ -2343,7 +2294,7 @@ function createApiClient({
2343
2294
  }
2344
2295
  const { count } = await httpClient.get(
2345
2296
  url`/v2/c/inbox-notifications/count`,
2346
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2297
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2347
2298
  { query },
2348
2299
  { signal: options?.signal }
2349
2300
  );
@@ -2352,7 +2303,7 @@ function createApiClient({
2352
2303
  async function markAllInboxNotificationsAsRead() {
2353
2304
  await httpClient.post(
2354
2305
  url`/v2/c/inbox-notifications/read`,
2355
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2306
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2356
2307
  {
2357
2308
  inboxNotificationIds: "all"
2358
2309
  }
@@ -2361,7 +2312,7 @@ function createApiClient({
2361
2312
  async function markInboxNotificationsAsRead(inboxNotificationIds) {
2362
2313
  await httpClient.post(
2363
2314
  url`/v2/c/inbox-notifications/read`,
2364
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2315
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2365
2316
  {
2366
2317
  inboxNotificationIds
2367
2318
  }
@@ -2381,19 +2332,19 @@ function createApiClient({
2381
2332
  async function deleteAllInboxNotifications() {
2382
2333
  await httpClient.delete(
2383
2334
  url`/v2/c/inbox-notifications`,
2384
- await authManager.getAuthValue({ requestedScope: "comments:read" })
2335
+ await authManager.getAuthValue({ resource: "personal", access: "write" })
2385
2336
  );
2386
2337
  }
2387
2338
  async function deleteInboxNotification(inboxNotificationId) {
2388
2339
  await httpClient.delete(
2389
2340
  url`/v2/c/inbox-notifications/${inboxNotificationId}`,
2390
- await authManager.getAuthValue({ requestedScope: "comments:read" })
2341
+ await authManager.getAuthValue({ resource: "personal", access: "write" })
2391
2342
  );
2392
2343
  }
2393
2344
  async function getNotificationSettings(options) {
2394
2345
  return httpClient.get(
2395
2346
  url`/v2/c/notification-settings`,
2396
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2347
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2397
2348
  void 0,
2398
2349
  { signal: options?.signal }
2399
2350
  );
@@ -2401,7 +2352,7 @@ function createApiClient({
2401
2352
  async function updateNotificationSettings(settings) {
2402
2353
  return httpClient.post(
2403
2354
  url`/v2/c/notification-settings`,
2404
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2355
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2405
2356
  settings
2406
2357
  );
2407
2358
  }
@@ -2413,7 +2364,7 @@ function createApiClient({
2413
2364
  const PAGE_SIZE = 50;
2414
2365
  const json = await httpClient.get(
2415
2366
  url`/v2/c/threads`,
2416
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2367
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2417
2368
  {
2418
2369
  cursor: options?.cursor,
2419
2370
  query,
@@ -2434,7 +2385,7 @@ function createApiClient({
2434
2385
  async function getUserThreadsSince_experimental(options) {
2435
2386
  const json = await httpClient.get(
2436
2387
  url`/v2/c/threads/delta`,
2437
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2388
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2438
2389
  { since: options.since.toISOString() },
2439
2390
  { signal: options.signal }
2440
2391
  );
@@ -2463,7 +2414,8 @@ function createApiClient({
2463
2414
  const { groups: plainGroups } = await httpClient.post(
2464
2415
  url`/v2/c/groups/find`,
2465
2416
  await authManager.getAuthValue({
2466
- requestedScope: "comments:read"
2417
+ resource: "personal",
2418
+ access: "write"
2467
2419
  }),
2468
2420
  { groupIds }
2469
2421
  );
@@ -2482,7 +2434,7 @@ function createApiClient({
2482
2434
  async function getUrlMetadata(_url) {
2483
2435
  const { metadata } = await httpClient.get(
2484
2436
  url`/v2/c/urls/metadata`,
2485
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
2437
+ await authManager.getAuthValue({ resource: "personal", access: "write" }),
2486
2438
  { url: _url }
2487
2439
  );
2488
2440
  return metadata;
@@ -2522,10 +2474,6 @@ function createApiClient({
2522
2474
  getAttachmentUrl,
2523
2475
  uploadAttachment,
2524
2476
  getOrCreateAttachmentUrlsStore,
2525
- // User attachments
2526
- uploadChatAttachment,
2527
- getOrCreateChatAttachmentUrlsStore,
2528
- getChatAttachmentUrl,
2529
2477
  // Room storage
2530
2478
  streamStorage,
2531
2479
  // Notifications
@@ -5219,22 +5167,327 @@ function createReceivingToolInvocation(invocationId, name, partialArgsText = "")
5219
5167
  };
5220
5168
  }
5221
5169
 
5222
- // src/protocol/AuthToken.ts
5223
- var Permission = /* @__PURE__ */ ((Permission2) => {
5224
- Permission2["Read"] = "room:read";
5225
- Permission2["Write"] = "room:write";
5226
- Permission2["PresenceWrite"] = "room:presence:write";
5227
- Permission2["CommentsWrite"] = "comments:write";
5228
- Permission2["CommentsRead"] = "comments:read";
5229
- Permission2["FeedsWrite"] = "feeds:write";
5230
- return Permission2;
5231
- })(Permission || {});
5232
- function canWriteStorage(scopes) {
5233
- return scopes.includes("room:write" /* Write */);
5234
- }
5235
- function canComment(scopes) {
5236
- return scopes.includes("comments:write" /* CommentsWrite */) || scopes.includes("room:write" /* Write */);
5170
+ // src/permissions.ts
5171
+ var Permission = {
5172
+ /**
5173
+ * Default permission for a room.
5174
+ */
5175
+ Read: "*:read",
5176
+ Write: "*:write",
5177
+ /**
5178
+ * Legacy aliases for default room permissions.
5179
+ */
5180
+ RoomWrite: "room:write",
5181
+ RoomRead: "room:read",
5182
+ /**
5183
+ * Storage
5184
+ */
5185
+ StorageRead: "storage:read",
5186
+ StorageWrite: "storage:write",
5187
+ StorageNone: "storage:none",
5188
+ /**
5189
+ * Comments
5190
+ */
5191
+ CommentsWrite: "comments:write",
5192
+ CommentsRead: "comments:read",
5193
+ CommentsNone: "comments:none",
5194
+ /**
5195
+ * Feeds
5196
+ */
5197
+ FeedsRead: "feeds:read",
5198
+ FeedsWrite: "feeds:write",
5199
+ FeedsNone: "feeds:none",
5200
+ /**
5201
+ * Legacy
5202
+ */
5203
+ LegacyRoomPresenceWrite: "room:presence:write"
5204
+ };
5205
+ var ACCESS_LEVELS = ["none", "read", "write"];
5206
+ var basePermissionScopes = /* @__PURE__ */ new Set([
5207
+ Permission.Read,
5208
+ Permission.Write,
5209
+ Permission.RoomRead,
5210
+ Permission.RoomWrite
5211
+ ]);
5212
+ var ACCESS_LEVEL_RANKS = {
5213
+ none: 0,
5214
+ read: 1,
5215
+ write: 2
5216
+ };
5217
+ var PERMISSIONS_BY_RESOURCE = {
5218
+ room: {
5219
+ read: [Permission.Read, Permission.RoomRead],
5220
+ write: [Permission.Write, Permission.RoomWrite]
5221
+ },
5222
+ personal: {
5223
+ write: []
5224
+ },
5225
+ storage: {
5226
+ write: [Permission.StorageWrite],
5227
+ read: [Permission.StorageRead],
5228
+ none: [Permission.StorageNone]
5229
+ },
5230
+ comments: {
5231
+ write: [Permission.CommentsWrite],
5232
+ read: [Permission.CommentsRead],
5233
+ none: [Permission.CommentsNone]
5234
+ },
5235
+ feeds: {
5236
+ write: [Permission.FeedsWrite],
5237
+ read: [Permission.FeedsRead],
5238
+ none: [Permission.FeedsNone]
5239
+ }
5240
+ };
5241
+ var NO_PERMISSION_MATRIX = {
5242
+ room: "none",
5243
+ storage: "none",
5244
+ comments: "none",
5245
+ feeds: "none",
5246
+ personal: "none"
5247
+ };
5248
+ var BASE_PERMISSION_RESOURCE = "room";
5249
+ var ROOM_PERMISSION_RESOURCES = [
5250
+ "storage",
5251
+ "comments",
5252
+ "feeds"
5253
+ ];
5254
+ var VALID_PERMISSIONS = new Set(Object.values(Permission));
5255
+ function isPermission(permission) {
5256
+ return VALID_PERMISSIONS.has(permission);
5257
+ }
5258
+ function resolveResourceAccess(scopes, resource) {
5259
+ const permissions = PERMISSIONS_BY_RESOURCE[resource];
5260
+ let resourceAccess;
5261
+ for (const access of ACCESS_LEVELS) {
5262
+ const scopedPermissions = permissions[access];
5263
+ if (scopedPermissions !== void 0 && scopedPermissions.some((permission) => scopes.includes(permission))) {
5264
+ resourceAccess = access;
5265
+ }
5266
+ }
5267
+ return resourceAccess;
5268
+ }
5269
+ function permissionMatrixFromResolvedScopes(resolved) {
5270
+ if (!resolved.hasDefaultPermission) {
5271
+ return { ...NO_PERMISSION_MATRIX };
5272
+ }
5273
+ const matrix = {
5274
+ ...NO_PERMISSION_MATRIX,
5275
+ [BASE_PERMISSION_RESOURCE]: resolved.baseAccess,
5276
+ personal: "write"
5277
+ };
5278
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5279
+ matrix[resource] = resolved.matrix[resource] ?? resolved.baseAccess;
5280
+ }
5281
+ return matrix;
5282
+ }
5283
+ function permissionMatrixFromScopes(scopes) {
5284
+ return permissionMatrixFromResolvedScopes(resolvePermissionScopes(scopes));
5285
+ }
5286
+ function resolvePermissionScopes(scopes) {
5287
+ const hasDefaultPermission = scopes.includes(Permission.Write) || scopes.includes(Permission.Read) || scopes.includes(Permission.RoomWrite) || scopes.includes(Permission.RoomRead);
5288
+ const baseAccess = scopes.includes(Permission.Write) || scopes.includes(Permission.RoomWrite) ? "write" : scopes.includes(Permission.Read) || scopes.includes(Permission.RoomRead) ? "read" : "none";
5289
+ const matrix = {};
5290
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5291
+ const access = resolveResourceAccess(scopes, resource);
5292
+ if (access !== void 0) {
5293
+ matrix[resource] = access;
5294
+ }
5295
+ }
5296
+ return { hasDefaultPermission, baseAccess, matrix };
5297
+ }
5298
+ function hasPermissionAccess(matrix, resource, requiredAccess) {
5299
+ const access = matrix[resource] ?? "none";
5300
+ return ACCESS_LEVEL_RANKS[access] >= ACCESS_LEVEL_RANKS[requiredAccess];
5301
+ }
5302
+ function resolveRoomPermissionMatrix(permissions, roomId) {
5303
+ const matchedPermissions = permissions.filter(
5304
+ (entry) => roomPatternMatches(entry.pattern, roomId)
5305
+ );
5306
+ if (matchedPermissions.length === 0) {
5307
+ return void 0;
5308
+ }
5309
+ let hasDefaultPermission = false;
5310
+ let baseAccess = "none";
5311
+ const explicitMatrix = {};
5312
+ const explicitSpecificity = {};
5313
+ for (const entry of matchedPermissions) {
5314
+ const resolved = resolvePermissionScopes(entry.scopes);
5315
+ const specificity = roomPatternSpecificity(entry.pattern);
5316
+ if (resolved.hasDefaultPermission) {
5317
+ hasDefaultPermission = true;
5318
+ baseAccess = strongestAccess(baseAccess, resolved.baseAccess);
5319
+ }
5320
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5321
+ const access = resolved.matrix[resource];
5322
+ if (access !== void 0) {
5323
+ const currentSpecificity = explicitSpecificity[resource] ?? -1;
5324
+ if (specificity > currentSpecificity) {
5325
+ explicitMatrix[resource] = access;
5326
+ explicitSpecificity[resource] = specificity;
5327
+ } else if (specificity === currentSpecificity) {
5328
+ explicitMatrix[resource] = strongestAccess(
5329
+ explicitMatrix[resource] ?? "none",
5330
+ access
5331
+ );
5332
+ }
5333
+ }
5334
+ }
5335
+ }
5336
+ return permissionMatrixFromResolvedScopes({
5337
+ hasDefaultPermission,
5338
+ baseAccess,
5339
+ matrix: explicitMatrix
5340
+ });
5341
+ }
5342
+ function normalizeRoomPermissions(permissions) {
5343
+ if (!Array.isArray(permissions)) {
5344
+ throw new Error("Permission list must be an array");
5345
+ }
5346
+ const result = [];
5347
+ for (const permission of permissions) {
5348
+ if (!isPermission(permission)) {
5349
+ throw new Error(`Not a valid permission: ${permission}`);
5350
+ }
5351
+ result.push(permission);
5352
+ }
5353
+ return result;
5354
+ }
5355
+ function normalizeRoomAccesses(accesses) {
5356
+ if (accesses === void 0) {
5357
+ return void 0;
5358
+ }
5359
+ return Object.fromEntries(
5360
+ Object.entries(accesses).map(([id, permissions]) => [
5361
+ id,
5362
+ normalizeRoomPermissions(permissions)
5363
+ ])
5364
+ );
5365
+ }
5366
+ function normalizeUpdateRoomAccesses(accesses) {
5367
+ if (accesses === void 0) {
5368
+ return void 0;
5369
+ }
5370
+ return Object.fromEntries(
5371
+ Object.entries(accesses).map(([id, permissions]) => [
5372
+ id,
5373
+ permissions === null ? null : normalizeRoomPermissions(permissions)
5374
+ ])
5375
+ );
5376
+ }
5377
+ function permissionMatrixToScopes(matrix) {
5378
+ const scopes = [];
5379
+ const baseAccess = matrix.room;
5380
+ if (baseAccess !== "none") {
5381
+ scopes.push(permissionForAccessLevel(BASE_PERMISSION_RESOURCE, baseAccess));
5382
+ }
5383
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5384
+ const access = matrix[resource];
5385
+ if (access !== baseAccess) {
5386
+ scopes.push(permissionForAccessLevel(resource, access));
5387
+ }
5388
+ }
5389
+ return scopes;
5390
+ }
5391
+ function mergeRoomPermissionScopes({
5392
+ defaultAccesses,
5393
+ groupsAccesses,
5394
+ userAccesses
5395
+ }) {
5396
+ const sources = [
5397
+ resolvePermissionScopes(defaultAccesses),
5398
+ mergeResolvedScopesByHighestAccess(
5399
+ groupsAccesses.map(resolvePermissionScopes)
5400
+ ),
5401
+ resolvePermissionScopes(userAccesses)
5402
+ ];
5403
+ const merged = {
5404
+ hasDefaultPermission: false,
5405
+ baseAccess: "none",
5406
+ matrix: {}
5407
+ };
5408
+ for (const source of sources) {
5409
+ if (source.hasDefaultPermission) {
5410
+ merged.hasDefaultPermission = true;
5411
+ merged.baseAccess = source.baseAccess;
5412
+ }
5413
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5414
+ const access = source.matrix[resource];
5415
+ if (access !== void 0) {
5416
+ merged.matrix[resource] = access;
5417
+ }
5418
+ }
5419
+ }
5420
+ return permissionMatrixToScopes(permissionMatrixFromResolvedScopes(merged));
5421
+ }
5422
+ function mergeResolvedScopesByHighestAccess(sources) {
5423
+ const merged = {
5424
+ hasDefaultPermission: false,
5425
+ baseAccess: "none",
5426
+ matrix: {}
5427
+ };
5428
+ for (const source of sources) {
5429
+ if (source.hasDefaultPermission) {
5430
+ merged.hasDefaultPermission = true;
5431
+ merged.baseAccess = strongestAccess(merged.baseAccess, source.baseAccess);
5432
+ }
5433
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5434
+ const access = source.matrix[resource];
5435
+ if (access !== void 0) {
5436
+ merged.matrix[resource] = strongestAccess(
5437
+ merged.matrix[resource] ?? "none",
5438
+ access
5439
+ );
5440
+ }
5441
+ }
5442
+ }
5443
+ return merged;
5444
+ }
5445
+ function permissionForAccessLevel(resource, access, field = resource) {
5446
+ const levels = PERMISSIONS_BY_RESOURCE[resource];
5447
+ const permissions = levels[access];
5448
+ if (permissions === void 0 || permissions.length === 0) {
5449
+ throw new Error(
5450
+ `Invalid permission level for ${field}: ${JSON.stringify(access) ?? String(access)}`
5451
+ );
5452
+ }
5453
+ return permissions[0];
5454
+ }
5455
+ function strongestAccess(left, right) {
5456
+ return ACCESS_LEVEL_RANKS[right] > ACCESS_LEVEL_RANKS[left] ? right : left;
5457
+ }
5458
+ function roomPatternMatches(pattern, roomId) {
5459
+ if (pattern.includes("*")) {
5460
+ return roomId.startsWith(pattern.replace("*", ""));
5461
+ }
5462
+ return pattern === roomId;
5463
+ }
5464
+ function roomPatternSpecificity(pattern) {
5465
+ return pattern.replace("*", "").length;
5466
+ }
5467
+ function validatePermissionsSet(scopes) {
5468
+ const unknownScopes = scopes.filter((scope) => !VALID_PERMISSIONS.has(scope));
5469
+ if (unknownScopes.length > 0) {
5470
+ return `Unknown permission scope(s): ${unknownScopes.join(", ")}`;
5471
+ }
5472
+ const baseScopes = scopes.filter((scope) => basePermissionScopes.has(scope));
5473
+ if (baseScopes.length !== 1) {
5474
+ return `Permissions must include exactly one of ${Permission.Read}, ${Permission.Write} (or the legacy aliases ${Permission.RoomRead}, ${Permission.RoomWrite}), got ${baseScopes.length === 0 ? "none" : baseScopes.join(", ")}`;
5475
+ }
5476
+ const seenFeatures = /* @__PURE__ */ new Set();
5477
+ for (const scope of scopes) {
5478
+ if (basePermissionScopes.has(scope) || scope === Permission.LegacyRoomPresenceWrite) {
5479
+ continue;
5480
+ }
5481
+ const feature = scope.slice(0, scope.indexOf(":"));
5482
+ if (seenFeatures.has(feature)) {
5483
+ return `Permissions can include at most one scope per feature, got multiple "${feature}" scopes`;
5484
+ }
5485
+ seenFeatures.add(feature);
5486
+ }
5487
+ return true;
5237
5488
  }
5489
+
5490
+ // src/protocol/AuthToken.ts
5238
5491
  function isValidAuthTokenPayload(data) {
5239
5492
  return isPlainObject(data) && (data.k === "acc" /* ACCESS_TOKEN */ || data.k === "id" /* ID_TOKEN */);
5240
5493
  }
@@ -5273,47 +5526,22 @@ function createAuthManager(authOptions, onAuthenticate) {
5273
5526
  const authentication = prepareAuthentication(authOptions);
5274
5527
  const seenTokens = /* @__PURE__ */ new Set();
5275
5528
  const tokens = [];
5276
- const expiryTimes = [];
5277
5529
  const requestPromises = /* @__PURE__ */ new Map();
5278
5530
  function reset() {
5279
5531
  seenTokens.clear();
5280
5532
  tokens.length = 0;
5281
- expiryTimes.length = 0;
5282
5533
  requestPromises.clear();
5283
5534
  }
5284
- function hasCorrespondingScopes(requestedScope, scopes) {
5285
- if (requestedScope === "comments:read") {
5286
- return scopes.includes("comments:read" /* CommentsRead */) || scopes.includes("comments:write" /* CommentsWrite */) || scopes.includes("room:read" /* Read */) || scopes.includes("room:write" /* Write */);
5287
- } else if (requestedScope === "room:read") {
5288
- return scopes.includes("room:read" /* Read */) || scopes.includes("room:write" /* Write */);
5289
- }
5290
- return false;
5291
- }
5292
5535
  function getCachedToken(requestOptions) {
5293
5536
  const now2 = Math.ceil(Date.now() / 1e3);
5294
5537
  for (let i = tokens.length - 1; i >= 0; i--) {
5295
- const token = tokens[i];
5296
- const expiresAt = expiryTimes[i];
5297
- if (expiresAt <= now2) {
5538
+ const cachedToken = tokens[i];
5539
+ if (cachedToken.expiresAt <= now2) {
5298
5540
  tokens.splice(i, 1);
5299
- expiryTimes.splice(i, 1);
5300
5541
  continue;
5301
5542
  }
5302
- if (token.parsed.k === "id" /* ID_TOKEN */) {
5303
- return token;
5304
- } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
5305
- if (!requestOptions.roomId && Object.entries(token.parsed.perms).length === 0) {
5306
- return token;
5307
- }
5308
- for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
5309
- if (!requestOptions.roomId) {
5310
- if (resource.includes("*") && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
5311
- return token;
5312
- }
5313
- } else if (resource.includes("*") && requestOptions.roomId.startsWith(resource.replace("*", "")) || requestOptions.roomId === resource && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
5314
- return token;
5315
- }
5316
- }
5543
+ if (cachedTokenSatisfiesRequest(cachedToken, requestOptions)) {
5544
+ return cachedToken.token;
5317
5545
  }
5318
5546
  }
5319
5547
  return void 0;
@@ -5331,6 +5559,10 @@ function createAuthManager(authOptions, onAuthenticate) {
5331
5559
  });
5332
5560
  const parsed = parseAuthToken(response.token);
5333
5561
  if (seenTokens.has(parsed.raw)) {
5562
+ const cachedToken = getCachedToken(options);
5563
+ if (cachedToken?.raw === parsed.raw) {
5564
+ return cachedToken;
5565
+ }
5334
5566
  throw new StopRetrying(
5335
5567
  "The same Liveblocks auth token was issued from the backend before. Caching Liveblocks tokens is not supported."
5336
5568
  );
@@ -5371,11 +5603,12 @@ function createAuthManager(authOptions, onAuthenticate) {
5371
5603
  return { type: "secret", token: cachedToken };
5372
5604
  }
5373
5605
  let currentPromise;
5374
- if (requestOptions.roomId) {
5375
- currentPromise = requestPromises.get(requestOptions.roomId);
5606
+ const requestKey = getAuthRequestKey(requestOptions);
5607
+ if (requestKey !== void 0) {
5608
+ currentPromise = requestPromises.get(requestKey);
5376
5609
  if (currentPromise === void 0) {
5377
5610
  currentPromise = makeAuthRequest(requestOptions);
5378
- requestPromises.set(requestOptions.roomId, currentPromise);
5611
+ requestPromises.set(requestKey, currentPromise);
5379
5612
  }
5380
5613
  } else {
5381
5614
  currentPromise = requestPromises.get("liveblocks-user-token");
@@ -5389,12 +5622,12 @@ function createAuthManager(authOptions, onAuthenticate) {
5389
5622
  const BUFFER = 30;
5390
5623
  const expiresAt = Math.floor(Date.now() / 1e3) + (token.parsed.exp - token.parsed.iat) - BUFFER;
5391
5624
  seenTokens.add(token.raw);
5392
- tokens.push(token);
5393
- expiryTimes.push(expiresAt);
5625
+ const cachedToken2 = makeCachedToken(token, expiresAt);
5626
+ tokens.push(cachedToken2);
5394
5627
  return { type: "secret", token };
5395
5628
  } finally {
5396
- if (requestOptions.roomId) {
5397
- requestPromises.delete(requestOptions.roomId);
5629
+ if (requestKey !== void 0) {
5630
+ requestPromises.delete(requestKey);
5398
5631
  } else {
5399
5632
  requestPromises.delete("liveblocks-user-token");
5400
5633
  }
@@ -5405,6 +5638,43 @@ function createAuthManager(authOptions, onAuthenticate) {
5405
5638
  getAuthValue
5406
5639
  };
5407
5640
  }
5641
+ function getAuthRequestKey(request) {
5642
+ if (request.roomId === void 0) {
5643
+ return void 0;
5644
+ }
5645
+ return `${request.roomId}:${request.resource}:${request.access}`;
5646
+ }
5647
+ function makeCachedToken(token, expiresAt) {
5648
+ if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
5649
+ return {
5650
+ token,
5651
+ expiresAt,
5652
+ permissions: Object.entries(token.parsed.perms).map(
5653
+ ([pattern, scopes]) => ({
5654
+ pattern,
5655
+ scopes: normalizeRoomPermissions(scopes)
5656
+ })
5657
+ )
5658
+ };
5659
+ }
5660
+ return { token, expiresAt };
5661
+ }
5662
+ function cachedTokenSatisfiesRequest(cachedToken, request) {
5663
+ if (cachedToken.token.parsed.k === "id" /* ID_TOKEN */) {
5664
+ return true;
5665
+ }
5666
+ if (request.resource === "personal") {
5667
+ return true;
5668
+ }
5669
+ if (request.roomId === void 0) {
5670
+ return false;
5671
+ }
5672
+ const matrix = resolveRoomPermissionMatrix(
5673
+ cachedToken.permissions ?? [],
5674
+ request.roomId
5675
+ );
5676
+ return matrix !== void 0 && hasPermissionAccess(matrix, request.resource, request.access);
5677
+ }
5408
5678
  function prepareAuthentication(authOptions) {
5409
5679
  const { publicApiKey, authEndpoint } = authOptions;
5410
5680
  if (authEndpoint !== void 0 && publicApiKey !== void 0) {
@@ -5502,6 +5772,9 @@ var OpCode = Object.freeze({
5502
5772
  function isIgnoredOp(op) {
5503
5773
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
5504
5774
  }
5775
+ function isCreateOp(op) {
5776
+ return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
5777
+ }
5505
5778
 
5506
5779
  // src/protocol/StorageNode.ts
5507
5780
  var CrdtType = Object.freeze({
@@ -5759,12 +6032,112 @@ function asPos(str) {
5759
6032
  return isPos(str) ? str : convertToPos(str);
5760
6033
  }
5761
6034
 
6035
+ // src/crdts/UnacknowledgedOps.ts
6036
+ var UnacknowledgedOps = class {
6037
+ // opId -> op
6038
+ #byOpId = /* @__PURE__ */ new Map();
6039
+ // position -> (opId -> Create op)
6040
+ #createOpsByPosition = /* @__PURE__ */ new Map();
6041
+ // parentId -> (opId -> Create op)
6042
+ #createOpsByParent = /* @__PURE__ */ new Map();
6043
+ // opIds of pending ops that were in flight when a connection died, so the
6044
+ // server may already have processed them. See isPossiblyStored().
6045
+ #possiblyStoredOpIds = /* @__PURE__ */ new Set();
6046
+ #posKey(parentId, parentKey) {
6047
+ return `${parentId}
6048
+ ${parentKey}`;
6049
+ }
6050
+ get size() {
6051
+ return this.#byOpId.size;
6052
+ }
6053
+ /**
6054
+ * Mark the given Op as still unacknowledged.
6055
+ */
6056
+ add(op) {
6057
+ this.#byOpId.set(op.opId, op);
6058
+ if (isCreateOp(op)) {
6059
+ const posKey = this.#posKey(op.parentId, op.parentKey);
6060
+ let atPosition = this.#createOpsByPosition.get(posKey);
6061
+ if (atPosition === void 0) {
6062
+ atPosition = /* @__PURE__ */ new Map();
6063
+ this.#createOpsByPosition.set(posKey, atPosition);
6064
+ }
6065
+ atPosition.set(op.opId, op);
6066
+ let inParent = this.#createOpsByParent.get(op.parentId);
6067
+ if (inParent === void 0) {
6068
+ inParent = /* @__PURE__ */ new Map();
6069
+ this.#createOpsByParent.set(op.parentId, inParent);
6070
+ }
6071
+ inParent.set(op.opId, op);
6072
+ }
6073
+ }
6074
+ /**
6075
+ * Drop the op with the given opId from the set, because the server has
6076
+ * acknowledged it (confirmed our own op, or signalled it was seen but
6077
+ * ignored).
6078
+ */
6079
+ delete(opId) {
6080
+ const op = this.#byOpId.get(opId);
6081
+ if (op === void 0) {
6082
+ return;
6083
+ }
6084
+ this.#byOpId.delete(opId);
6085
+ this.#possiblyStoredOpIds.delete(opId);
6086
+ if (isCreateOp(op)) {
6087
+ const posKey = this.#posKey(op.parentId, op.parentKey);
6088
+ const atPosition = this.#createOpsByPosition.get(posKey);
6089
+ atPosition?.delete(opId);
6090
+ if (atPosition !== void 0 && atPosition.size === 0) {
6091
+ this.#createOpsByPosition.delete(posKey);
6092
+ }
6093
+ const inParent = this.#createOpsByParent.get(op.parentId);
6094
+ inParent?.delete(opId);
6095
+ if (inParent !== void 0 && inParent.size === 0) {
6096
+ this.#createOpsByParent.delete(op.parentId);
6097
+ }
6098
+ }
6099
+ }
6100
+ /**
6101
+ * The still-unacknowledged Create ops with the given `parentId` and
6102
+ * `parentKey` (targeting one exact position), in dispatch order. O(1) lookup.
6103
+ * Empty if none.
6104
+ */
6105
+ getByParentIdAndKey(parentId, parentKey) {
6106
+ return this.#createOpsByPosition.get(this.#posKey(parentId, parentKey))?.values() ?? [];
6107
+ }
6108
+ /**
6109
+ * The still-unacknowledged Create ops with the given `parentId` (across all
6110
+ * positions), in dispatch order. O(1) lookup. Empty if none.
6111
+ */
6112
+ getByParentId(parentId) {
6113
+ return this.#createOpsByParent.get(parentId)?.values() ?? [];
6114
+ }
6115
+ /** All still-unacknowledged ops, in dispatch order. */
6116
+ values() {
6117
+ return this.#byOpId.values();
6118
+ }
6119
+ isPossiblyStored(opId) {
6120
+ return this.#possiblyStoredOpIds.has(opId);
6121
+ }
6122
+ /**
6123
+ * Mark every currently pending op as possibly stored on the server. Called
6124
+ * when the connection dies: all of these ops were in flight, and their
6125
+ * (possibly lost) acks would have been the only way to know their fate.
6126
+ */
6127
+ markAllAsPossiblyStored() {
6128
+ for (const opId of this.#byOpId.keys()) {
6129
+ this.#possiblyStoredOpIds.add(opId);
6130
+ }
6131
+ }
6132
+ };
6133
+
5762
6134
  // src/crdts/AbstractCrdt.ts
5763
6135
  function createManagedPool(roomId, options) {
5764
6136
  const {
5765
6137
  getCurrentConnectionId,
5766
6138
  onDispatch,
5767
- isStorageWritable = () => true
6139
+ isStorageWritable = () => true,
6140
+ unacknowledgedOps = new UnacknowledgedOps()
5768
6141
  } = options;
5769
6142
  let clock = 0;
5770
6143
  let opClock = 0;
@@ -5786,7 +6159,8 @@ function createManagedPool(roomId, options) {
5786
6159
  "Cannot write to storage with a read only user, please ensure the user has write permissions"
5787
6160
  );
5788
6161
  }
5789
- }
6162
+ },
6163
+ unacknowledgedOps
5790
6164
  };
5791
6165
  }
5792
6166
  function crdtAsLiveNode(value) {
@@ -6068,11 +6442,9 @@ function childNodeLt(a, b) {
6068
6442
  var LiveList = class _LiveList extends AbstractCrdt {
6069
6443
  #items;
6070
6444
  #implicitlyDeletedItems;
6071
- #unacknowledgedSets;
6072
6445
  constructor(items) {
6073
6446
  super();
6074
6447
  this.#implicitlyDeletedItems = /* @__PURE__ */ new WeakSet();
6075
- this.#unacknowledgedSets = /* @__PURE__ */ new Map();
6076
6448
  const nodes = [];
6077
6449
  let lastPos;
6078
6450
  for (const item of items) {
@@ -6102,12 +6474,13 @@ var LiveList = class _LiveList extends AbstractCrdt {
6102
6474
  }
6103
6475
  /**
6104
6476
  * @internal
6105
- * This function assumes that the resulting ops will be sent to the server if they have an 'opId'
6106
- * so we mutate _unacknowledgedSets to avoid potential flickering
6107
- * https://github.com/liveblocks/liveblocks/pull/1177
6477
+ * Serializes this list (and its children) into Create ops. Each child's
6478
+ * create is tagged with the "set" intent (in the loop below) so that a list
6479
+ * created and immediately mutated doesn't transiently re-show its initial
6480
+ * items (flicker, https://github.com/liveblocks/liveblocks/pull/1177).
6108
6481
  *
6109
- * This is quite unintuitive and should disappear as soon as
6110
- * we introduce an explicit LiveList.Set operation
6482
+ * This is quite unintuitive and should disappear as soon as we introduce an
6483
+ * explicit LiveList.Set operation.
6111
6484
  */
6112
6485
  _toOps(parentId, parentKey) {
6113
6486
  if (this._id === void 0) {
@@ -6123,7 +6496,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
6123
6496
  ops.push(op);
6124
6497
  for (const item of this.#items) {
6125
6498
  const parentKey2 = item._getParentKeyOrThrow();
6126
- const childOps = addIntentToOpTree(
6499
+ const childOps = addIntentToRootOp(
6127
6500
  item._toOps(this._id, parentKey2),
6128
6501
  "set"
6129
6502
  );
@@ -6167,6 +6540,28 @@ var LiveList = class _LiveList extends AbstractCrdt {
6167
6540
  (item) => item._getParentKeyOrThrow() === position
6168
6541
  );
6169
6542
  }
6543
+ /**
6544
+ * The opId of this list's still-unacknowledged "set" op at the given position,
6545
+ * or undefined if none. Derived from the room's unacknowledgedOps (the single
6546
+ * source of truth) rather than tracked in a per-instance map. The pool's
6547
+ * position index already scopes to this list's (parentId, position); the last
6548
+ * match wins, matching the original last-write-wins map semantics.
6549
+ */
6550
+ #unacknowledgedSetOpIdAt(position) {
6551
+ if (this._pool === void 0 || this._id === void 0) {
6552
+ return void 0;
6553
+ }
6554
+ let opId;
6555
+ for (const op of this._pool.unacknowledgedOps.getByParentIdAndKey(
6556
+ this._id,
6557
+ position
6558
+ )) {
6559
+ if (op.intent === "set") {
6560
+ opId = op.opId;
6561
+ }
6562
+ }
6563
+ return opId;
6564
+ }
6170
6565
  /** @internal */
6171
6566
  _attach(id, pool) {
6172
6567
  super._attach(id, pool);
@@ -6247,13 +6642,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
6247
6642
  if (deletedDelta) {
6248
6643
  delta.push(deletedDelta);
6249
6644
  }
6250
- const unacknowledgedOpId = this.#unacknowledgedSets.get(op.parentKey);
6251
- if (unacknowledgedOpId !== void 0) {
6252
- if (unacknowledgedOpId !== op.opId) {
6253
- return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
6254
- } else {
6255
- this.#unacknowledgedSets.delete(op.parentKey);
6256
- }
6645
+ const unacknowledgedOpId = this.#unacknowledgedSetOpIdAt(op.parentKey);
6646
+ if (unacknowledgedOpId !== void 0 && unacknowledgedOpId !== op.opId) {
6647
+ return delta.length === 0 ? { modified: false } : { modified: makeUpdate(this, delta), reverse: [] };
6257
6648
  }
6258
6649
  const indexOfItemWithSamePosition = this._indexOfPosition(op.parentKey);
6259
6650
  const existingItem = this.#items.find((item) => item._id === op.id);
@@ -6344,11 +6735,92 @@ var LiveList = class _LiveList extends AbstractCrdt {
6344
6735
  this.#shiftItemPosition(existingItemIndex, key);
6345
6736
  }
6346
6737
  const { newItem, newIndex } = this.#createAttachItemAndSort(op, key);
6738
+ const bumpDeltas = this.#bumpUnackedPushesAbove(key);
6347
6739
  return {
6348
- modified: makeUpdate(this, [insertDelta(newIndex, newItem)]),
6740
+ modified: makeUpdate(this, [
6741
+ insertDelta(newIndex, newItem),
6742
+ ...bumpDeltas
6743
+ ]),
6349
6744
  reverse: []
6350
6745
  };
6351
6746
  }
6747
+ /**
6748
+ * This list's own still-unacknowledged pushed items (their `intent: "push"`
6749
+ * Create op is still pending in the room's unacknowledgedOps). Derived from
6750
+ * the single source of truth, so an item drops out the instant its op is
6751
+ * acked, with no per-instance membership to leak. Yielded in push order.
6752
+ *
6753
+ * Excludes ops that may already be stored on the server (they were in
6754
+ * flight when a connection died, so their fate is unknown): the bump
6755
+ * prediction assumes the server has not processed the op yet, which is only
6756
+ * guaranteed for ops sent on the current connection. For these excluded
6757
+ * ops, the server's (re-)ack states the authoritative position; predicting
6758
+ * locally could produce a wrong position that no ack would correct.
6759
+ *
6760
+ * Restricted to items currently in `#items`: a pushed node whose op is still
6761
+ * pending may have been pulled out of the list (e.g. implicitly deleted by a
6762
+ * remote set, or removed by an undo) while still living in the pool, and such
6763
+ * a node must not be repositioned.
6764
+ */
6765
+ *#unackedPushNodes() {
6766
+ if (this._pool === void 0 || this._id === void 0) {
6767
+ return;
6768
+ }
6769
+ for (const op of this._pool.unacknowledgedOps.getByParentId(this._id)) {
6770
+ if (op.intent !== "push") {
6771
+ continue;
6772
+ }
6773
+ if (this._pool.unacknowledgedOps.isPossiblyStored(op.opId)) {
6774
+ continue;
6775
+ }
6776
+ const node = this._pool.getNode(op.id);
6777
+ if (node !== void 0 && this.#items.includes(node)) {
6778
+ yield node;
6779
+ }
6780
+ }
6781
+ }
6782
+ /**
6783
+ * Optimistic no-flip for pushed items. When a remote op lands at or below my
6784
+ * still-unacked pushed items, those items must end up *after* it: FIFO plus
6785
+ * the room's serial processing guarantee the remote was processed first, so
6786
+ * my unacked pushes belong behind it. Re-chain the whole unacked-push block,
6787
+ * in push order, to sit after the highest confirmed sibling, so it keeps
6788
+ * rendering as a contiguous tail instead of getting interleaved. Local-only;
6789
+ * the real acks overwrite these keys with the (identical) server keys.
6790
+ */
6791
+ #bumpUnackedPushesAbove(remoteKey) {
6792
+ const pending = new Set(this.#unackedPushNodes());
6793
+ if (pending.size === 0) {
6794
+ return [];
6795
+ }
6796
+ let minPending;
6797
+ for (const node of pending) {
6798
+ const pos = node._parentPos;
6799
+ if (minPending === void 0 || pos < minPending) {
6800
+ minPending = pos;
6801
+ }
6802
+ }
6803
+ if (remoteKey < nn(minPending)) {
6804
+ return [];
6805
+ }
6806
+ let base;
6807
+ for (const item of this.#items) {
6808
+ if (!pending.has(item)) {
6809
+ base = item._parentPos;
6810
+ }
6811
+ }
6812
+ const deltas = [];
6813
+ for (const node of pending) {
6814
+ const previousIndex = this.#items.findIndex((item) => item === node);
6815
+ base = makePosition(base);
6816
+ this.#updateItemPosition(node, base);
6817
+ const index = this.#items.findIndex((item) => item === node);
6818
+ if (index !== previousIndex) {
6819
+ deltas.push(moveDelta(previousIndex, index, node));
6820
+ }
6821
+ }
6822
+ return deltas;
6823
+ }
6352
6824
  #applyInsertAck(op) {
6353
6825
  const existingItem = this.#items.find((item) => item._id === op.id);
6354
6826
  const key = asPos(op.parentKey);
@@ -6429,7 +6901,6 @@ var LiveList = class _LiveList extends AbstractCrdt {
6429
6901
  if (this._pool?.getNode(id) !== void 0) {
6430
6902
  return { modified: false };
6431
6903
  }
6432
- this.#unacknowledgedSets.set(key, nn(op.opId));
6433
6904
  const indexOfItemWithSameKey = this._indexOfPosition(key);
6434
6905
  child._attach(id, nn(this._pool));
6435
6906
  child._setParentLink(this, key);
@@ -6439,7 +6910,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
6439
6910
  existingItem._detach();
6440
6911
  this.#items.remove(existingItem);
6441
6912
  this.#items.add(child);
6442
- const reverse = addIntentToOpTree(
6913
+ const reverse = addIntentToRootOp(
6443
6914
  existingItem._toOps(nn(this._id), key),
6444
6915
  "set",
6445
6916
  op.id
@@ -6718,7 +7189,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
6718
7189
  value._attach(id, this._pool);
6719
7190
  const ops = value._toOpsWithOpId(this._id, position, this._pool);
6720
7191
  this._pool.dispatch(
6721
- intent === "push" ? addIntentToOpTree(ops, "push") : ops,
7192
+ intent === "push" ? addIntentToRootOp(ops, "push") : ops,
6722
7193
  [{ type: OpCode.DELETE_CRDT, id }],
6723
7194
  /* @__PURE__ */ new Map([
6724
7195
  [this._id, makeUpdate(this, [insertDelta(index, value)])]
@@ -6876,13 +7347,12 @@ var LiveList = class _LiveList extends AbstractCrdt {
6876
7347
  value._attach(id, this._pool);
6877
7348
  const storageUpdates = /* @__PURE__ */ new Map();
6878
7349
  storageUpdates.set(this._id, makeUpdate(this, [setDelta(index, value)]));
6879
- const ops = addIntentToOpTree(
7350
+ const ops = addIntentToRootOp(
6880
7351
  value._toOpsWithOpId(this._id, position, this._pool),
6881
7352
  "set",
6882
7353
  existingId
6883
7354
  );
6884
- this.#unacknowledgedSets.set(position, nn(ops[0].opId));
6885
- const reverseOps = addIntentToOpTree(
7355
+ const reverseOps = addIntentToRootOp(
6886
7356
  existingItem._toOps(this._id, position),
6887
7357
  "set",
6888
7358
  id
@@ -7085,7 +7555,7 @@ function moveDelta(previousIndex, index, item) {
7085
7555
  previousIndex
7086
7556
  };
7087
7557
  }
7088
- function addIntentToOpTree(ops, intent, deletedId) {
7558
+ function addIntentToRootOp(ops, intent, deletedId) {
7089
7559
  return ops.map((op, index) => {
7090
7560
  if (index === 0) {
7091
7561
  const firstOp = op;
@@ -7741,6 +8211,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
7741
8211
  const id = nn(this._id);
7742
8212
  const parentKey = nn(child._parentKey);
7743
8213
  const reverse = child._toOps(id, parentKey);
8214
+ const deletedItem = liveNodeToLson(child);
7744
8215
  for (const [key, value] of this.#synced) {
7745
8216
  if (value === child) {
7746
8217
  this.#synced.delete(key);
@@ -7752,7 +8223,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
7752
8223
  node: this,
7753
8224
  type: "LiveObject",
7754
8225
  updates: {
7755
- [parentKey]: { type: "delete" }
8226
+ [parentKey]: { type: "delete", deletedItem }
7756
8227
  }
7757
8228
  };
7758
8229
  return { modified: storageUpdate, reverse };
@@ -8329,23 +8800,93 @@ function lsonToLiveNode(value) {
8329
8800
  return new LiveRegister(value);
8330
8801
  }
8331
8802
  }
8332
- function getTreesDiffOperations(currentItems, newItems) {
8803
+ function dumpPool(pool) {
8804
+ const rows = Array.from(pool.nodes.values(), (node) => {
8805
+ const parent = node.parent;
8806
+ const parentId = parent.type === "HasParent" ? parent.node._id ?? "?" : parent.type === "Orphaned" ? "<orphaned>" : "-";
8807
+ let value;
8808
+ if (node instanceof LiveRegister) {
8809
+ value = stringifyOrLog(node.data);
8810
+ } else if (node instanceof LiveList) {
8811
+ value = "<LiveList>";
8812
+ } else if (node instanceof LiveMap) {
8813
+ value = "<LiveMap>";
8814
+ } else {
8815
+ value = "<LiveObject>";
8816
+ }
8817
+ return { id: nn(node._id), parentId, key: node._parentKey ?? "", value };
8818
+ });
8819
+ rows.sort((a, b) => {
8820
+ if (a.parentId !== b.parentId) return a.parentId < b.parentId ? -1 : 1;
8821
+ if (a.key !== b.key) return a.key < b.key ? -1 : 1;
8822
+ return 0;
8823
+ });
8824
+ return rows.map(
8825
+ (r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
8826
+ ).join("\n");
8827
+ }
8828
+ function isJsonEq(a, b) {
8829
+ if (a === b) {
8830
+ return true;
8831
+ }
8832
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
8833
+ return false;
8834
+ }
8835
+ if (Array.isArray(a) || Array.isArray(b)) {
8836
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
8837
+ return false;
8838
+ }
8839
+ for (let i = 0; i < a.length; i++) {
8840
+ if (!isJsonEq(a[i], b[i])) {
8841
+ return false;
8842
+ }
8843
+ }
8844
+ return true;
8845
+ }
8846
+ const aKeys = Object.keys(a);
8847
+ if (aKeys.length !== Object.keys(b).length) {
8848
+ return false;
8849
+ }
8850
+ for (const key of aKeys) {
8851
+ if (!isJsonEq(a[key], b[key])) {
8852
+ return false;
8853
+ }
8854
+ }
8855
+ return true;
8856
+ }
8857
+ function diffNodeMap(prev, next) {
8333
8858
  const ops = [];
8334
- currentItems.forEach((_, id) => {
8335
- if (!newItems.get(id)) {
8859
+ prev.forEach((_, id) => {
8860
+ if (!next.get(id)) {
8336
8861
  ops.push({ type: OpCode.DELETE_CRDT, id });
8337
8862
  }
8338
8863
  });
8339
- newItems.forEach((crdt, id) => {
8340
- const currentCrdt = currentItems.get(id);
8864
+ next.forEach((crdt, id) => {
8865
+ const currentCrdt = prev.get(id);
8341
8866
  if (currentCrdt) {
8342
8867
  if (crdt.type === CrdtType.OBJECT) {
8343
- if (currentCrdt.type !== CrdtType.OBJECT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data)) {
8344
- ops.push({
8345
- type: OpCode.UPDATE_OBJECT,
8346
- id,
8347
- data: crdt.data
8348
- });
8868
+ if (currentCrdt.type !== CrdtType.OBJECT) {
8869
+ ops.push({ type: OpCode.UPDATE_OBJECT, id, data: crdt.data });
8870
+ } else {
8871
+ const changed = /* @__PURE__ */ new Map();
8872
+ for (const key of Object.keys(crdt.data)) {
8873
+ const value = crdt.data[key];
8874
+ if (value !== void 0 && !isJsonEq(value, currentCrdt.data[key])) {
8875
+ changed.set(key, value);
8876
+ }
8877
+ }
8878
+ if (changed.size > 0) {
8879
+ ops.push({
8880
+ type: OpCode.UPDATE_OBJECT,
8881
+ id,
8882
+ data: Object.fromEntries(changed)
8883
+ });
8884
+ }
8885
+ for (const key of Object.keys(currentCrdt.data)) {
8886
+ if (!(key in crdt.data)) {
8887
+ ops.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });
8888
+ }
8889
+ }
8349
8890
  }
8350
8891
  }
8351
8892
  if (crdt.parentKey !== currentCrdt.parentKey) {
@@ -8568,7 +9109,7 @@ function partialSyncConnection(room) {
8568
9109
  });
8569
9110
  }
8570
9111
  function partialSyncStorage(room) {
8571
- const root = room.getStorageSnapshot();
9112
+ const root = room.getStorageOrNull();
8572
9113
  if (root) {
8573
9114
  sendToPanel({
8574
9115
  msg: "room::sync::partial",
@@ -8598,7 +9139,7 @@ function partialSyncOthers(room) {
8598
9139
  }
8599
9140
  }
8600
9141
  function fullSync(room) {
8601
- const root = room.getStorageSnapshot();
9142
+ const root = room.getStorageOrNull();
8602
9143
  const me = room[kInternal].getSelf_forDevTools();
8603
9144
  const others = room[kInternal].getOthers_forDevTools();
8604
9145
  room.fetchYDoc("");
@@ -9023,15 +9564,15 @@ var ClientMsgCode = Object.freeze({
9023
9564
 
9024
9565
  // src/refs/ManagedOthers.ts
9025
9566
  function makeUser(conn, presence) {
9026
- const { connectionId, id, info } = conn;
9027
- const canWrite = canWriteStorage(conn.scopes);
9567
+ const { connectionId, id, info, access } = conn;
9568
+ const { canWrite, canComment } = access;
9028
9569
  return freeze(
9029
9570
  compactObject({
9030
9571
  connectionId,
9031
9572
  id,
9032
9573
  info,
9033
9574
  canWrite,
9034
- canComment: canComment(conn.scopes),
9575
+ canComment,
9035
9576
  isReadOnly: !canWrite,
9036
9577
  // Deprecated, kept for backward-compatibility
9037
9578
  presence
@@ -9102,7 +9643,7 @@ var ManagedOthers = class {
9102
9643
  * Records a known connection. This records the connection ID and the
9103
9644
  * associated metadata.
9104
9645
  */
9105
- setConnection(connectionId, metaUserId, metaUserInfo, scopes) {
9646
+ setConnection(connectionId, metaUserId, metaUserInfo, access) {
9106
9647
  this.#internal.mutate((state) => {
9107
9648
  state.connections.set(
9108
9649
  connectionId,
@@ -9110,7 +9651,7 @@ var ManagedOthers = class {
9110
9651
  connectionId,
9111
9652
  id: metaUserId,
9112
9653
  info: metaUserInfo,
9113
- scopes
9654
+ access
9114
9655
  })
9115
9656
  );
9116
9657
  if (!state.presences.has(connectionId)) {
@@ -9263,6 +9804,14 @@ function defaultMessageFromContext(context) {
9263
9804
 
9264
9805
  // src/room.ts
9265
9806
  var FEEDS_TIMEOUT = 5e3;
9807
+ function connectionAccessFromScopes(scopes) {
9808
+ const roomPermissions = normalizeRoomPermissions(scopes);
9809
+ const matrix = permissionMatrixFromScopes(roomPermissions);
9810
+ return {
9811
+ canWrite: hasPermissionAccess(matrix, "storage", "write"),
9812
+ canComment: hasPermissionAccess(matrix, "comments", "write")
9813
+ };
9814
+ }
9266
9815
  function makeIdFactory(connectionId) {
9267
9816
  let count = 0;
9268
9817
  return () => `${connectionId}:${count++}`;
@@ -9338,6 +9887,7 @@ function createRoom(options, config) {
9338
9887
  delegates,
9339
9888
  config.enableDebugLogging
9340
9889
  );
9890
+ const unacknowledgedOps = new UnacknowledgedOps();
9341
9891
  const context = {
9342
9892
  buffer: {
9343
9893
  flushTimerID: void 0,
@@ -9365,14 +9915,15 @@ function createRoom(options, config) {
9365
9915
  pool: createManagedPool(roomId, {
9366
9916
  getCurrentConnectionId,
9367
9917
  onDispatch,
9368
- isStorageWritable
9918
+ isStorageWritable,
9919
+ unacknowledgedOps
9369
9920
  }),
9370
9921
  root: void 0,
9371
9922
  undoStack: [],
9372
9923
  redoStack: [],
9373
9924
  pausedHistory: null,
9374
9925
  activeBatch: null,
9375
- unacknowledgedOps: /* @__PURE__ */ new Map()
9926
+ unacknowledgedOps
9376
9927
  };
9377
9928
  const nodeMapBuffer = makeNodeMapBuffer();
9378
9929
  const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
@@ -9433,12 +9984,13 @@ function createRoom(options, config) {
9433
9984
  )
9434
9985
  };
9435
9986
  if (_getStorage$ !== null) {
9436
- refreshStorage({ flush: false });
9987
+ refreshStorage();
9437
9988
  }
9438
9989
  flushNowOrSoon();
9439
9990
  }
9440
9991
  function onDidDisconnect() {
9441
9992
  clearTimeout(context.buffer.flushTimerID);
9993
+ context.unacknowledgedOps.markAllAsPossiblyStored();
9442
9994
  }
9443
9995
  managedSocket.events.onMessage.subscribe(handleServerMessage);
9444
9996
  managedSocket.events.statusDidChange.subscribe(onStatusDidChange);
@@ -9484,8 +10036,8 @@ function createRoom(options, config) {
9484
10036
  }
9485
10037
  }
9486
10038
  function isStorageWritable() {
9487
- const scopes = context.dynamicSessionInfoSig.get()?.scopes;
9488
- return scopes !== void 0 ? canWriteStorage(scopes) : true;
10039
+ const permissionMatrix = context.dynamicSessionInfoSig.get()?.permissionMatrix;
10040
+ return permissionMatrix !== void 0 ? hasPermissionAccess(permissionMatrix, "storage", "write") : true;
9489
10041
  }
9490
10042
  const eventHub = {
9491
10043
  status: makeEventSource(),
@@ -9546,14 +10098,22 @@ function createRoom(options, config) {
9546
10098
  if (staticSession === null || dynamicSession === null) {
9547
10099
  return null;
9548
10100
  } else {
9549
- const canWrite = canWriteStorage(dynamicSession.scopes);
10101
+ const canWrite = hasPermissionAccess(
10102
+ dynamicSession.permissionMatrix,
10103
+ "storage",
10104
+ "write"
10105
+ );
9550
10106
  return {
9551
10107
  connectionId: dynamicSession.actor,
9552
10108
  id: staticSession.userId,
9553
10109
  info: staticSession.userInfo,
9554
10110
  presence: myPresence,
9555
10111
  canWrite,
9556
- canComment: canComment(dynamicSession.scopes)
10112
+ canComment: hasPermissionAccess(
10113
+ dynamicSession.permissionMatrix,
10114
+ "comments",
10115
+ "write"
10116
+ )
9557
10117
  };
9558
10118
  }
9559
10119
  }
@@ -9579,7 +10139,7 @@ function createRoom(options, config) {
9579
10139
  for (const [id, crdt] of context.pool.nodes) {
9580
10140
  currentItems.set(id, crdt._serialize());
9581
10141
  }
9582
- const ops = getTreesDiffOperations(currentItems, nodes);
10142
+ const ops = diffNodeMap(currentItems, nodes);
9583
10143
  const result = applyRemoteOps(ops);
9584
10144
  notify(result.updates);
9585
10145
  } else {
@@ -9842,7 +10402,9 @@ function createRoom(options, config) {
9842
10402
  context.dynamicSessionInfoSig.set({
9843
10403
  actor: message.actor,
9844
10404
  nonce: message.nonce,
9845
- scopes: message.scopes,
10405
+ permissionMatrix: permissionMatrixFromScopes(
10406
+ normalizeRoomPermissions(message.scopes)
10407
+ ),
9846
10408
  meta: message.meta
9847
10409
  });
9848
10410
  context.idFactory = makeIdFactory(message.actor);
@@ -9863,7 +10425,7 @@ function createRoom(options, config) {
9863
10425
  connectionId,
9864
10426
  user.id,
9865
10427
  user.info,
9866
- user.scopes
10428
+ connectionAccessFromScopes(user.scopes)
9867
10429
  );
9868
10430
  }
9869
10431
  return { type: "reset" };
@@ -9883,7 +10445,7 @@ function createRoom(options, config) {
9883
10445
  message.actor,
9884
10446
  message.id,
9885
10447
  message.info,
9886
- message.scopes
10448
+ connectionAccessFromScopes(message.scopes)
9887
10449
  );
9888
10450
  context.buffer.messages.push({
9889
10451
  type: ClientMsgCode.UPDATE_PRESENCE,
@@ -9911,12 +10473,11 @@ function createRoom(options, config) {
9911
10473
  }
9912
10474
  }
9913
10475
  function applyAndSendOfflineOps(unackedOps) {
9914
- if (unackedOps.size === 0) {
10476
+ if (unackedOps.length === 0) {
9915
10477
  return;
9916
10478
  }
9917
10479
  const messages = [];
9918
- const inOps = Array.from(unackedOps.values());
9919
- const result = applyLocalOps(inOps);
10480
+ const result = applyLocalOps(unackedOps);
9920
10481
  messages.push({
9921
10482
  type: ClientMsgCode.UPDATE_STORAGE,
9922
10483
  ops: result.opsToEmit
@@ -10140,7 +10701,7 @@ function createRoom(options, config) {
10140
10701
  const storageOps = context.buffer.storageOperations;
10141
10702
  if (storageOps.length > 0) {
10142
10703
  for (const op of storageOps) {
10143
- context.unacknowledgedOps.set(op.opId, op);
10704
+ context.unacknowledgedOps.add(op);
10144
10705
  }
10145
10706
  notifyStorageStatus();
10146
10707
  }
@@ -10367,36 +10928,25 @@ function createRoom(options, config) {
10367
10928
  }
10368
10929
  }
10369
10930
  function processInitialStorage(nodes) {
10370
- const unacknowledgedOps = new Map(context.unacknowledgedOps);
10931
+ const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
10371
10932
  createOrUpdateRootFromMessage(nodes);
10372
- applyAndSendOfflineOps(unacknowledgedOps);
10933
+ applyAndSendOfflineOps(unacknowledgedOps2);
10373
10934
  _resolveStoragePromise?.();
10374
10935
  notifyStorageStatus();
10375
10936
  eventHub.storageDidLoad.notify();
10376
10937
  }
10377
- async function streamStorage() {
10378
- if (!managedSocket.authValue) return;
10379
- const nodes = new Map(
10380
- await httpClient.streamStorage({ roomId })
10381
- );
10382
- processInitialStorage(nodes);
10383
- }
10384
- function refreshStorage(options2) {
10938
+ function refreshStorage() {
10385
10939
  const messages = context.buffer.messages;
10386
- if (config.unstable_streamData) {
10387
- void streamStorage();
10388
- } else if (!messages.some((msg) => msg.type === ClientMsgCode.FETCH_STORAGE)) {
10940
+ if (!messages.some((msg) => msg.type === ClientMsgCode.FETCH_STORAGE)) {
10389
10941
  messages.push({ type: ClientMsgCode.FETCH_STORAGE });
10390
10942
  nodeMapBuffer.take();
10391
10943
  stopwatch?.start();
10392
10944
  }
10393
- if (options2.flush) {
10394
- flushNowOrSoon();
10395
- }
10396
10945
  }
10397
10946
  function startLoadingStorage() {
10398
10947
  if (_getStorage$ === null) {
10399
- refreshStorage({ flush: true });
10948
+ refreshStorage();
10949
+ flushNowOrSoon();
10400
10950
  _getStorage$ = new Promise((resolve) => {
10401
10951
  _resolveStoragePromise = resolve;
10402
10952
  });
@@ -10404,7 +10954,7 @@ function createRoom(options, config) {
10404
10954
  }
10405
10955
  return _getStorage$;
10406
10956
  }
10407
- function getStorageSnapshot() {
10957
+ function getStorageOrNull() {
10408
10958
  const root = context.root;
10409
10959
  if (root !== void 0) {
10410
10960
  return root;
@@ -10718,7 +11268,7 @@ function createRoom(options, config) {
10718
11268
  }
10719
11269
  }
10720
11270
  function isStorageReady() {
10721
- return getStorageSnapshot() !== null;
11271
+ return getStorageOrNull() !== null;
10722
11272
  }
10723
11273
  async function waitUntilStorageReady() {
10724
11274
  while (!isStorageReady()) {
@@ -10958,6 +11508,13 @@ function createRoom(options, config) {
10958
11508
  connect: () => managedSocket.connect(),
10959
11509
  reconnect: () => managedSocket.reconnect(),
10960
11510
  disconnect: () => managedSocket.disconnect(),
11511
+ _dump: () => {
11512
+ const n = context.pool.nodes.size;
11513
+ return `Room "${roomId}" (${n} node${n === 1 ? "" : "s"}):
11514
+ ${dumpPool(
11515
+ context.pool
11516
+ )}`;
11517
+ },
10961
11518
  destroy: () => {
10962
11519
  pendingFeedsRequests.forEach(
10963
11520
  (request) => request.reject(new Error("Room destroyed"))
@@ -11003,7 +11560,9 @@ function createRoom(options, config) {
11003
11560
  updateFeedMessage,
11004
11561
  deleteFeedMessage,
11005
11562
  getStorage,
11006
- getStorageSnapshot,
11563
+ getStorageOrNull,
11564
+ getStorageSnapshot: getStorageOrNull,
11565
+ // Deprecated alias, will be removed in the future
11007
11566
  getStorageStatus,
11008
11567
  isPresenceReady,
11009
11568
  isStorageReady,
@@ -11149,7 +11708,11 @@ function isRoomEventName(value) {
11149
11708
  }
11150
11709
  function makeAuthDelegateForRoom(roomId, authManager) {
11151
11710
  return async () => {
11152
- return authManager.getAuthValue({ requestedScope: "room:read", roomId });
11711
+ return authManager.getAuthValue({
11712
+ roomId,
11713
+ resource: "room",
11714
+ access: "read"
11715
+ });
11153
11716
  };
11154
11717
  }
11155
11718
  function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
@@ -11221,7 +11784,6 @@ function createClient(options) {
11221
11784
  const httpClient = createApiClient({
11222
11785
  baseUrl,
11223
11786
  fetchPolyfill,
11224
- currentUserId,
11225
11787
  authManager
11226
11788
  });
11227
11789
  const roomsById = /* @__PURE__ */ new Map();
@@ -11239,7 +11801,8 @@ function createClient(options) {
11239
11801
  ),
11240
11802
  authenticate: async () => {
11241
11803
  const resp = await authManager.getAuthValue({
11242
- requestedScope: "room:read"
11804
+ resource: "personal",
11805
+ access: "write"
11243
11806
  });
11244
11807
  if (resp.type === "public") {
11245
11808
  throw new StopRetrying(
@@ -11312,7 +11875,6 @@ function createClient(options) {
11312
11875
  enableDebugLogging: clientOptions.enableDebugLogging,
11313
11876
  baseUrl,
11314
11877
  errorEventSource: liveblocksErrorSource,
11315
- unstable_streamData: !!clientOptions.unstable_streamData,
11316
11878
  roomHttpClient: httpClient,
11317
11879
  createSyncSource,
11318
11880
  badgeLocation: clientOptions.badgeLocation ?? "bottom-right"
@@ -11465,6 +12027,7 @@ function createClient(options) {
11465
12027
  {
11466
12028
  enterRoom,
11467
12029
  getRoom,
12030
+ _dump: () => Array.from(roomsById.values(), ({ room }) => room._dump()).join("\n\n"),
11468
12031
  logout,
11469
12032
  // Public inbox notifications API
11470
12033
  getInboxNotifications: httpClient.getInboxNotifications,
@@ -12135,6 +12698,7 @@ export {
12135
12698
  createManagedPool,
12136
12699
  createNotificationSettings,
12137
12700
  createThreadId,
12701
+ deepLiveify,
12138
12702
  defineAiTool,
12139
12703
  deprecate,
12140
12704
  deprecateIf,
@@ -12146,6 +12710,7 @@ export {
12146
12710
  generateUrl,
12147
12711
  getMentionsFromCommentBody,
12148
12712
  getSubscriptionKey,
12713
+ hasPermissionAccess,
12149
12714
  html,
12150
12715
  htmlSafe,
12151
12716
  isCommentBodyLink,
@@ -12173,11 +12738,16 @@ export {
12173
12738
  makePosition,
12174
12739
  mapValues,
12175
12740
  memoizeOnSuccess,
12741
+ mergeRoomPermissionScopes,
12176
12742
  nanoid,
12177
12743
  nn,
12178
12744
  nodeStreamToCompactNodes,
12745
+ normalizeRoomAccesses,
12746
+ normalizeRoomPermissions,
12747
+ normalizeUpdateRoomAccesses,
12179
12748
  objectToQuery,
12180
12749
  patchNotificationSettings,
12750
+ permissionMatrixFromScopes,
12181
12751
  raise,
12182
12752
  resolveMentionsInCommentBody,
12183
12753
  sanitizeUrl,
@@ -12190,6 +12760,7 @@ export {
12190
12760
  tryParseJson,
12191
12761
  url,
12192
12762
  urljoin,
12763
+ validatePermissionsSet,
12193
12764
  wait,
12194
12765
  warnOnce,
12195
12766
  warnOnceIf,