canvu-react 0.4.42 → 0.4.44
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/native.cjs +480 -63
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +46 -3
- package/dist/native.d.ts +46 -3
- package/dist/native.js +480 -64
- package/dist/native.js.map +1 -1
- package/dist/realtime.cjs +82 -12
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +82 -12
- package/dist/realtime.js.map +1 -1
- package/dist/realtimeNative.cjs +85 -15
- package/dist/realtimeNative.cjs.map +1 -1
- package/dist/realtimeNative.js +85 -15
- package/dist/realtimeNative.js.map +1 -1
- package/package.json +1 -1
package/dist/realtimeNative.js
CHANGED
|
@@ -247,6 +247,12 @@ function parseRealtimeServerMessage(value) {
|
|
|
247
247
|
}
|
|
248
248
|
return void 0;
|
|
249
249
|
}
|
|
250
|
+
var CLIENT_ONLY_IMAGE_KEYS = /* @__PURE__ */ new Set([
|
|
251
|
+
"imageBlobId",
|
|
252
|
+
"imageThumbnailBlobId",
|
|
253
|
+
"imageRasterHref",
|
|
254
|
+
"imageThumbnailHref"
|
|
255
|
+
]);
|
|
250
256
|
function getSceneItemId(item) {
|
|
251
257
|
const id = item.id;
|
|
252
258
|
return typeof id === "string" ? id : null;
|
|
@@ -263,6 +269,43 @@ function hasMissingLocalItems(localItems, incomingItems) {
|
|
|
263
269
|
}
|
|
264
270
|
return false;
|
|
265
271
|
}
|
|
272
|
+
function getComparableRealtimeItem(item) {
|
|
273
|
+
const comparable = { ...item };
|
|
274
|
+
if (item.toolKind === "image") {
|
|
275
|
+
for (const key of CLIENT_ONLY_IMAGE_KEYS) {
|
|
276
|
+
delete comparable[key];
|
|
277
|
+
}
|
|
278
|
+
comparable.childrenSvg = "";
|
|
279
|
+
}
|
|
280
|
+
return comparable;
|
|
281
|
+
}
|
|
282
|
+
function serializeComparableRealtimeItem(item) {
|
|
283
|
+
return JSON.stringify(getComparableRealtimeItem(item));
|
|
284
|
+
}
|
|
285
|
+
function hasChangedLocalItems(localItems, incomingItems) {
|
|
286
|
+
const incomingItemsById = /* @__PURE__ */ new Map();
|
|
287
|
+
for (const incomingItem of incomingItems) {
|
|
288
|
+
const id = getSceneItemId(incomingItem);
|
|
289
|
+
if (id) incomingItemsById.set(id, incomingItem);
|
|
290
|
+
}
|
|
291
|
+
for (const localItem of localItems) {
|
|
292
|
+
const id = getSceneItemId(localItem);
|
|
293
|
+
const incomingItem = id ? incomingItemsById.get(id) : null;
|
|
294
|
+
if (!incomingItem) continue;
|
|
295
|
+
if (serializeComparableRealtimeItem(localItem) !== serializeComparableRealtimeItem(incomingItem)) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
function shouldPreserveLocalRealtimeItems({
|
|
302
|
+
localItems,
|
|
303
|
+
incomingItems,
|
|
304
|
+
hasPendingLocalChanges
|
|
305
|
+
}) {
|
|
306
|
+
if (!hasPendingLocalChanges) return false;
|
|
307
|
+
return hasMissingLocalItems(localItems, incomingItems) || hasChangedLocalItems(localItems, incomingItems);
|
|
308
|
+
}
|
|
266
309
|
function useRealtimeCanvasDocument(options) {
|
|
267
310
|
const {
|
|
268
311
|
session,
|
|
@@ -275,6 +318,8 @@ function useRealtimeCanvasDocument(options) {
|
|
|
275
318
|
const [loading, setLoading] = useState(false);
|
|
276
319
|
const lastAppliedRevisionRef = useRef(null);
|
|
277
320
|
const inFlightRevisionRef = useRef(null);
|
|
321
|
+
const latestItemsRef = useRef(items);
|
|
322
|
+
const hasLocalChangeInFlightRef = useRef(false);
|
|
278
323
|
const hasEverPropagatedItemsRef = useRef(false);
|
|
279
324
|
const realtimeEnabled = enabled && session != null;
|
|
280
325
|
const documentRevision = session?.document?.revision ?? null;
|
|
@@ -297,6 +342,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
297
342
|
onItemsChange?.(normalizeItems ? normalizeItems(nextItems) : nextItems);
|
|
298
343
|
return;
|
|
299
344
|
}
|
|
345
|
+
hasLocalChangeInFlightRef.current = true;
|
|
300
346
|
const normalizedItems = normalizeItems ? normalizeItems(nextItems) : nextItems;
|
|
301
347
|
onItemsChange?.(normalizedItems);
|
|
302
348
|
session?.remoteAdapter.send?.(normalizedItems);
|
|
@@ -304,10 +350,16 @@ function useRealtimeCanvasDocument(options) {
|
|
|
304
350
|
[enabled, normalizeItems, onItemsChange, session]
|
|
305
351
|
);
|
|
306
352
|
useEffect(() => {
|
|
353
|
+
latestItemsRef.current = items;
|
|
307
354
|
if (items.length > 0) {
|
|
308
355
|
hasEverPropagatedItemsRef.current = true;
|
|
309
356
|
}
|
|
310
|
-
}, [items
|
|
357
|
+
}, [items]);
|
|
358
|
+
useEffect(() => {
|
|
359
|
+
if (!hasLocalOfflineDraft && !hasPendingDocumentSync) {
|
|
360
|
+
hasLocalChangeInFlightRef.current = false;
|
|
361
|
+
}
|
|
362
|
+
}, [hasLocalOfflineDraft, hasPendingDocumentSync]);
|
|
311
363
|
useEffect(() => {
|
|
312
364
|
if (!realtimeEnabled || !onItemsChange || !session?.document) return;
|
|
313
365
|
if (documentUpdatedByClientId === connectionClientId) return;
|
|
@@ -321,17 +373,22 @@ function useRealtimeCanvasDocument(options) {
|
|
|
321
373
|
if (cancelled) return;
|
|
322
374
|
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
323
375
|
lastAppliedRevisionRef.current = documentRevision;
|
|
324
|
-
const
|
|
325
|
-
const
|
|
376
|
+
const localItems = latestItemsRef.current;
|
|
377
|
+
const hasLocalItems = localItems.length > 0;
|
|
378
|
+
const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync || hasLocalChangeInFlightRef.current;
|
|
326
379
|
if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
|
|
327
380
|
if (hasLocalItems) {
|
|
328
|
-
const normalizedLocalItems = normalizeItems ? normalizeItems(
|
|
381
|
+
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
329
382
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
330
383
|
}
|
|
331
384
|
return;
|
|
332
385
|
}
|
|
333
|
-
if (
|
|
334
|
-
|
|
386
|
+
if (shouldPreserveLocalRealtimeItems({
|
|
387
|
+
localItems,
|
|
388
|
+
incomingItems: resolvedItems,
|
|
389
|
+
hasPendingLocalChanges
|
|
390
|
+
})) {
|
|
391
|
+
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
335
392
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
336
393
|
return;
|
|
337
394
|
}
|
|
@@ -357,7 +414,6 @@ function useRealtimeCanvasDocument(options) {
|
|
|
357
414
|
documentUpdatedByClientId,
|
|
358
415
|
hasLocalOfflineDraft,
|
|
359
416
|
hasPendingDocumentSync,
|
|
360
|
-
items,
|
|
361
417
|
normalizeItems,
|
|
362
418
|
onItemsChange,
|
|
363
419
|
realtimeEnabled,
|
|
@@ -520,7 +576,7 @@ function useRealtimePeerFollow(options) {
|
|
|
520
576
|
}, [followedPeerId, onFollowEnd, sessionPeers, viewportRef]);
|
|
521
577
|
}
|
|
522
578
|
var ITEMS_KEY = "items";
|
|
523
|
-
var
|
|
579
|
+
var CLIENT_ONLY_IMAGE_KEYS2 = /* @__PURE__ */ new Set([
|
|
524
580
|
"imageBlobId",
|
|
525
581
|
"imageThumbnailBlobId",
|
|
526
582
|
"imageRasterHref",
|
|
@@ -613,7 +669,7 @@ function updateYMapInPlace(yMap, next) {
|
|
|
613
669
|
function normalizeRealtimeItem(item) {
|
|
614
670
|
if (item.toolKind !== "image") return item;
|
|
615
671
|
const normalized = Object.fromEntries(
|
|
616
|
-
Object.entries(item).filter(([key]) => !
|
|
672
|
+
Object.entries(item).filter(([key]) => !CLIENT_ONLY_IMAGE_KEYS2.has(key))
|
|
617
673
|
);
|
|
618
674
|
normalized.childrenSvg = "";
|
|
619
675
|
return normalized;
|
|
@@ -766,6 +822,7 @@ var ORIGIN_BOOTSTRAP = /* @__PURE__ */ Symbol("canvu/realtime/bootstrap");
|
|
|
766
822
|
var DRAFT_STORAGE_PREFIX = "canvu-realtime-draft:";
|
|
767
823
|
var DOCUMENT_FLUSH_DEBOUNCE_MS = 120;
|
|
768
824
|
var DRAFT_PERSIST_DEBOUNCE_MS = 420;
|
|
825
|
+
var CONNECTION_MESSAGE_STATE_UPDATE_INTERVAL_MS = 1e3;
|
|
769
826
|
function requestRuntimeIdleCallback(callback, timeout) {
|
|
770
827
|
const runtime = globalThis;
|
|
771
828
|
if (typeof runtime.requestIdleCallback === "function") {
|
|
@@ -777,7 +834,7 @@ function cancelRuntimeIdleCallback(handle) {
|
|
|
777
834
|
if (handle == null) return;
|
|
778
835
|
const runtime = globalThis;
|
|
779
836
|
if (typeof runtime.cancelIdleCallback === "function") {
|
|
780
|
-
runtime.cancelIdleCallback(
|
|
837
|
+
runtime.cancelIdleCallback(handle);
|
|
781
838
|
return;
|
|
782
839
|
}
|
|
783
840
|
globalThis.clearTimeout(handle);
|
|
@@ -898,6 +955,9 @@ function sameSerializedItems(left, right) {
|
|
|
898
955
|
function nowMs() {
|
|
899
956
|
return Date.now();
|
|
900
957
|
}
|
|
958
|
+
function shouldUpdateRealtimeConnectionMessageState(input) {
|
|
959
|
+
return input.lastStateUpdateAt == null || input.receivedAt - input.lastStateUpdateAt >= CONNECTION_MESSAGE_STATE_UPDATE_INTERVAL_MS;
|
|
960
|
+
}
|
|
901
961
|
function hasDurableDocumentPersistence(snapshot) {
|
|
902
962
|
return snapshot.persistedRevision == null || snapshot.persistedRevision >= snapshot.revision;
|
|
903
963
|
}
|
|
@@ -1061,6 +1121,7 @@ function useRealtimeSession(options) {
|
|
|
1061
1121
|
const connectionStateRef = useRef(
|
|
1062
1122
|
enabled ? "connecting" : "offline"
|
|
1063
1123
|
);
|
|
1124
|
+
const lastConnectionMessageStateUpdateAtRef = useRef(null);
|
|
1064
1125
|
const localDraftRef = useRef(null);
|
|
1065
1126
|
const conflictRef = useRef(null);
|
|
1066
1127
|
const onErrorRef = useRef(onError);
|
|
@@ -1626,6 +1687,7 @@ function useRealtimeSession(options) {
|
|
|
1626
1687
|
useEffect(() => {
|
|
1627
1688
|
if (!enabled) {
|
|
1628
1689
|
manualDisconnectRef.current = true;
|
|
1690
|
+
lastConnectionMessageStateUpdateAtRef.current = null;
|
|
1629
1691
|
clearReconnectTimer();
|
|
1630
1692
|
clearHeartbeatTimer();
|
|
1631
1693
|
clearConnectTimeout();
|
|
@@ -1682,6 +1744,7 @@ function useRealtimeSession(options) {
|
|
|
1682
1744
|
return;
|
|
1683
1745
|
}
|
|
1684
1746
|
let disposed = false;
|
|
1747
|
+
lastConnectionMessageStateUpdateAtRef.current = null;
|
|
1685
1748
|
updateConnectionRef.current((prev) => ({
|
|
1686
1749
|
...prev,
|
|
1687
1750
|
state: retryCountRef.current > 0 ? "reconnecting" : "connecting",
|
|
@@ -1735,10 +1798,17 @@ function useRealtimeSession(options) {
|
|
|
1735
1798
|
}
|
|
1736
1799
|
const parsed = parseRealtimeServerMessage(payload);
|
|
1737
1800
|
if (!parsed) return;
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1801
|
+
const receivedAt = nowMs();
|
|
1802
|
+
if (shouldUpdateRealtimeConnectionMessageState({
|
|
1803
|
+
lastStateUpdateAt: lastConnectionMessageStateUpdateAtRef.current,
|
|
1804
|
+
receivedAt
|
|
1805
|
+
})) {
|
|
1806
|
+
lastConnectionMessageStateUpdateAtRef.current = receivedAt;
|
|
1807
|
+
updateConnectionRef.current((prev) => ({
|
|
1808
|
+
...prev,
|
|
1809
|
+
lastMessageAt: receivedAt
|
|
1810
|
+
}));
|
|
1811
|
+
}
|
|
1742
1812
|
if (parsed.type === "session:welcome") {
|
|
1743
1813
|
retryCountRef.current = 0;
|
|
1744
1814
|
updateConnectionRef.current((prev) => ({
|
|
@@ -1747,7 +1817,7 @@ function useRealtimeSession(options) {
|
|
|
1747
1817
|
connected: true,
|
|
1748
1818
|
clientId: parsed.clientId,
|
|
1749
1819
|
retryCount: 0,
|
|
1750
|
-
lastConnectedAt:
|
|
1820
|
+
lastConnectedAt: receivedAt,
|
|
1751
1821
|
lastError: null
|
|
1752
1822
|
}));
|
|
1753
1823
|
applyPeersRef.current(parsed.peers);
|