@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.cjs +994 -423
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -126
- package/dist/index.d.ts +228 -126
- package/dist/index.js +891 -320
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
1568
|
-
|
|
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
|
-
|
|
1607
|
-
|
|
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
|
-
|
|
1652
|
-
|
|
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
|
-
|
|
1674
|
-
|
|
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
|
-
|
|
1694
|
-
|
|
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
|
-
|
|
1703
|
-
|
|
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
|
-
|
|
1730
|
-
|
|
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
|
-
|
|
1740
|
-
|
|
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
|
-
|
|
1751
|
-
|
|
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
|
-
|
|
1767
|
-
|
|
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
|
-
|
|
1782
|
-
|
|
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
|
-
|
|
1791
|
-
|
|
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
|
-
|
|
1802
|
-
|
|
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
|
-
|
|
1811
|
-
|
|
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
|
-
|
|
1820
|
-
|
|
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
|
-
|
|
1829
|
-
|
|
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
|
-
|
|
1839
|
-
|
|
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
|
-
|
|
1896
|
-
|
|
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
|
-
|
|
1914
|
-
|
|
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
|
-
|
|
1940
|
-
|
|
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
|
-
|
|
1964
|
-
|
|
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
|
-
|
|
1976
|
-
|
|
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
|
-
|
|
1994
|
-
|
|
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
|
-
|
|
2115
|
-
|
|
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
|
-
|
|
2128
|
-
|
|
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
|
-
|
|
2141
|
-
|
|
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
|
-
|
|
2162
|
-
|
|
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
|
-
|
|
2177
|
-
|
|
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
|
-
|
|
2186
|
-
|
|
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
|
-
|
|
2195
|
-
|
|
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
|
-
|
|
2204
|
-
|
|
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
|
-
|
|
2217
|
-
|
|
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
|
-
|
|
2240
|
-
|
|
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
|
-
|
|
2258
|
-
|
|
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
|
-
|
|
2278
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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({
|
|
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/
|
|
5223
|
-
var Permission =
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
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
|
|
5296
|
-
|
|
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 (
|
|
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
|
-
|
|
5375
|
-
|
|
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(
|
|
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
|
-
|
|
5393
|
-
|
|
5625
|
+
const cachedToken2 = makeCachedToken(token, expiresAt);
|
|
5626
|
+
tokens.push(cachedToken2);
|
|
5394
5627
|
return { type: "secret", token };
|
|
5395
5628
|
} finally {
|
|
5396
|
-
if (
|
|
5397
|
-
requestPromises.delete(
|
|
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
|
-
*
|
|
6106
|
-
*
|
|
6107
|
-
*
|
|
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
|
-
*
|
|
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 =
|
|
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.#
|
|
6251
|
-
if (unacknowledgedOpId !== void 0) {
|
|
6252
|
-
|
|
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, [
|
|
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 =
|
|
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" ?
|
|
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 =
|
|
7350
|
+
const ops = addIntentToRootOp(
|
|
6880
7351
|
value._toOpsWithOpId(this._id, position, this._pool),
|
|
6881
7352
|
"set",
|
|
6882
7353
|
existingId
|
|
6883
7354
|
);
|
|
6884
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
8335
|
-
if (!
|
|
8859
|
+
prev.forEach((_, id) => {
|
|
8860
|
+
if (!next.get(id)) {
|
|
8336
8861
|
ops.push({ type: OpCode.DELETE_CRDT, id });
|
|
8337
8862
|
}
|
|
8338
8863
|
});
|
|
8339
|
-
|
|
8340
|
-
const currentCrdt =
|
|
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
|
|
8344
|
-
ops.push({
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
9488
|
-
return
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
10476
|
+
if (unackedOps.length === 0) {
|
|
9915
10477
|
return;
|
|
9916
10478
|
}
|
|
9917
10479
|
const messages = [];
|
|
9918
|
-
const
|
|
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.
|
|
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
|
|
10931
|
+
const unacknowledgedOps2 = [...context.unacknowledgedOps.values()];
|
|
10371
10932
|
createOrUpdateRootFromMessage(nodes);
|
|
10372
|
-
applyAndSendOfflineOps(
|
|
10933
|
+
applyAndSendOfflineOps(unacknowledgedOps2);
|
|
10373
10934
|
_resolveStoragePromise?.();
|
|
10374
10935
|
notifyStorageStatus();
|
|
10375
10936
|
eventHub.storageDidLoad.notify();
|
|
10376
10937
|
}
|
|
10377
|
-
|
|
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 (
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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,
|