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/realtime.cjs
CHANGED
|
@@ -526,12 +526,16 @@ function parseDocumentSnapshot(value) {
|
|
|
526
526
|
const updatedAt = getNumber(value.updatedAt);
|
|
527
527
|
const items = parseItems(value.items);
|
|
528
528
|
if (revision == null || updatedAt == null || items == null) return void 0;
|
|
529
|
+
const deletedItemIds = Array.isArray(value.deletedItemIds) ? value.deletedItemIds.filter(
|
|
530
|
+
(entry) => typeof entry === "string"
|
|
531
|
+
) : void 0;
|
|
529
532
|
return {
|
|
530
533
|
revision,
|
|
531
534
|
updatedAt,
|
|
532
535
|
items,
|
|
533
536
|
...getString(value.updatedByClientId) ? { updatedByClientId: getString(value.updatedByClientId) } : {},
|
|
534
|
-
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {}
|
|
537
|
+
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {},
|
|
538
|
+
...deletedItemIds ? { deletedItemIds } : {}
|
|
535
539
|
};
|
|
536
540
|
}
|
|
537
541
|
function parseRealtimeSessionPeer(value) {
|
|
@@ -2029,6 +2033,16 @@ var CLIENT_ONLY_IMAGE_KEYS = /* @__PURE__ */ new Set([
|
|
|
2029
2033
|
"imageThumbnailHref"
|
|
2030
2034
|
]);
|
|
2031
2035
|
var SERVER_ADD_RACE_WINDOW_MS = 2e3;
|
|
2036
|
+
var LOCAL_DELETE_TOMBSTONE_TTL_MS = 1e4;
|
|
2037
|
+
function isLocalDeleteTombstoneActive(board, id, now) {
|
|
2038
|
+
const deletedAt = board.locallyDeletedItemIds.get(id);
|
|
2039
|
+
if (deletedAt == null) return false;
|
|
2040
|
+
if (now - deletedAt >= LOCAL_DELETE_TOMBSTONE_TTL_MS) {
|
|
2041
|
+
board.locallyDeletedItemIds.delete(id);
|
|
2042
|
+
return false;
|
|
2043
|
+
}
|
|
2044
|
+
return true;
|
|
2045
|
+
}
|
|
2032
2046
|
function createYjsBoardDoc() {
|
|
2033
2047
|
const doc = new Y__namespace.Doc();
|
|
2034
2048
|
const yItems = doc.getArray(ITEMS_KEY);
|
|
@@ -2037,9 +2051,22 @@ function createYjsBoardDoc() {
|
|
|
2037
2051
|
yItems,
|
|
2038
2052
|
lastServerConfirmedIds: /* @__PURE__ */ new Set(),
|
|
2039
2053
|
lastServerConfirmedItemSerializations: /* @__PURE__ */ new Map(),
|
|
2040
|
-
serverItemSeenAt: /* @__PURE__ */ new Map()
|
|
2054
|
+
serverItemSeenAt: /* @__PURE__ */ new Map(),
|
|
2055
|
+
serverDeletedItemIds: /* @__PURE__ */ new Set(),
|
|
2056
|
+
locallyDeletedItemIds: /* @__PURE__ */ new Map(),
|
|
2057
|
+
consumerSeenItemIds: /* @__PURE__ */ new Set()
|
|
2041
2058
|
};
|
|
2042
2059
|
}
|
|
2060
|
+
function getRealtimeDeletedItemIds(board) {
|
|
2061
|
+
const now = Date.now();
|
|
2062
|
+
const result = new Set(board.serverDeletedItemIds);
|
|
2063
|
+
for (const id of Array.from(board.locallyDeletedItemIds.keys())) {
|
|
2064
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
2065
|
+
result.add(id);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
return result;
|
|
2069
|
+
}
|
|
2043
2070
|
function getItemId(item) {
|
|
2044
2071
|
if (item instanceof Y__namespace.Map) {
|
|
2045
2072
|
const id2 = item.get("id");
|
|
@@ -2120,6 +2147,38 @@ function normalizeRealtimeItem(item) {
|
|
|
2120
2147
|
normalized.childrenSvg = "";
|
|
2121
2148
|
return normalized;
|
|
2122
2149
|
}
|
|
2150
|
+
function reorderYItemsToMatchLocalItems(yItems, items) {
|
|
2151
|
+
const currentItems = readVectorItems(yItems);
|
|
2152
|
+
const currentItemsById = /* @__PURE__ */ new Map();
|
|
2153
|
+
for (const currentItem of currentItems) {
|
|
2154
|
+
const id = getItemId(currentItem);
|
|
2155
|
+
if (id) currentItemsById.set(id, currentItem);
|
|
2156
|
+
}
|
|
2157
|
+
const orderedItems = [];
|
|
2158
|
+
const orderedIds = /* @__PURE__ */ new Set();
|
|
2159
|
+
for (const item of items) {
|
|
2160
|
+
const id = getItemId(item);
|
|
2161
|
+
if (!id) continue;
|
|
2162
|
+
const currentItem = currentItemsById.get(id);
|
|
2163
|
+
if (!currentItem) continue;
|
|
2164
|
+
orderedItems.push(currentItem);
|
|
2165
|
+
orderedIds.add(id);
|
|
2166
|
+
}
|
|
2167
|
+
for (const currentItem of currentItems) {
|
|
2168
|
+
const id = getItemId(currentItem);
|
|
2169
|
+
if (!id || !orderedIds.has(id)) orderedItems.push(currentItem);
|
|
2170
|
+
}
|
|
2171
|
+
const alreadyOrdered = currentItems.length === orderedItems.length && currentItems.every((currentItem, index) => {
|
|
2172
|
+
const nextItem = orderedItems[index];
|
|
2173
|
+
return nextItem ? getItemId(currentItem) === getItemId(nextItem) : false;
|
|
2174
|
+
});
|
|
2175
|
+
if (alreadyOrdered) return;
|
|
2176
|
+
if (yItems.length > 0) yItems.delete(0, yItems.length);
|
|
2177
|
+
yItems.insert(
|
|
2178
|
+
0,
|
|
2179
|
+
orderedItems.map((item) => vectorItemToYMap(item))
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2123
2182
|
function applyLocalItemsToYDoc(board, options) {
|
|
2124
2183
|
const { items, origin } = options;
|
|
2125
2184
|
const addedIds = [];
|
|
@@ -2136,9 +2195,11 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2136
2195
|
const toDelete = [];
|
|
2137
2196
|
for (const [id, entry] of currentIndex) {
|
|
2138
2197
|
if (nextIds.has(id)) continue;
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2198
|
+
if (!board.consumerSeenItemIds.has(id)) {
|
|
2199
|
+
const serverSeenAt = board.serverItemSeenAt.get(id);
|
|
2200
|
+
if (serverSeenAt != null && now - serverSeenAt < SERVER_ADD_RACE_WINDOW_MS) {
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2142
2203
|
}
|
|
2143
2204
|
toDelete.push({ id, index: entry.index });
|
|
2144
2205
|
}
|
|
@@ -2146,6 +2207,7 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2146
2207
|
for (const { id, index } of toDelete) {
|
|
2147
2208
|
board.yItems.delete(index, 1);
|
|
2148
2209
|
board.serverItemSeenAt.delete(id);
|
|
2210
|
+
board.locallyDeletedItemIds.set(id, now);
|
|
2149
2211
|
removedIds.push(id);
|
|
2150
2212
|
}
|
|
2151
2213
|
const refreshedIndex = indexYItemsById(board.yItems);
|
|
@@ -2154,6 +2216,9 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2154
2216
|
if (!item) continue;
|
|
2155
2217
|
const id = getItemId(item);
|
|
2156
2218
|
if (!id) continue;
|
|
2219
|
+
board.consumerSeenItemIds.add(id);
|
|
2220
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
2221
|
+
board.locallyDeletedItemIds.delete(id);
|
|
2157
2222
|
const existing = refreshedIndex.get(id);
|
|
2158
2223
|
if (existing) {
|
|
2159
2224
|
updateYMapInPlace(existing.yMap, item);
|
|
@@ -2163,16 +2228,38 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2163
2228
|
board.yItems.push([yMap]);
|
|
2164
2229
|
addedIds.push(id);
|
|
2165
2230
|
}
|
|
2231
|
+
reorderYItemsToMatchLocalItems(board.yItems, items);
|
|
2166
2232
|
}, origin);
|
|
2167
2233
|
return { addedIds, removedIds };
|
|
2168
2234
|
}
|
|
2169
2235
|
function applyServerSnapshotToYDoc(board, options) {
|
|
2170
|
-
const { items: snapshotItems, origin } = options;
|
|
2236
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
2171
2237
|
const now = Date.now();
|
|
2238
|
+
const snapshotItemIds = /* @__PURE__ */ new Set();
|
|
2239
|
+
for (const item of snapshotItems) {
|
|
2240
|
+
const id = getItemId(item);
|
|
2241
|
+
if (id) snapshotItemIds.add(id);
|
|
2242
|
+
}
|
|
2172
2243
|
board.doc.transact(() => {
|
|
2244
|
+
if (deletedItemIds) {
|
|
2245
|
+
for (const id of deletedItemIds) {
|
|
2246
|
+
if (snapshotItemIds.has(id)) continue;
|
|
2247
|
+
board.serverDeletedItemIds.add(id);
|
|
2248
|
+
board.locallyDeletedItemIds.delete(id);
|
|
2249
|
+
board.serverItemSeenAt.delete(id);
|
|
2250
|
+
const existing = indexYItemsById(board.yItems).get(id);
|
|
2251
|
+
if (existing) {
|
|
2252
|
+
board.yItems.delete(existing.index, 1);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2173
2256
|
for (const item of snapshotItems) {
|
|
2174
2257
|
const id = getItemId(item);
|
|
2175
2258
|
if (!id) continue;
|
|
2259
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
2260
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2176
2263
|
const existing = indexYItemsById(board.yItems).get(id);
|
|
2177
2264
|
if (existing) {
|
|
2178
2265
|
const currentSerialized = serializeItem(existing.yMap);
|
|
@@ -2203,7 +2290,7 @@ function applyServerSnapshotToYDoc(board, options) {
|
|
|
2203
2290
|
}
|
|
2204
2291
|
}
|
|
2205
2292
|
function replaceYDocWithSnapshot(board, options) {
|
|
2206
|
-
const { items: snapshotItems, origin } = options;
|
|
2293
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
2207
2294
|
const now = Date.now();
|
|
2208
2295
|
board.doc.transact(() => {
|
|
2209
2296
|
if (board.yItems.length > 0) {
|
|
@@ -2216,6 +2303,12 @@ function replaceYDocWithSnapshot(board, options) {
|
|
|
2216
2303
|
board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
|
|
2217
2304
|
board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
|
|
2218
2305
|
board.serverItemSeenAt = /* @__PURE__ */ new Map();
|
|
2306
|
+
board.locallyDeletedItemIds = /* @__PURE__ */ new Map();
|
|
2307
|
+
if (deletedItemIds) {
|
|
2308
|
+
for (const id of deletedItemIds) {
|
|
2309
|
+
board.serverDeletedItemIds.add(id);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2219
2312
|
for (const item of snapshotItems) {
|
|
2220
2313
|
const id = getItemId(item);
|
|
2221
2314
|
if (id) {
|
|
@@ -2578,7 +2671,7 @@ function useRealtimeSession(options) {
|
|
|
2578
2671
|
const applyDocument = react.useCallback(
|
|
2579
2672
|
(snapshot, options2) => {
|
|
2580
2673
|
const currentSnapshot = latestDocumentRef.current;
|
|
2581
|
-
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && currentSnapshot.persistedRevision === snapshot.persistedRevision && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
2674
|
+
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)) {
|
|
2582
2675
|
return;
|
|
2583
2676
|
}
|
|
2584
2677
|
const board = boardRef.current;
|
|
@@ -2597,11 +2690,13 @@ function useRealtimeSession(options) {
|
|
|
2597
2690
|
if (options2?.replace) {
|
|
2598
2691
|
replaceYDocWithSnapshot(board, {
|
|
2599
2692
|
items: snapshot.items,
|
|
2693
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
2600
2694
|
origin: ORIGIN_REMOTE
|
|
2601
2695
|
});
|
|
2602
2696
|
} else {
|
|
2603
2697
|
applyServerSnapshotToYDoc(board, {
|
|
2604
2698
|
items: snapshot.items,
|
|
2699
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
2605
2700
|
origin: ORIGIN_REMOTE
|
|
2606
2701
|
});
|
|
2607
2702
|
}
|
|
@@ -2609,7 +2704,11 @@ function useRealtimeSession(options) {
|
|
|
2609
2704
|
const mergedItems = board ? readVectorItems(board.yItems) : snapshot.items;
|
|
2610
2705
|
const mergedSnapshot = {
|
|
2611
2706
|
...snapshot,
|
|
2612
|
-
items: mergedItems
|
|
2707
|
+
items: mergedItems,
|
|
2708
|
+
// Expose the accumulated tombstones (server-confirmed + pending
|
|
2709
|
+
// local deletes) so document consumers can prune stale local
|
|
2710
|
+
// views instead of re-sending deleted items.
|
|
2711
|
+
...board ? { deletedItemIds: Array.from(getRealtimeDeletedItemIds(board)) } : {}
|
|
2613
2712
|
};
|
|
2614
2713
|
currentRevisionRef.current = snapshot.revision;
|
|
2615
2714
|
latestDocumentRef.current = mergedSnapshot;
|
|
@@ -3734,6 +3833,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3734
3833
|
const realtimeEnabled = enabled && session != null;
|
|
3735
3834
|
const documentRevision = session?.document?.revision ?? null;
|
|
3736
3835
|
const documentItems = session?.document?.items;
|
|
3836
|
+
const documentDeletedItemIds = session?.document?.deletedItemIds;
|
|
3737
3837
|
const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
|
|
3738
3838
|
const connectionClientId = session?.connection.clientId ?? null;
|
|
3739
3839
|
const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
|
|
@@ -3786,15 +3886,22 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3786
3886
|
if (cancelled) return;
|
|
3787
3887
|
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
3788
3888
|
lastAppliedRevisionRef.current = documentRevision;
|
|
3789
|
-
const
|
|
3889
|
+
const rawLocalItems = latestItemsRef.current;
|
|
3890
|
+
const deletedIds = new Set(documentDeletedItemIds ?? []);
|
|
3891
|
+
const localItems = deletedIds.size > 0 ? rawLocalItems.filter((item) => {
|
|
3892
|
+
const id = getSceneItemId(item);
|
|
3893
|
+
return !id || !deletedIds.has(id);
|
|
3894
|
+
}) : rawLocalItems;
|
|
3790
3895
|
const hasLocalItems = localItems.length > 0;
|
|
3791
3896
|
const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync || hasLocalChangeInFlightRef.current;
|
|
3792
3897
|
if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
|
|
3793
3898
|
if (hasLocalItems) {
|
|
3794
3899
|
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
3795
3900
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
3901
|
+
return;
|
|
3796
3902
|
}
|
|
3797
|
-
|
|
3903
|
+
const emptinessExplainedByDeletes = rawLocalItems.length > 0 && localItems.length === 0;
|
|
3904
|
+
if (!emptinessExplainedByDeletes) return;
|
|
3798
3905
|
}
|
|
3799
3906
|
if (shouldPreserveLocalRealtimeItems({
|
|
3800
3907
|
localItems,
|
|
@@ -3822,6 +3929,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3822
3929
|
}, [
|
|
3823
3930
|
applyIncomingItems,
|
|
3824
3931
|
connectionClientId,
|
|
3932
|
+
documentDeletedItemIds,
|
|
3825
3933
|
documentItems,
|
|
3826
3934
|
documentRevision,
|
|
3827
3935
|
documentUpdatedByClientId,
|