canvu-react 0.4.79 → 0.4.81
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/realtime.cjs +119 -11
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +5 -0
- package/dist/realtime.d.ts +5 -0
- package/dist/realtime.js +119 -11
- package/dist/realtime.js.map +1 -1
- package/dist/realtimeNative.cjs +119 -11
- package/dist/realtimeNative.cjs.map +1 -1
- package/dist/realtimeNative.d.cts +5 -0
- package/dist/realtimeNative.d.ts +5 -0
- package/dist/realtimeNative.js +119 -11
- package/dist/realtimeNative.js.map +1 -1
- package/package.json +1 -1
package/dist/realtimeNative.cjs
CHANGED
|
@@ -104,12 +104,16 @@ function parseDocumentSnapshot(value) {
|
|
|
104
104
|
const updatedAt = getNumber(value.updatedAt);
|
|
105
105
|
const items = parseItems(value.items);
|
|
106
106
|
if (revision == null || updatedAt == null || items == null) return void 0;
|
|
107
|
+
const deletedItemIds = Array.isArray(value.deletedItemIds) ? value.deletedItemIds.filter(
|
|
108
|
+
(entry) => typeof entry === "string"
|
|
109
|
+
) : void 0;
|
|
107
110
|
return {
|
|
108
111
|
revision,
|
|
109
112
|
updatedAt,
|
|
110
113
|
items,
|
|
111
114
|
...getString(value.updatedByClientId) ? { updatedByClientId: getString(value.updatedByClientId) } : {},
|
|
112
|
-
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {}
|
|
115
|
+
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {},
|
|
116
|
+
...deletedItemIds ? { deletedItemIds } : {}
|
|
113
117
|
};
|
|
114
118
|
}
|
|
115
119
|
function parseRealtimeSessionPeer(value) {
|
|
@@ -346,6 +350,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
346
350
|
const realtimeEnabled = enabled && session != null;
|
|
347
351
|
const documentRevision = session?.document?.revision ?? null;
|
|
348
352
|
const documentItems = session?.document?.items;
|
|
353
|
+
const documentDeletedItemIds = session?.document?.deletedItemIds;
|
|
349
354
|
const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
|
|
350
355
|
const connectionClientId = session?.connection.clientId ?? null;
|
|
351
356
|
const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
|
|
@@ -395,15 +400,22 @@ function useRealtimeCanvasDocument(options) {
|
|
|
395
400
|
if (cancelled) return;
|
|
396
401
|
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
397
402
|
lastAppliedRevisionRef.current = documentRevision;
|
|
398
|
-
const
|
|
403
|
+
const rawLocalItems = latestItemsRef.current;
|
|
404
|
+
const deletedIds = new Set(documentDeletedItemIds ?? []);
|
|
405
|
+
const localItems = deletedIds.size > 0 ? rawLocalItems.filter((item) => {
|
|
406
|
+
const id = getSceneItemId(item);
|
|
407
|
+
return !id || !deletedIds.has(id);
|
|
408
|
+
}) : rawLocalItems;
|
|
399
409
|
const hasLocalItems = localItems.length > 0;
|
|
400
410
|
const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync || hasLocalChangeInFlightRef.current;
|
|
401
411
|
if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
|
|
402
412
|
if (hasLocalItems) {
|
|
403
413
|
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
404
414
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
415
|
+
return;
|
|
405
416
|
}
|
|
406
|
-
|
|
417
|
+
const emptinessExplainedByDeletes = rawLocalItems.length > 0 && localItems.length === 0;
|
|
418
|
+
if (!emptinessExplainedByDeletes) return;
|
|
407
419
|
}
|
|
408
420
|
if (shouldPreserveLocalRealtimeItems({
|
|
409
421
|
localItems,
|
|
@@ -431,6 +443,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
431
443
|
}, [
|
|
432
444
|
applyIncomingItems,
|
|
433
445
|
connectionClientId,
|
|
446
|
+
documentDeletedItemIds,
|
|
434
447
|
documentItems,
|
|
435
448
|
documentRevision,
|
|
436
449
|
documentUpdatedByClientId,
|
|
@@ -607,6 +620,16 @@ var CLIENT_ONLY_IMAGE_KEYS2 = /* @__PURE__ */ new Set([
|
|
|
607
620
|
"imageThumbnailHref"
|
|
608
621
|
]);
|
|
609
622
|
var SERVER_ADD_RACE_WINDOW_MS = 2e3;
|
|
623
|
+
var LOCAL_DELETE_TOMBSTONE_TTL_MS = 1e4;
|
|
624
|
+
function isLocalDeleteTombstoneActive(board, id, now) {
|
|
625
|
+
const deletedAt = board.locallyDeletedItemIds.get(id);
|
|
626
|
+
if (deletedAt == null) return false;
|
|
627
|
+
if (now - deletedAt >= LOCAL_DELETE_TOMBSTONE_TTL_MS) {
|
|
628
|
+
board.locallyDeletedItemIds.delete(id);
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
610
633
|
function createYjsBoardDoc() {
|
|
611
634
|
const doc = new Y__namespace.Doc();
|
|
612
635
|
const yItems = doc.getArray(ITEMS_KEY);
|
|
@@ -615,9 +638,22 @@ function createYjsBoardDoc() {
|
|
|
615
638
|
yItems,
|
|
616
639
|
lastServerConfirmedIds: /* @__PURE__ */ new Set(),
|
|
617
640
|
lastServerConfirmedItemSerializations: /* @__PURE__ */ new Map(),
|
|
618
|
-
serverItemSeenAt: /* @__PURE__ */ new Map()
|
|
641
|
+
serverItemSeenAt: /* @__PURE__ */ new Map(),
|
|
642
|
+
serverDeletedItemIds: /* @__PURE__ */ new Set(),
|
|
643
|
+
locallyDeletedItemIds: /* @__PURE__ */ new Map(),
|
|
644
|
+
consumerSeenItemIds: /* @__PURE__ */ new Set()
|
|
619
645
|
};
|
|
620
646
|
}
|
|
647
|
+
function getRealtimeDeletedItemIds(board) {
|
|
648
|
+
const now = Date.now();
|
|
649
|
+
const result = new Set(board.serverDeletedItemIds);
|
|
650
|
+
for (const id of Array.from(board.locallyDeletedItemIds.keys())) {
|
|
651
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
652
|
+
result.add(id);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
621
657
|
function getItemId(item) {
|
|
622
658
|
if (item instanceof Y__namespace.Map) {
|
|
623
659
|
const id2 = item.get("id");
|
|
@@ -698,6 +734,38 @@ function normalizeRealtimeItem(item) {
|
|
|
698
734
|
normalized.childrenSvg = "";
|
|
699
735
|
return normalized;
|
|
700
736
|
}
|
|
737
|
+
function reorderYItemsToMatchLocalItems(yItems, items) {
|
|
738
|
+
const currentItems = readVectorItems(yItems);
|
|
739
|
+
const currentItemsById = /* @__PURE__ */ new Map();
|
|
740
|
+
for (const currentItem of currentItems) {
|
|
741
|
+
const id = getItemId(currentItem);
|
|
742
|
+
if (id) currentItemsById.set(id, currentItem);
|
|
743
|
+
}
|
|
744
|
+
const orderedItems = [];
|
|
745
|
+
const orderedIds = /* @__PURE__ */ new Set();
|
|
746
|
+
for (const item of items) {
|
|
747
|
+
const id = getItemId(item);
|
|
748
|
+
if (!id) continue;
|
|
749
|
+
const currentItem = currentItemsById.get(id);
|
|
750
|
+
if (!currentItem) continue;
|
|
751
|
+
orderedItems.push(currentItem);
|
|
752
|
+
orderedIds.add(id);
|
|
753
|
+
}
|
|
754
|
+
for (const currentItem of currentItems) {
|
|
755
|
+
const id = getItemId(currentItem);
|
|
756
|
+
if (!id || !orderedIds.has(id)) orderedItems.push(currentItem);
|
|
757
|
+
}
|
|
758
|
+
const alreadyOrdered = currentItems.length === orderedItems.length && currentItems.every((currentItem, index) => {
|
|
759
|
+
const nextItem = orderedItems[index];
|
|
760
|
+
return nextItem ? getItemId(currentItem) === getItemId(nextItem) : false;
|
|
761
|
+
});
|
|
762
|
+
if (alreadyOrdered) return;
|
|
763
|
+
if (yItems.length > 0) yItems.delete(0, yItems.length);
|
|
764
|
+
yItems.insert(
|
|
765
|
+
0,
|
|
766
|
+
orderedItems.map((item) => vectorItemToYMap(item))
|
|
767
|
+
);
|
|
768
|
+
}
|
|
701
769
|
function applyLocalItemsToYDoc(board, options) {
|
|
702
770
|
const { items, origin } = options;
|
|
703
771
|
const addedIds = [];
|
|
@@ -714,9 +782,11 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
714
782
|
const toDelete = [];
|
|
715
783
|
for (const [id, entry] of currentIndex) {
|
|
716
784
|
if (nextIds.has(id)) continue;
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
785
|
+
if (!board.consumerSeenItemIds.has(id)) {
|
|
786
|
+
const serverSeenAt = board.serverItemSeenAt.get(id);
|
|
787
|
+
if (serverSeenAt != null && now - serverSeenAt < SERVER_ADD_RACE_WINDOW_MS) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
720
790
|
}
|
|
721
791
|
toDelete.push({ id, index: entry.index });
|
|
722
792
|
}
|
|
@@ -724,6 +794,7 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
724
794
|
for (const { id, index } of toDelete) {
|
|
725
795
|
board.yItems.delete(index, 1);
|
|
726
796
|
board.serverItemSeenAt.delete(id);
|
|
797
|
+
board.locallyDeletedItemIds.set(id, now);
|
|
727
798
|
removedIds.push(id);
|
|
728
799
|
}
|
|
729
800
|
const refreshedIndex = indexYItemsById(board.yItems);
|
|
@@ -732,6 +803,9 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
732
803
|
if (!item) continue;
|
|
733
804
|
const id = getItemId(item);
|
|
734
805
|
if (!id) continue;
|
|
806
|
+
board.consumerSeenItemIds.add(id);
|
|
807
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
808
|
+
board.locallyDeletedItemIds.delete(id);
|
|
735
809
|
const existing = refreshedIndex.get(id);
|
|
736
810
|
if (existing) {
|
|
737
811
|
updateYMapInPlace(existing.yMap, item);
|
|
@@ -741,16 +815,38 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
741
815
|
board.yItems.push([yMap]);
|
|
742
816
|
addedIds.push(id);
|
|
743
817
|
}
|
|
818
|
+
reorderYItemsToMatchLocalItems(board.yItems, items);
|
|
744
819
|
}, origin);
|
|
745
820
|
return { addedIds, removedIds };
|
|
746
821
|
}
|
|
747
822
|
function applyServerSnapshotToYDoc(board, options) {
|
|
748
|
-
const { items: snapshotItems, origin } = options;
|
|
823
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
749
824
|
const now = Date.now();
|
|
825
|
+
const snapshotItemIds = /* @__PURE__ */ new Set();
|
|
826
|
+
for (const item of snapshotItems) {
|
|
827
|
+
const id = getItemId(item);
|
|
828
|
+
if (id) snapshotItemIds.add(id);
|
|
829
|
+
}
|
|
750
830
|
board.doc.transact(() => {
|
|
831
|
+
if (deletedItemIds) {
|
|
832
|
+
for (const id of deletedItemIds) {
|
|
833
|
+
if (snapshotItemIds.has(id)) continue;
|
|
834
|
+
board.serverDeletedItemIds.add(id);
|
|
835
|
+
board.locallyDeletedItemIds.delete(id);
|
|
836
|
+
board.serverItemSeenAt.delete(id);
|
|
837
|
+
const existing = indexYItemsById(board.yItems).get(id);
|
|
838
|
+
if (existing) {
|
|
839
|
+
board.yItems.delete(existing.index, 1);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
751
843
|
for (const item of snapshotItems) {
|
|
752
844
|
const id = getItemId(item);
|
|
753
845
|
if (!id) continue;
|
|
846
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
847
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
754
850
|
const existing = indexYItemsById(board.yItems).get(id);
|
|
755
851
|
if (existing) {
|
|
756
852
|
const currentSerialized = serializeItem(existing.yMap);
|
|
@@ -781,7 +877,7 @@ function applyServerSnapshotToYDoc(board, options) {
|
|
|
781
877
|
}
|
|
782
878
|
}
|
|
783
879
|
function replaceYDocWithSnapshot(board, options) {
|
|
784
|
-
const { items: snapshotItems, origin } = options;
|
|
880
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
785
881
|
const now = Date.now();
|
|
786
882
|
board.doc.transact(() => {
|
|
787
883
|
if (board.yItems.length > 0) {
|
|
@@ -794,6 +890,12 @@ function replaceYDocWithSnapshot(board, options) {
|
|
|
794
890
|
board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
|
|
795
891
|
board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
|
|
796
892
|
board.serverItemSeenAt = /* @__PURE__ */ new Map();
|
|
893
|
+
board.locallyDeletedItemIds = /* @__PURE__ */ new Map();
|
|
894
|
+
if (deletedItemIds) {
|
|
895
|
+
for (const id of deletedItemIds) {
|
|
896
|
+
board.serverDeletedItemIds.add(id);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
797
899
|
for (const item of snapshotItems) {
|
|
798
900
|
const id = getItemId(item);
|
|
799
901
|
if (id) {
|
|
@@ -1240,7 +1342,7 @@ function useRealtimeSession(options) {
|
|
|
1240
1342
|
const applyDocument = react.useCallback(
|
|
1241
1343
|
(snapshot, options2) => {
|
|
1242
1344
|
const currentSnapshot = latestDocumentRef.current;
|
|
1243
|
-
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && currentSnapshot.persistedRevision === snapshot.persistedRevision && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
1345
|
+
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && currentSnapshot.persistedRevision === snapshot.persistedRevision && (currentSnapshot.deletedItemIds?.length ?? 0) === (snapshot.deletedItemIds?.length ?? 0) && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
1244
1346
|
return;
|
|
1245
1347
|
}
|
|
1246
1348
|
const board = boardRef.current;
|
|
@@ -1259,11 +1361,13 @@ function useRealtimeSession(options) {
|
|
|
1259
1361
|
if (options2?.replace) {
|
|
1260
1362
|
replaceYDocWithSnapshot(board, {
|
|
1261
1363
|
items: snapshot.items,
|
|
1364
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
1262
1365
|
origin: ORIGIN_REMOTE
|
|
1263
1366
|
});
|
|
1264
1367
|
} else {
|
|
1265
1368
|
applyServerSnapshotToYDoc(board, {
|
|
1266
1369
|
items: snapshot.items,
|
|
1370
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
1267
1371
|
origin: ORIGIN_REMOTE
|
|
1268
1372
|
});
|
|
1269
1373
|
}
|
|
@@ -1271,7 +1375,11 @@ function useRealtimeSession(options) {
|
|
|
1271
1375
|
const mergedItems = board ? readVectorItems(board.yItems) : snapshot.items;
|
|
1272
1376
|
const mergedSnapshot = {
|
|
1273
1377
|
...snapshot,
|
|
1274
|
-
items: mergedItems
|
|
1378
|
+
items: mergedItems,
|
|
1379
|
+
// Expose the accumulated tombstones (server-confirmed + pending
|
|
1380
|
+
// local deletes) so document consumers can prune stale local
|
|
1381
|
+
// views instead of re-sending deleted items.
|
|
1382
|
+
...board ? { deletedItemIds: Array.from(getRealtimeDeletedItemIds(board)) } : {}
|
|
1275
1383
|
};
|
|
1276
1384
|
currentRevisionRef.current = snapshot.revision;
|
|
1277
1385
|
latestDocumentRef.current = mergedSnapshot;
|