canvu-react 0.3.9 → 0.3.11
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/asset-hydration-3Iv5xHxM.d.cts +27 -0
- package/dist/asset-hydration-BEG21hMp.d.ts +27 -0
- package/dist/chatbot.d.cts +2 -2
- package/dist/chatbot.d.ts +2 -2
- package/dist/index.cjs +469 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +468 -9
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +645 -704
- package/dist/native.cjs.map +1 -1
- package/dist/native.js +645 -704
- package/dist/native.js.map +1 -1
- package/dist/react.cjs +358 -141
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +9 -16
- package/dist/react.d.ts +9 -16
- package/dist/react.js +358 -142
- package/dist/react.js.map +1 -1
- package/dist/realtime.cjs +541 -38
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +40 -5
- package/dist/realtime.d.ts +40 -5
- package/dist/realtime.js +541 -39
- package/dist/realtime.js.map +1 -1
- package/dist/{shape-builders-DxPoOecg.d.cts → shape-builders-DFudWDFI.d.cts} +111 -1
- package/dist/{shape-builders-DTYvub8W.d.ts → shape-builders-ENwnK-zT.d.ts} +111 -1
- package/dist/{types-DgEArHkA.d.ts → types-BtAJFS_-.d.ts} +1 -0
- package/dist/{types-BtLGGw0r.d.cts → types-CTyASYIm.d.cts} +2 -105
- package/dist/{types-B58i5k-u.d.cts → types-DNwjgs5U.d.cts} +1 -0
- package/dist/{types-ChnTSRSe.d.ts → types-UvUy2Eed.d.ts} +2 -105
- package/package.json +1 -1
package/dist/realtime.js
CHANGED
|
@@ -100,7 +100,7 @@ function PresenceRemoteLayer({
|
|
|
100
100
|
const rootTransform = formatCameraTransform(camera);
|
|
101
101
|
const overlayStrokePx = 1.25;
|
|
102
102
|
const LUCIDE_POINTER_VIEWBOX = 24;
|
|
103
|
-
const remoteCursorScreenPx =
|
|
103
|
+
const remoteCursorScreenPx = 22;
|
|
104
104
|
const iconWorldScale = remoteCursorScreenPx / (LUCIDE_POINTER_VIEWBOX * z);
|
|
105
105
|
return /* @__PURE__ */ jsx(
|
|
106
106
|
"svg",
|
|
@@ -164,9 +164,9 @@ function PresenceRemoteLayer({
|
|
|
164
164
|
let cursorNode = null;
|
|
165
165
|
if (cur) {
|
|
166
166
|
const displayName = peer.displayName;
|
|
167
|
-
const labelOffsetX =
|
|
168
|
-
const labelOffsetY =
|
|
169
|
-
const labelFont =
|
|
167
|
+
const labelOffsetX = 14 / z;
|
|
168
|
+
const labelOffsetY = 14 / z;
|
|
169
|
+
const labelFont = 12 / z;
|
|
170
170
|
cursorNode = /* @__PURE__ */ jsxs("g", { children: [
|
|
171
171
|
/* @__PURE__ */ jsx(
|
|
172
172
|
"g",
|
|
@@ -1742,7 +1742,7 @@ function useRealtimePeerFollow(options) {
|
|
|
1742
1742
|
const { viewportRef, sessionPeers, followedPeerId, onFollowEnd } = options;
|
|
1743
1743
|
const endedPeerIdRef = useRef(null);
|
|
1744
1744
|
const lastAppliedCameraKeyRef = useRef(null);
|
|
1745
|
-
const [
|
|
1745
|
+
const [, setViewportSizeVersion] = useState(0);
|
|
1746
1746
|
useEffect(() => {
|
|
1747
1747
|
if (!followedPeerId) return;
|
|
1748
1748
|
let animationFrameId = 0;
|
|
@@ -1794,16 +1794,27 @@ function useRealtimePeerFollow(options) {
|
|
|
1794
1794
|
return;
|
|
1795
1795
|
}
|
|
1796
1796
|
lastAppliedCameraKeyRef.current = nextCameraKey;
|
|
1797
|
-
}, [
|
|
1798
|
-
followedPeerId,
|
|
1799
|
-
onFollowEnd,
|
|
1800
|
-
sessionPeers,
|
|
1801
|
-
viewportRef,
|
|
1802
|
-
viewportSizeVersion
|
|
1803
|
-
]);
|
|
1797
|
+
}, [followedPeerId, onFollowEnd, sessionPeers, viewportRef]);
|
|
1804
1798
|
}
|
|
1805
1799
|
|
|
1806
1800
|
// src/react/plugins/realtime/use-realtime-session.ts
|
|
1801
|
+
var DRAFT_STORAGE_PREFIX = "canvu-realtime-draft:";
|
|
1802
|
+
var DOCUMENT_FLUSH_DEBOUNCE_MS = 120;
|
|
1803
|
+
var DRAFT_PERSIST_DEBOUNCE_MS = 420;
|
|
1804
|
+
function requestWindowIdleCallback(callback, timeout) {
|
|
1805
|
+
if (typeof window.requestIdleCallback === "function") {
|
|
1806
|
+
return window.requestIdleCallback(callback, { timeout });
|
|
1807
|
+
}
|
|
1808
|
+
return window.setTimeout(callback, timeout);
|
|
1809
|
+
}
|
|
1810
|
+
function cancelWindowIdleCallback(handle) {
|
|
1811
|
+
if (handle == null) return;
|
|
1812
|
+
if (typeof window.cancelIdleCallback === "function") {
|
|
1813
|
+
window.cancelIdleCallback(handle);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
window.clearTimeout(handle);
|
|
1817
|
+
}
|
|
1807
1818
|
function createClientId() {
|
|
1808
1819
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1809
1820
|
return crypto.randomUUID();
|
|
@@ -1811,6 +1822,7 @@ function createClientId() {
|
|
|
1811
1822
|
return `client-${Math.random().toString(36).slice(2, 10)}`;
|
|
1812
1823
|
}
|
|
1813
1824
|
function normalizeSocketUrl(input) {
|
|
1825
|
+
if (!input) return "";
|
|
1814
1826
|
if (input.startsWith("ws://") || input.startsWith("wss://")) return input;
|
|
1815
1827
|
if (input.startsWith("http://")) return `ws://${input.slice("http://".length)}`;
|
|
1816
1828
|
if (input.startsWith("https://")) return `wss://${input.slice("https://".length)}`;
|
|
@@ -1829,9 +1841,29 @@ function isValidSocketUrl(input) {
|
|
|
1829
1841
|
return false;
|
|
1830
1842
|
}
|
|
1831
1843
|
}
|
|
1844
|
+
function sanitizeRealtimeItem(item) {
|
|
1845
|
+
if (item.toolKind !== "image") return item;
|
|
1846
|
+
return {
|
|
1847
|
+
...item,
|
|
1848
|
+
imageBlobId: void 0,
|
|
1849
|
+
imageThumbnailBlobId: void 0,
|
|
1850
|
+
imageRasterHref: void 0,
|
|
1851
|
+
imageThumbnailHref: void 0,
|
|
1852
|
+
childrenSvg: ""
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
function sanitizeRealtimeItems(items) {
|
|
1856
|
+
return items.map(sanitizeRealtimeItem);
|
|
1857
|
+
}
|
|
1858
|
+
function sanitizeRealtimeSnapshot(snapshot) {
|
|
1859
|
+
return {
|
|
1860
|
+
...snapshot,
|
|
1861
|
+
items: sanitizeRealtimeItems(snapshot.items)
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1832
1864
|
function serializeItems(items) {
|
|
1833
1865
|
try {
|
|
1834
|
-
return JSON.stringify(items);
|
|
1866
|
+
return JSON.stringify(sanitizeRealtimeItems(items));
|
|
1835
1867
|
} catch {
|
|
1836
1868
|
return null;
|
|
1837
1869
|
}
|
|
@@ -1846,6 +1878,58 @@ function sameSerializedItems(left, right) {
|
|
|
1846
1878
|
function nowMs() {
|
|
1847
1879
|
return Date.now();
|
|
1848
1880
|
}
|
|
1881
|
+
function draftStorageKey(roomId) {
|
|
1882
|
+
return `${DRAFT_STORAGE_PREFIX}${roomId}`;
|
|
1883
|
+
}
|
|
1884
|
+
function isRealtimeOfflineDraft(value) {
|
|
1885
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1886
|
+
const record = value;
|
|
1887
|
+
return typeof record.roomId === "string" && typeof record.baseRevision === "number" && typeof record.updatedAt === "number" && Array.isArray(record.items);
|
|
1888
|
+
}
|
|
1889
|
+
function readRealtimeOfflineDraft(roomId) {
|
|
1890
|
+
if (typeof window === "undefined" || !roomId) return null;
|
|
1891
|
+
try {
|
|
1892
|
+
const raw = window.localStorage.getItem(draftStorageKey(roomId));
|
|
1893
|
+
if (!raw) return null;
|
|
1894
|
+
const parsed = JSON.parse(raw);
|
|
1895
|
+
if (!isRealtimeOfflineDraft(parsed)) return null;
|
|
1896
|
+
return {
|
|
1897
|
+
...parsed,
|
|
1898
|
+
items: sanitizeRealtimeItems(parsed.items)
|
|
1899
|
+
};
|
|
1900
|
+
} catch {
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function writeRealtimeOfflineDraft(draft) {
|
|
1905
|
+
if (typeof window === "undefined") return;
|
|
1906
|
+
if (!draft) return;
|
|
1907
|
+
try {
|
|
1908
|
+
window.localStorage.setItem(
|
|
1909
|
+
draftStorageKey(draft.roomId),
|
|
1910
|
+
JSON.stringify({
|
|
1911
|
+
...draft,
|
|
1912
|
+
items: sanitizeRealtimeItems(draft.items)
|
|
1913
|
+
})
|
|
1914
|
+
);
|
|
1915
|
+
} catch {
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
function removeRealtimeOfflineDraft(roomId) {
|
|
1919
|
+
if (typeof window === "undefined" || !roomId) return;
|
|
1920
|
+
try {
|
|
1921
|
+
window.localStorage.removeItem(draftStorageKey(roomId));
|
|
1922
|
+
} catch {
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
function buildDraftSnapshot(draft, clientId) {
|
|
1926
|
+
return {
|
|
1927
|
+
revision: draft.baseRevision,
|
|
1928
|
+
items: draft.items,
|
|
1929
|
+
updatedAt: draft.updatedAt,
|
|
1930
|
+
updatedByClientId: clientId
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1849
1933
|
function getViewportCameraSnapshot(viewport) {
|
|
1850
1934
|
if (!viewport) return null;
|
|
1851
1935
|
const camera = viewport.getCamera();
|
|
@@ -1859,6 +1943,17 @@ function getViewportCameraSnapshot(viewport) {
|
|
|
1859
1943
|
viewportHeight: viewportSize.height
|
|
1860
1944
|
};
|
|
1861
1945
|
}
|
|
1946
|
+
function prepareRealtimeItems(items) {
|
|
1947
|
+
try {
|
|
1948
|
+
const sanitizedItems = sanitizeRealtimeItems(items);
|
|
1949
|
+
return {
|
|
1950
|
+
items: sanitizedItems,
|
|
1951
|
+
serialized: JSON.stringify(sanitizedItems)
|
|
1952
|
+
};
|
|
1953
|
+
} catch {
|
|
1954
|
+
return null;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1862
1957
|
function useRealtimeSession(options) {
|
|
1863
1958
|
const {
|
|
1864
1959
|
url,
|
|
@@ -1877,6 +1972,10 @@ function useRealtimeSession(options) {
|
|
|
1877
1972
|
const reconnectTimerRef = useRef(null);
|
|
1878
1973
|
const heartbeatTimerRef = useRef(null);
|
|
1879
1974
|
const connectTimeoutRef = useRef(null);
|
|
1975
|
+
const documentFlushTimerRef = useRef(null);
|
|
1976
|
+
const documentFlushIdleRef = useRef(null);
|
|
1977
|
+
const draftPersistTimerRef = useRef(null);
|
|
1978
|
+
const draftPersistIdleRef = useRef(null);
|
|
1880
1979
|
const manualDisconnectRef = useRef(false);
|
|
1881
1980
|
const retryCountRef = useRef(0);
|
|
1882
1981
|
const currentRevisionRef = useRef(0);
|
|
@@ -1893,6 +1992,8 @@ function useRealtimeSession(options) {
|
|
|
1893
1992
|
const connectionStateRef = useRef(
|
|
1894
1993
|
enabled ? "connecting" : "offline"
|
|
1895
1994
|
);
|
|
1995
|
+
const localDraftRef = useRef(null);
|
|
1996
|
+
const conflictRef = useRef(null);
|
|
1896
1997
|
const onErrorRef = useRef(onError);
|
|
1897
1998
|
onErrorRef.current = onError;
|
|
1898
1999
|
const [connectSequence, setConnectSequence] = useState(0);
|
|
@@ -1909,7 +2010,18 @@ function useRealtimeSession(options) {
|
|
|
1909
2010
|
});
|
|
1910
2011
|
const [sessionPeers, setSessionPeers] = useState([]);
|
|
1911
2012
|
const [document2, setDocument] = useState(null);
|
|
2013
|
+
const [hasLocalOfflineDraft, setHasLocalOfflineDraft] = useState(false);
|
|
2014
|
+
const [hasPendingDocumentSync, setHasPendingDocumentSync] = useState(false);
|
|
2015
|
+
const [conflict, setConflict] = useState(null);
|
|
1912
2016
|
connectionStateRef.current = connection.state;
|
|
2017
|
+
const syncState = useMemo(() => {
|
|
2018
|
+
if (conflict != null) return "conflicted";
|
|
2019
|
+
if (connection.connected) return "connected";
|
|
2020
|
+
if (connection.state === "reconnecting" || connection.state === "connecting") {
|
|
2021
|
+
return "reconnecting";
|
|
2022
|
+
}
|
|
2023
|
+
return "offline";
|
|
2024
|
+
}, [conflict, connection.connected, connection.state]);
|
|
1913
2025
|
const clearReconnectTimer = useCallback(() => {
|
|
1914
2026
|
if (reconnectTimerRef.current != null) {
|
|
1915
2027
|
window.clearTimeout(reconnectTimerRef.current);
|
|
@@ -1928,6 +2040,22 @@ function useRealtimeSession(options) {
|
|
|
1928
2040
|
connectTimeoutRef.current = null;
|
|
1929
2041
|
}
|
|
1930
2042
|
}, []);
|
|
2043
|
+
const clearDocumentFlushSchedule = useCallback(() => {
|
|
2044
|
+
if (documentFlushTimerRef.current != null) {
|
|
2045
|
+
window.clearTimeout(documentFlushTimerRef.current);
|
|
2046
|
+
documentFlushTimerRef.current = null;
|
|
2047
|
+
}
|
|
2048
|
+
cancelWindowIdleCallback(documentFlushIdleRef.current);
|
|
2049
|
+
documentFlushIdleRef.current = null;
|
|
2050
|
+
}, []);
|
|
2051
|
+
const clearDraftPersistSchedule = useCallback(() => {
|
|
2052
|
+
if (draftPersistTimerRef.current != null) {
|
|
2053
|
+
window.clearTimeout(draftPersistTimerRef.current);
|
|
2054
|
+
draftPersistTimerRef.current = null;
|
|
2055
|
+
}
|
|
2056
|
+
cancelWindowIdleCallback(draftPersistIdleRef.current);
|
|
2057
|
+
draftPersistIdleRef.current = null;
|
|
2058
|
+
}, []);
|
|
1931
2059
|
const updateConnection = useCallback(
|
|
1932
2060
|
(patch) => {
|
|
1933
2061
|
setConnection((prev) => {
|
|
@@ -1953,6 +2081,84 @@ function useRealtimeSession(options) {
|
|
|
1953
2081
|
},
|
|
1954
2082
|
[notifySubscribers]
|
|
1955
2083
|
);
|
|
2084
|
+
const setConflictState = useCallback(
|
|
2085
|
+
(nextConflict) => {
|
|
2086
|
+
conflictRef.current = nextConflict;
|
|
2087
|
+
setConflict(nextConflict);
|
|
2088
|
+
},
|
|
2089
|
+
[]
|
|
2090
|
+
);
|
|
2091
|
+
const setLocalDraft = useCallback(
|
|
2092
|
+
(nextDraft) => {
|
|
2093
|
+
localDraftRef.current = nextDraft;
|
|
2094
|
+
setHasLocalOfflineDraft(nextDraft != null);
|
|
2095
|
+
if (nextDraft) return;
|
|
2096
|
+
removeRealtimeOfflineDraft(roomId);
|
|
2097
|
+
},
|
|
2098
|
+
[roomId]
|
|
2099
|
+
);
|
|
2100
|
+
const persistLocalDraft = useCallback(() => {
|
|
2101
|
+
clearDraftPersistSchedule();
|
|
2102
|
+
if (!localDraftRef.current) {
|
|
2103
|
+
removeRealtimeOfflineDraft(roomId);
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
writeRealtimeOfflineDraft(localDraftRef.current);
|
|
2107
|
+
}, [clearDraftPersistSchedule, roomId]);
|
|
2108
|
+
const scheduleDraftPersistence = useCallback(() => {
|
|
2109
|
+
clearDraftPersistSchedule();
|
|
2110
|
+
draftPersistTimerRef.current = window.setTimeout(() => {
|
|
2111
|
+
draftPersistTimerRef.current = null;
|
|
2112
|
+
draftPersistIdleRef.current = requestWindowIdleCallback(() => {
|
|
2113
|
+
draftPersistIdleRef.current = null;
|
|
2114
|
+
persistLocalDraft();
|
|
2115
|
+
}, DRAFT_PERSIST_DEBOUNCE_MS);
|
|
2116
|
+
}, DRAFT_PERSIST_DEBOUNCE_MS);
|
|
2117
|
+
}, [clearDraftPersistSchedule, persistLocalDraft]);
|
|
2118
|
+
const clearLocalDraft = useCallback(() => {
|
|
2119
|
+
setLocalDraft(null);
|
|
2120
|
+
clearDraftPersistSchedule();
|
|
2121
|
+
}, [clearDraftPersistSchedule, setLocalDraft]);
|
|
2122
|
+
const createSelfPeer = useCallback(
|
|
2123
|
+
(state) => ({
|
|
2124
|
+
id: peer.id,
|
|
2125
|
+
clientId: clientIdRef.current,
|
|
2126
|
+
peerId: peer.id,
|
|
2127
|
+
roomId,
|
|
2128
|
+
joinedAt: connection.lastConnectedAt ?? nowMs(),
|
|
2129
|
+
lastSeenAt: nowMs(),
|
|
2130
|
+
cursor: lastCursorRef.current,
|
|
2131
|
+
isSelf: true,
|
|
2132
|
+
connectionState: state,
|
|
2133
|
+
...peer.displayName ? { displayName: peer.displayName } : {},
|
|
2134
|
+
...peer.color ? { color: peer.color } : {},
|
|
2135
|
+
...peer.image ? { image: peer.image } : {},
|
|
2136
|
+
...lastMarkupStrokeRef.current !== void 0 ? { markupStroke: lastMarkupStrokeRef.current ?? null } : {},
|
|
2137
|
+
...lastCameraRef.current !== void 0 ? { camera: lastCameraRef.current ?? null } : {},
|
|
2138
|
+
...lastActiveToolRef.current ? { activeTool: lastActiveToolRef.current } : {}
|
|
2139
|
+
}),
|
|
2140
|
+
[
|
|
2141
|
+
connection.lastConnectedAt,
|
|
2142
|
+
peer.color,
|
|
2143
|
+
peer.displayName,
|
|
2144
|
+
peer.id,
|
|
2145
|
+
peer.image,
|
|
2146
|
+
roomId
|
|
2147
|
+
]
|
|
2148
|
+
);
|
|
2149
|
+
const collapsePeersToSelf = useCallback(
|
|
2150
|
+
(state) => {
|
|
2151
|
+
setSessionPeers((prev) => {
|
|
2152
|
+
const selfPeer = prev.find(
|
|
2153
|
+
(peerState) => peerState.clientId === clientIdRef.current
|
|
2154
|
+
);
|
|
2155
|
+
return [
|
|
2156
|
+
selfPeer ? { ...selfPeer, isSelf: true, connectionState: state } : createSelfPeer(state)
|
|
2157
|
+
];
|
|
2158
|
+
});
|
|
2159
|
+
},
|
|
2160
|
+
[createSelfPeer]
|
|
2161
|
+
);
|
|
1956
2162
|
const applyPeers = useCallback((peers) => {
|
|
1957
2163
|
const selfClientId = clientIdRef.current;
|
|
1958
2164
|
setSessionPeers(
|
|
@@ -1979,30 +2185,108 @@ function useRealtimeSession(options) {
|
|
|
1979
2185
|
[buildClientMessage]
|
|
1980
2186
|
);
|
|
1981
2187
|
const flushQueuedDocument = useCallback(() => {
|
|
2188
|
+
clearDocumentFlushSchedule();
|
|
2189
|
+
if (conflictRef.current) return;
|
|
1982
2190
|
const next = queuedItemsRef.current;
|
|
1983
2191
|
if (!next || outboundInFlightRef.current) return;
|
|
1984
|
-
queuedItemsRef.current = null;
|
|
1985
2192
|
const baseRevision = currentRevisionRef.current;
|
|
1986
|
-
const
|
|
1987
|
-
|
|
2193
|
+
const preparedItems = prepareRealtimeItems(next);
|
|
2194
|
+
if (!preparedItems) return;
|
|
2195
|
+
queuedItemsRef.current = null;
|
|
2196
|
+
outboundInFlightRef.current = {
|
|
2197
|
+
baseRevision,
|
|
2198
|
+
items: preparedItems.items,
|
|
2199
|
+
serialized: preparedItems.serialized
|
|
2200
|
+
};
|
|
1988
2201
|
const didSend = sendRaw({
|
|
1989
2202
|
type: "document:update",
|
|
1990
2203
|
roomId,
|
|
1991
2204
|
clientId: clientIdRef.current,
|
|
1992
2205
|
baseRevision,
|
|
1993
|
-
items:
|
|
2206
|
+
items: preparedItems.items
|
|
1994
2207
|
});
|
|
1995
2208
|
if (!didSend) {
|
|
1996
2209
|
queuedItemsRef.current = next;
|
|
1997
2210
|
outboundInFlightRef.current = null;
|
|
2211
|
+
setHasPendingDocumentSync(true);
|
|
1998
2212
|
}
|
|
1999
|
-
}, [roomId, sendRaw]);
|
|
2213
|
+
}, [clearDocumentFlushSchedule, roomId, sendRaw]);
|
|
2214
|
+
const scheduleDocumentFlush = useCallback(() => {
|
|
2215
|
+
clearDocumentFlushSchedule();
|
|
2216
|
+
documentFlushTimerRef.current = window.setTimeout(() => {
|
|
2217
|
+
documentFlushTimerRef.current = null;
|
|
2218
|
+
documentFlushIdleRef.current = requestWindowIdleCallback(() => {
|
|
2219
|
+
documentFlushIdleRef.current = null;
|
|
2220
|
+
flushQueuedDocument();
|
|
2221
|
+
}, DOCUMENT_FLUSH_DEBOUNCE_MS);
|
|
2222
|
+
}, DOCUMENT_FLUSH_DEBOUNCE_MS);
|
|
2223
|
+
}, [clearDocumentFlushSchedule, flushQueuedDocument]);
|
|
2000
2224
|
const queueDocumentSend = useCallback(
|
|
2001
2225
|
(items) => {
|
|
2226
|
+
setLocalDraft({
|
|
2227
|
+
roomId,
|
|
2228
|
+
baseRevision: currentRevisionRef.current,
|
|
2229
|
+
items,
|
|
2230
|
+
updatedAt: nowMs()
|
|
2231
|
+
});
|
|
2232
|
+
scheduleDraftPersistence();
|
|
2002
2233
|
queuedItemsRef.current = items;
|
|
2003
|
-
|
|
2234
|
+
setHasPendingDocumentSync(true);
|
|
2235
|
+
if (conflictRef.current) return;
|
|
2236
|
+
scheduleDocumentFlush();
|
|
2237
|
+
},
|
|
2238
|
+
[roomId, scheduleDocumentFlush, scheduleDraftPersistence, setLocalDraft]
|
|
2239
|
+
);
|
|
2240
|
+
const applyDraftSnapshot = useCallback(
|
|
2241
|
+
(draft, options2) => {
|
|
2242
|
+
applyDocument(buildDraftSnapshot(draft, clientIdRef.current), options2);
|
|
2004
2243
|
},
|
|
2005
|
-
[
|
|
2244
|
+
[applyDocument]
|
|
2245
|
+
);
|
|
2246
|
+
const resolveAuthoritativeDocument = useCallback(
|
|
2247
|
+
(serverDocument, options2) => {
|
|
2248
|
+
const localDraft = localDraftRef.current;
|
|
2249
|
+
if (!localDraft) {
|
|
2250
|
+
setConflictState(null);
|
|
2251
|
+
setHasPendingDocumentSync(false);
|
|
2252
|
+
applyDocument(serverDocument, options2);
|
|
2253
|
+
return false;
|
|
2254
|
+
}
|
|
2255
|
+
if (sameSerializedItems(localDraft.items, serverDocument.items)) {
|
|
2256
|
+
clearLocalDraft();
|
|
2257
|
+
setHasPendingDocumentSync(false);
|
|
2258
|
+
setConflictState(null);
|
|
2259
|
+
applyDocument(serverDocument, options2);
|
|
2260
|
+
return true;
|
|
2261
|
+
}
|
|
2262
|
+
if (serverDocument.revision === localDraft.baseRevision) {
|
|
2263
|
+
setConflictState(null);
|
|
2264
|
+
applyDraftSnapshot(localDraft, {
|
|
2265
|
+
suppressSubscriberNotify: options2?.suppressSubscriberNotify ?? sameSerializedItems(latestDocumentRef.current?.items, localDraft.items)
|
|
2266
|
+
});
|
|
2267
|
+
outboundInFlightRef.current = null;
|
|
2268
|
+
queuedItemsRef.current = localDraft.items;
|
|
2269
|
+
setHasPendingDocumentSync(true);
|
|
2270
|
+
scheduleDocumentFlush();
|
|
2271
|
+
return true;
|
|
2272
|
+
}
|
|
2273
|
+
setConflictState({
|
|
2274
|
+
serverRevision: serverDocument.revision,
|
|
2275
|
+
serverItems: serverDocument.items,
|
|
2276
|
+
localItems: localDraft.items
|
|
2277
|
+
});
|
|
2278
|
+
applyDraftSnapshot(localDraft, {
|
|
2279
|
+
suppressSubscriberNotify: options2?.suppressSubscriberNotify ?? sameSerializedItems(latestDocumentRef.current?.items, localDraft.items)
|
|
2280
|
+
});
|
|
2281
|
+
return true;
|
|
2282
|
+
},
|
|
2283
|
+
[
|
|
2284
|
+
applyDocument,
|
|
2285
|
+
applyDraftSnapshot,
|
|
2286
|
+
clearLocalDraft,
|
|
2287
|
+
scheduleDocumentFlush,
|
|
2288
|
+
setConflictState
|
|
2289
|
+
]
|
|
2006
2290
|
);
|
|
2007
2291
|
const sendPresenceUpdate = useCallback(() => {
|
|
2008
2292
|
sendRaw({
|
|
@@ -2041,17 +2325,108 @@ function useRealtimeSession(options) {
|
|
|
2041
2325
|
reconnect,
|
|
2042
2326
|
updateConnection
|
|
2043
2327
|
]);
|
|
2328
|
+
const resolveConflict = useCallback(
|
|
2329
|
+
(action) => {
|
|
2330
|
+
const activeConflict = conflictRef.current;
|
|
2331
|
+
if (!activeConflict) return;
|
|
2332
|
+
if (action === "use-server") {
|
|
2333
|
+
clearLocalDraft();
|
|
2334
|
+
queuedItemsRef.current = null;
|
|
2335
|
+
outboundInFlightRef.current = null;
|
|
2336
|
+
setConflictState(null);
|
|
2337
|
+
applyDocument({
|
|
2338
|
+
revision: activeConflict.serverRevision,
|
|
2339
|
+
items: activeConflict.serverItems,
|
|
2340
|
+
updatedAt: nowMs()
|
|
2341
|
+
});
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const nextDraft = {
|
|
2345
|
+
roomId,
|
|
2346
|
+
baseRevision: activeConflict.serverRevision,
|
|
2347
|
+
items: activeConflict.localItems,
|
|
2348
|
+
updatedAt: nowMs()
|
|
2349
|
+
};
|
|
2350
|
+
setLocalDraft(nextDraft);
|
|
2351
|
+
scheduleDraftPersistence();
|
|
2352
|
+
setConflictState(null);
|
|
2353
|
+
applyDraftSnapshot(nextDraft, {
|
|
2354
|
+
suppressSubscriberNotify: sameSerializedItems(
|
|
2355
|
+
latestDocumentRef.current?.items,
|
|
2356
|
+
nextDraft.items
|
|
2357
|
+
)
|
|
2358
|
+
});
|
|
2359
|
+
currentRevisionRef.current = activeConflict.serverRevision;
|
|
2360
|
+
queuedItemsRef.current = nextDraft.items;
|
|
2361
|
+
outboundInFlightRef.current = null;
|
|
2362
|
+
setHasPendingDocumentSync(true);
|
|
2363
|
+
scheduleDocumentFlush();
|
|
2364
|
+
},
|
|
2365
|
+
[
|
|
2366
|
+
applyDocument,
|
|
2367
|
+
applyDraftSnapshot,
|
|
2368
|
+
clearLocalDraft,
|
|
2369
|
+
roomId,
|
|
2370
|
+
scheduleDocumentFlush,
|
|
2371
|
+
scheduleDraftPersistence,
|
|
2372
|
+
setConflictState,
|
|
2373
|
+
setLocalDraft
|
|
2374
|
+
]
|
|
2375
|
+
);
|
|
2376
|
+
useEffect(() => {
|
|
2377
|
+
if (!roomId) {
|
|
2378
|
+
clearDocumentFlushSchedule();
|
|
2379
|
+
clearDraftPersistSchedule();
|
|
2380
|
+
localDraftRef.current = null;
|
|
2381
|
+
setHasLocalOfflineDraft(false);
|
|
2382
|
+
setHasPendingDocumentSync(false);
|
|
2383
|
+
setConflictState(null);
|
|
2384
|
+
queuedItemsRef.current = null;
|
|
2385
|
+
outboundInFlightRef.current = null;
|
|
2386
|
+
latestDocumentRef.current = null;
|
|
2387
|
+
setDocument(null);
|
|
2388
|
+
currentRevisionRef.current = 0;
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
const localDraft = readRealtimeOfflineDraft(roomId);
|
|
2392
|
+
setLocalDraft(localDraft);
|
|
2393
|
+
setHasPendingDocumentSync(localDraft != null);
|
|
2394
|
+
setConflictState(null);
|
|
2395
|
+
queuedItemsRef.current = localDraft?.items ?? null;
|
|
2396
|
+
outboundInFlightRef.current = null;
|
|
2397
|
+
if (localDraft) {
|
|
2398
|
+
applyDraftSnapshot(localDraft, {
|
|
2399
|
+
suppressSubscriberNotify: sameSerializedItems(
|
|
2400
|
+
latestDocumentRef.current?.items,
|
|
2401
|
+
localDraft.items
|
|
2402
|
+
)
|
|
2403
|
+
});
|
|
2404
|
+
} else {
|
|
2405
|
+
latestDocumentRef.current = null;
|
|
2406
|
+
setDocument(null);
|
|
2407
|
+
currentRevisionRef.current = 0;
|
|
2408
|
+
}
|
|
2409
|
+
}, [
|
|
2410
|
+
applyDraftSnapshot,
|
|
2411
|
+
clearDocumentFlushSchedule,
|
|
2412
|
+
clearDraftPersistSchedule,
|
|
2413
|
+
roomId,
|
|
2414
|
+
setConflictState,
|
|
2415
|
+
setLocalDraft
|
|
2416
|
+
]);
|
|
2044
2417
|
useEffect(() => {
|
|
2045
2418
|
if (!enabled) {
|
|
2046
2419
|
manualDisconnectRef.current = true;
|
|
2047
2420
|
clearReconnectTimer();
|
|
2048
2421
|
clearHeartbeatTimer();
|
|
2049
2422
|
clearConnectTimeout();
|
|
2423
|
+
clearDocumentFlushSchedule();
|
|
2050
2424
|
wsRef.current?.close();
|
|
2051
2425
|
wsRef.current = null;
|
|
2052
|
-
|
|
2053
|
-
queuedItemsRef.current = null;
|
|
2426
|
+
queuedItemsRef.current = localDraftRef.current?.items ?? null;
|
|
2054
2427
|
outboundInFlightRef.current = null;
|
|
2428
|
+
setHasPendingDocumentSync(localDraftRef.current != null);
|
|
2429
|
+
collapsePeersToSelf("offline");
|
|
2055
2430
|
updateConnection((prev) => ({
|
|
2056
2431
|
...prev,
|
|
2057
2432
|
state: "offline",
|
|
@@ -2063,6 +2438,17 @@ function useRealtimeSession(options) {
|
|
|
2063
2438
|
}
|
|
2064
2439
|
manualDisconnectRef.current = false;
|
|
2065
2440
|
const socketUrl = normalizeSocketUrl(url);
|
|
2441
|
+
if (!socketUrl) {
|
|
2442
|
+
collapsePeersToSelf("offline");
|
|
2443
|
+
updateConnection((prev) => ({
|
|
2444
|
+
...prev,
|
|
2445
|
+
state: "offline",
|
|
2446
|
+
connected: false,
|
|
2447
|
+
roomId,
|
|
2448
|
+
lastError: null
|
|
2449
|
+
}));
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2066
2452
|
if (!isValidSocketUrl(socketUrl)) {
|
|
2067
2453
|
updateConnection((prev) => ({
|
|
2068
2454
|
...prev,
|
|
@@ -2132,8 +2518,6 @@ function useRealtimeSession(options) {
|
|
|
2132
2518
|
}));
|
|
2133
2519
|
if (parsed.type === "session:welcome") {
|
|
2134
2520
|
retryCountRef.current = 0;
|
|
2135
|
-
const queuedBeforeWelcome = queuedItemsRef.current;
|
|
2136
|
-
const shouldPromoteQueuedLocalDraft = queuedBeforeWelcome != null && parsed.document.revision === 0 && parsed.document.items.length === 0;
|
|
2137
2521
|
updateConnection((prev) => ({
|
|
2138
2522
|
...prev,
|
|
2139
2523
|
state: "connected",
|
|
@@ -2144,14 +2528,21 @@ function useRealtimeSession(options) {
|
|
|
2144
2528
|
lastError: null
|
|
2145
2529
|
}));
|
|
2146
2530
|
applyPeers(parsed.peers);
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2531
|
+
const handledByDraft = resolveAuthoritativeDocument(
|
|
2532
|
+
sanitizeRealtimeSnapshot(parsed.document),
|
|
2533
|
+
{
|
|
2534
|
+
suppressSubscriberNotify: localDraftRef.current != null && sameSerializedItems(
|
|
2535
|
+
latestDocumentRef.current?.items,
|
|
2536
|
+
localDraftRef.current.items
|
|
2537
|
+
)
|
|
2538
|
+
}
|
|
2539
|
+
);
|
|
2540
|
+
if (!handledByDraft) {
|
|
2151
2541
|
queuedItemsRef.current = null;
|
|
2152
2542
|
outboundInFlightRef.current = null;
|
|
2543
|
+
setHasPendingDocumentSync(false);
|
|
2153
2544
|
}
|
|
2154
|
-
|
|
2545
|
+
scheduleDocumentFlush();
|
|
2155
2546
|
return;
|
|
2156
2547
|
}
|
|
2157
2548
|
if (parsed.type === "presence:sync") {
|
|
@@ -2198,30 +2589,39 @@ function useRealtimeSession(options) {
|
|
|
2198
2589
|
const isSelfAck = parsed.document.updatedByClientId === selfClientId;
|
|
2199
2590
|
if (!isSelfAck) {
|
|
2200
2591
|
outboundInFlightRef.current = null;
|
|
2201
|
-
queuedItemsRef.current = null;
|
|
2202
|
-
|
|
2592
|
+
queuedItemsRef.current = localDraftRef.current?.items ?? null;
|
|
2593
|
+
setHasPendingDocumentSync(queuedItemsRef.current != null);
|
|
2594
|
+
resolveAuthoritativeDocument(sanitizeRealtimeSnapshot(parsed.document));
|
|
2203
2595
|
return;
|
|
2204
2596
|
}
|
|
2205
2597
|
const shouldSuppress = inFlight != null && sameSerializedItems(inFlight.items, parsed.document.items);
|
|
2206
2598
|
outboundInFlightRef.current = null;
|
|
2207
|
-
applyDocument(parsed.document, {
|
|
2599
|
+
applyDocument(sanitizeRealtimeSnapshot(parsed.document), {
|
|
2600
|
+
suppressSubscriberNotify: shouldSuppress
|
|
2601
|
+
});
|
|
2208
2602
|
if (queuedItemsRef.current) {
|
|
2209
2603
|
if (sameSerializedItems(queuedItemsRef.current, parsed.document.items)) {
|
|
2210
2604
|
queuedItemsRef.current = null;
|
|
2211
2605
|
} else {
|
|
2212
|
-
|
|
2606
|
+
scheduleDocumentFlush();
|
|
2213
2607
|
}
|
|
2214
2608
|
}
|
|
2609
|
+
if (!queuedItemsRef.current) {
|
|
2610
|
+
clearLocalDraft();
|
|
2611
|
+
}
|
|
2612
|
+
setHasPendingDocumentSync(queuedItemsRef.current != null);
|
|
2613
|
+
setConflictState(null);
|
|
2215
2614
|
return;
|
|
2216
2615
|
}
|
|
2217
2616
|
if (parsed.type === "document:resync-required") {
|
|
2218
2617
|
outboundInFlightRef.current = null;
|
|
2219
|
-
queuedItemsRef.current = null;
|
|
2618
|
+
queuedItemsRef.current = localDraftRef.current?.items ?? null;
|
|
2619
|
+
setHasPendingDocumentSync(queuedItemsRef.current != null);
|
|
2220
2620
|
updateConnection((prev) => ({
|
|
2221
2621
|
...prev,
|
|
2222
2622
|
lastError: parsed.reason
|
|
2223
2623
|
}));
|
|
2224
|
-
|
|
2624
|
+
resolveAuthoritativeDocument(sanitizeRealtimeSnapshot(parsed.document));
|
|
2225
2625
|
}
|
|
2226
2626
|
});
|
|
2227
2627
|
socket.addEventListener("error", () => {
|
|
@@ -2237,6 +2637,9 @@ function useRealtimeSession(options) {
|
|
|
2237
2637
|
clearHeartbeatTimer();
|
|
2238
2638
|
clearConnectTimeout();
|
|
2239
2639
|
wsRef.current = null;
|
|
2640
|
+
collapsePeersToSelf(
|
|
2641
|
+
manualDisconnectRef.current || !enabled ? "offline" : "reconnecting"
|
|
2642
|
+
);
|
|
2240
2643
|
updateConnection((prev) => ({
|
|
2241
2644
|
...prev,
|
|
2242
2645
|
connected: false,
|
|
@@ -2252,6 +2655,7 @@ function useRealtimeSession(options) {
|
|
|
2252
2655
|
clearReconnectTimer();
|
|
2253
2656
|
clearHeartbeatTimer();
|
|
2254
2657
|
clearConnectTimeout();
|
|
2658
|
+
clearDocumentFlushSchedule();
|
|
2255
2659
|
socket.close();
|
|
2256
2660
|
};
|
|
2257
2661
|
}, [
|
|
@@ -2259,19 +2663,24 @@ function useRealtimeSession(options) {
|
|
|
2259
2663
|
applyPeers,
|
|
2260
2664
|
clearConnectTimeout,
|
|
2261
2665
|
clearHeartbeatTimer,
|
|
2666
|
+
clearLocalDraft,
|
|
2262
2667
|
clearReconnectTimer,
|
|
2668
|
+
collapsePeersToSelf,
|
|
2669
|
+
clearDocumentFlushSchedule,
|
|
2263
2670
|
connectSequence,
|
|
2264
2671
|
connectTimeoutMs,
|
|
2265
2672
|
enabled,
|
|
2266
|
-
flushQueuedDocument,
|
|
2267
2673
|
heartbeatMs,
|
|
2268
2674
|
peer.color,
|
|
2269
2675
|
peer.displayName,
|
|
2270
|
-
peer.image,
|
|
2271
2676
|
peer.id,
|
|
2677
|
+
peer.image,
|
|
2678
|
+
resolveAuthoritativeDocument,
|
|
2272
2679
|
roomId,
|
|
2680
|
+
scheduleDocumentFlush,
|
|
2273
2681
|
scheduleReconnect,
|
|
2274
2682
|
sendRaw,
|
|
2683
|
+
setConflictState,
|
|
2275
2684
|
updateConnection,
|
|
2276
2685
|
url
|
|
2277
2686
|
]);
|
|
@@ -2286,6 +2695,18 @@ function useRealtimeSession(options) {
|
|
|
2286
2695
|
)
|
|
2287
2696
|
);
|
|
2288
2697
|
}, [connection.state]);
|
|
2698
|
+
useEffect(
|
|
2699
|
+
() => () => {
|
|
2700
|
+
clearDocumentFlushSchedule();
|
|
2701
|
+
clearDraftPersistSchedule();
|
|
2702
|
+
},
|
|
2703
|
+
[clearDocumentFlushSchedule, clearDraftPersistSchedule]
|
|
2704
|
+
);
|
|
2705
|
+
const flushDocumentSync = useCallback(async () => {
|
|
2706
|
+
persistLocalDraft();
|
|
2707
|
+
if (!connection.connected) return;
|
|
2708
|
+
flushQueuedDocument();
|
|
2709
|
+
}, [connection.connected, flushQueuedDocument, persistLocalDraft]);
|
|
2289
2710
|
const remoteAdapter = useMemo(
|
|
2290
2711
|
() => ({
|
|
2291
2712
|
subscribe(onItems) {
|
|
@@ -2299,9 +2720,12 @@ function useRealtimeSession(options) {
|
|
|
2299
2720
|
},
|
|
2300
2721
|
send(items) {
|
|
2301
2722
|
queueDocumentSend(items);
|
|
2723
|
+
},
|
|
2724
|
+
flush() {
|
|
2725
|
+
return flushDocumentSync();
|
|
2302
2726
|
}
|
|
2303
2727
|
}),
|
|
2304
|
-
[queueDocumentSend]
|
|
2728
|
+
[flushDocumentSync, queueDocumentSend]
|
|
2305
2729
|
);
|
|
2306
2730
|
const disconnect = useCallback(() => {
|
|
2307
2731
|
manualDisconnectRef.current = true;
|
|
@@ -2315,6 +2739,7 @@ function useRealtimeSession(options) {
|
|
|
2315
2739
|
});
|
|
2316
2740
|
wsRef.current?.close();
|
|
2317
2741
|
wsRef.current = null;
|
|
2742
|
+
collapsePeersToSelf("offline");
|
|
2318
2743
|
updateConnection((prev) => ({
|
|
2319
2744
|
...prev,
|
|
2320
2745
|
state: "offline",
|
|
@@ -2324,6 +2749,7 @@ function useRealtimeSession(options) {
|
|
|
2324
2749
|
clearConnectTimeout,
|
|
2325
2750
|
clearHeartbeatTimer,
|
|
2326
2751
|
clearReconnectTimer,
|
|
2752
|
+
collapsePeersToSelf,
|
|
2327
2753
|
roomId,
|
|
2328
2754
|
sendRaw,
|
|
2329
2755
|
updateConnection
|
|
@@ -2389,8 +2815,15 @@ function useRealtimeSession(options) {
|
|
|
2389
2815
|
remotePresence,
|
|
2390
2816
|
remoteAdapter,
|
|
2391
2817
|
document: document2,
|
|
2818
|
+
hasLocalOfflineDraft,
|
|
2819
|
+
hasPendingDocumentSync,
|
|
2820
|
+
syncState,
|
|
2821
|
+
conflict,
|
|
2392
2822
|
bindViewportPresence,
|
|
2393
2823
|
syncViewportPresence,
|
|
2824
|
+
resolveConflict,
|
|
2825
|
+
clearLocalDraft,
|
|
2826
|
+
flushDocumentSync,
|
|
2394
2827
|
disconnect,
|
|
2395
2828
|
reconnectNow
|
|
2396
2829
|
};
|
|
@@ -2543,7 +2976,76 @@ function realtimeSessionPlugin(options) {
|
|
|
2543
2976
|
render: () => /* @__PURE__ */ jsx(RealtimeSessionPanel, { ...options })
|
|
2544
2977
|
};
|
|
2545
2978
|
}
|
|
2979
|
+
function useRealtimeCanvasDocument(options) {
|
|
2980
|
+
const {
|
|
2981
|
+
session,
|
|
2982
|
+
items,
|
|
2983
|
+
onItemsChange,
|
|
2984
|
+
normalizeItems,
|
|
2985
|
+
hydrateItems,
|
|
2986
|
+
enabled = true
|
|
2987
|
+
} = options;
|
|
2988
|
+
const [loading, setLoading] = useState(false);
|
|
2989
|
+
const lastAppliedRevisionRef = useRef(null);
|
|
2990
|
+
const realtimeEnabled = enabled && session != null;
|
|
2991
|
+
const applyIncomingItems = useCallback(
|
|
2992
|
+
async (nextItems) => {
|
|
2993
|
+
const normalizedItems = normalizeItems ? normalizeItems(nextItems) : [...nextItems];
|
|
2994
|
+
if (!hydrateItems) return normalizedItems;
|
|
2995
|
+
return await hydrateItems(normalizedItems);
|
|
2996
|
+
},
|
|
2997
|
+
[hydrateItems, normalizeItems]
|
|
2998
|
+
);
|
|
2999
|
+
const handleItemsChange = useCallback(
|
|
3000
|
+
(nextItems) => {
|
|
3001
|
+
if (!enabled) {
|
|
3002
|
+
onItemsChange?.(normalizeItems ? normalizeItems(nextItems) : nextItems);
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
const normalizedItems = normalizeItems ? normalizeItems(nextItems) : nextItems;
|
|
3006
|
+
onItemsChange?.(normalizedItems);
|
|
3007
|
+
session?.remoteAdapter.send?.(normalizedItems);
|
|
3008
|
+
},
|
|
3009
|
+
[enabled, normalizeItems, onItemsChange, session]
|
|
3010
|
+
);
|
|
3011
|
+
useEffect(() => {
|
|
3012
|
+
if (!realtimeEnabled || !onItemsChange || !session?.document) return;
|
|
3013
|
+
if (session.document.updatedByClientId === session.connection.clientId) return;
|
|
3014
|
+
if (lastAppliedRevisionRef.current === session.document.revision) return;
|
|
3015
|
+
let cancelled = false;
|
|
3016
|
+
setLoading(true);
|
|
3017
|
+
void applyIncomingItems(session.document.items).then((resolvedItems) => {
|
|
3018
|
+
if (cancelled) return;
|
|
3019
|
+
lastAppliedRevisionRef.current = session.document?.revision ?? null;
|
|
3020
|
+
onItemsChange(resolvedItems);
|
|
3021
|
+
}).finally(() => {
|
|
3022
|
+
if (cancelled) return;
|
|
3023
|
+
setLoading(false);
|
|
3024
|
+
});
|
|
3025
|
+
return () => {
|
|
3026
|
+
cancelled = true;
|
|
3027
|
+
};
|
|
3028
|
+
}, [applyIncomingItems, realtimeEnabled, onItemsChange, session]);
|
|
3029
|
+
return useMemo(
|
|
3030
|
+
() => ({
|
|
3031
|
+
items,
|
|
3032
|
+
onItemsChange: onItemsChange ? handleItemsChange : void 0,
|
|
3033
|
+
loading,
|
|
3034
|
+
saving: session?.hasPendingDocumentSync ?? false,
|
|
3035
|
+
hasLocalOfflineDraft: session?.hasLocalOfflineDraft ?? false,
|
|
3036
|
+
syncState: session?.syncState ?? "offline",
|
|
3037
|
+
conflict: session?.conflict ?? null,
|
|
3038
|
+
resolveConflict: session?.resolveConflict ?? (() => {
|
|
3039
|
+
}),
|
|
3040
|
+
clearLocalDraft: session?.clearLocalDraft ?? (() => {
|
|
3041
|
+
}),
|
|
3042
|
+
flush: session?.flushDocumentSync ?? (async () => {
|
|
3043
|
+
})
|
|
3044
|
+
}),
|
|
3045
|
+
[handleItemsChange, items, loading, onItemsChange, session]
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
2546
3048
|
|
|
2547
|
-
export { PresenceRemoteLayer, REALTIME_COMMENT_TOOL, RealtimeCommentsOverlay, RealtimeSessionPanel, createRealtimeCommentAvatarDataUrl, createRealtimeCommentDraftItem, createRealtimeCommentItem, defaultPresenceColorForId, getRealtimeCommentData, isRealtimeCommentDraftItem, isRealtimeCommentItem, parseRealtimeClientMessage, parseRealtimeServerMessage, realtimeCollaborationPlugin, realtimeCommentsPlugin, realtimeSessionPlugin, remoteMarkupStrokeFromPlacementPreview, useRealtimeComments, useRealtimePeerFollow, useRealtimeSession, withRealtimeCommentTool };
|
|
3049
|
+
export { PresenceRemoteLayer, REALTIME_COMMENT_TOOL, RealtimeCommentsOverlay, RealtimeSessionPanel, createRealtimeCommentAvatarDataUrl, createRealtimeCommentDraftItem, createRealtimeCommentItem, defaultPresenceColorForId, getRealtimeCommentData, isRealtimeCommentDraftItem, isRealtimeCommentItem, parseRealtimeClientMessage, parseRealtimeServerMessage, realtimeCollaborationPlugin, realtimeCommentsPlugin, realtimeSessionPlugin, remoteMarkupStrokeFromPlacementPreview, useRealtimeCanvasDocument, useRealtimeComments, useRealtimePeerFollow, useRealtimeSession, withRealtimeCommentTool };
|
|
2548
3050
|
//# sourceMappingURL=realtime.js.map
|
|
2549
3051
|
//# sourceMappingURL=realtime.js.map
|