canvu-react 0.4.7 → 0.4.9

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.js CHANGED
@@ -1996,6 +1996,7 @@ function createYjsBoardDoc() {
1996
1996
  doc,
1997
1997
  yItems,
1998
1998
  lastServerConfirmedIds: /* @__PURE__ */ new Set(),
1999
+ lastServerConfirmedItemSerializations: /* @__PURE__ */ new Map(),
1999
2000
  serverItemSeenAt: /* @__PURE__ */ new Map()
2000
2001
  };
2001
2002
  }
@@ -2052,6 +2053,10 @@ function valuesEqual(left, right) {
2052
2053
  return false;
2053
2054
  }
2054
2055
  }
2056
+ function serializeItem(value) {
2057
+ const item = value instanceof Y.Map ? yMapToVectorItem(value) : value;
2058
+ return JSON.stringify(item);
2059
+ }
2055
2060
  function updateYMapInPlace(yMap, next) {
2056
2061
  const nextKeys = /* @__PURE__ */ new Set();
2057
2062
  for (const [key, value] of Object.entries(next)) {
@@ -2121,6 +2126,14 @@ function applyServerSnapshotToYDoc(board, options) {
2121
2126
  if (!id) continue;
2122
2127
  const existing = indexYItemsById(board.yItems).get(id);
2123
2128
  if (existing) {
2129
+ const currentSerialized = serializeItem(existing.yMap);
2130
+ const incomingSerialized = serializeItem(item);
2131
+ const confirmedSerialized = board.lastServerConfirmedItemSerializations.get(id);
2132
+ const hasPendingLocalChange = confirmedSerialized === void 0 ? currentSerialized !== incomingSerialized : currentSerialized !== confirmedSerialized && currentSerialized !== incomingSerialized;
2133
+ if (hasPendingLocalChange) {
2134
+ board.serverItemSeenAt.set(id, now);
2135
+ continue;
2136
+ }
2124
2137
  updateYMapInPlace(existing.yMap, item);
2125
2138
  board.serverItemSeenAt.set(id, now);
2126
2139
  continue;
@@ -2131,9 +2144,13 @@ function applyServerSnapshotToYDoc(board, options) {
2131
2144
  }
2132
2145
  }, origin);
2133
2146
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2147
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2134
2148
  for (const item of snapshotItems) {
2135
2149
  const id = getItemId(item);
2136
- if (id) board.lastServerConfirmedIds.add(id);
2150
+ if (id) {
2151
+ board.lastServerConfirmedIds.add(id);
2152
+ board.lastServerConfirmedItemSerializations.set(id, serializeItem(item));
2153
+ }
2137
2154
  }
2138
2155
  }
2139
2156
  function replaceYDocWithSnapshot(board, options) {
@@ -2148,11 +2165,13 @@ function replaceYDocWithSnapshot(board, options) {
2148
2165
  }
2149
2166
  }, origin);
2150
2167
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2168
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2151
2169
  board.serverItemSeenAt = /* @__PURE__ */ new Map();
2152
2170
  for (const item of snapshotItems) {
2153
2171
  const id = getItemId(item);
2154
2172
  if (id) {
2155
2173
  board.lastServerConfirmedIds.add(id);
2174
+ board.lastServerConfirmedItemSerializations.set(id, serializeItem(item));
2156
2175
  board.serverItemSeenAt.set(id, now);
2157
2176
  }
2158
2177
  }
@@ -2164,7 +2183,8 @@ function getLocallyPendingItemIds(board) {
2164
2183
  if (!yMap) continue;
2165
2184
  const id = getItemId(yMap);
2166
2185
  if (!id) continue;
2167
- if (!board.lastServerConfirmedIds.has(id)) {
2186
+ const confirmedSerialized = board.lastServerConfirmedItemSerializations.get(id);
2187
+ if (!confirmedSerialized || serializeItem(yMap) !== confirmedSerialized) {
2168
2188
  pending.add(id);
2169
2189
  }
2170
2190
  }
@@ -2353,6 +2373,30 @@ function prepareRealtimeItems(items) {
2353
2373
  return null;
2354
2374
  }
2355
2375
  }
2376
+ function getRealtimeItemId(item) {
2377
+ const id = item.id;
2378
+ return typeof id === "string" ? id : null;
2379
+ }
2380
+ function serializeRealtimeItem(item) {
2381
+ try {
2382
+ return JSON.stringify(sanitizeRealtimeItem(item));
2383
+ } catch {
2384
+ return null;
2385
+ }
2386
+ }
2387
+ function resolvePendingDraftItemIds(board, items) {
2388
+ const pendingIds = /* @__PURE__ */ new Set();
2389
+ for (const item of items) {
2390
+ const id = getRealtimeItemId(item);
2391
+ if (!id) continue;
2392
+ const confirmedSerialized = board.lastServerConfirmedItemSerializations.get(id);
2393
+ const serialized = serializeRealtimeItem(item);
2394
+ if (!confirmedSerialized || !serialized || serialized !== confirmedSerialized) {
2395
+ pendingIds.add(id);
2396
+ }
2397
+ }
2398
+ return pendingIds.size > 0 ? Array.from(pendingIds) : void 0;
2399
+ }
2356
2400
  function useRealtimeSession(options) {
2357
2401
  const {
2358
2402
  url,
@@ -2543,7 +2587,8 @@ function useRealtimeSession(options) {
2543
2587
  }
2544
2588
  const board = boardRef.current;
2545
2589
  const draft = localDraftRef.current;
2546
- const draftToWrite = board && draft.yDocState == null ? { ...draft, yDocState: encodeYDocState(board) } : draft;
2590
+ const canEncodeYDocState = board && draft.yDocState == null && sameSerializedItems(readVectorItems(board.yItems), draft.items);
2591
+ const draftToWrite = canEncodeYDocState ? { ...draft, yDocState: encodeYDocState(board) } : draft;
2547
2592
  writeRealtimeOfflineDraft(draftToWrite);
2548
2593
  }, [clearDraftPersistSchedule, roomId]);
2549
2594
  const scheduleDraftPersistence = useCallback(() => {
@@ -2683,13 +2728,26 @@ function useRealtimeSession(options) {
2683
2728
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2684
2729
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2685
2730
  }, [clearDocumentFlushSchedule, flushQueuedDocument]);
2686
- const queueDocumentSend = useCallback((items) => {
2687
- pendingLocalItemsRef.current = items;
2688
- queuedDirtyRef.current = true;
2689
- setHasPendingDocumentSync(true);
2690
- setHasLocalOfflineDraft(true);
2691
- scheduleDocumentFlushRef.current();
2692
- }, []);
2731
+ const queueDocumentSend = useCallback(
2732
+ (items) => {
2733
+ pendingLocalItemsRef.current = items;
2734
+ queuedDirtyRef.current = true;
2735
+ setHasPendingDocumentSync(true);
2736
+ setHasLocalOfflineDraft(true);
2737
+ const board = boardRef.current;
2738
+ const draftItems = sanitizeRealtimeItems(items);
2739
+ setLocalDraftRef.current({
2740
+ roomId,
2741
+ baseRevision: currentRevisionRef.current,
2742
+ items: draftItems,
2743
+ updatedAt: nowMs(),
2744
+ pendingIds: board ? resolvePendingDraftItemIds(board, draftItems) : void 0
2745
+ });
2746
+ scheduleDraftPersistenceRef.current?.();
2747
+ scheduleDocumentFlushRef.current();
2748
+ },
2749
+ [roomId]
2750
+ );
2693
2751
  const applyDraftSnapshot = useCallback(
2694
2752
  (draft, options2) => {
2695
2753
  const board = boardRef.current;
@@ -2704,6 +2762,7 @@ function useRealtimeSession(options) {
2704
2762
  restoredFromBinary = decodeYDocState(board, draft.yDocState);
2705
2763
  if (restoredFromBinary) {
2706
2764
  const allIds = /* @__PURE__ */ new Set();
2765
+ const allItems = readVectorItems(board.yItems);
2707
2766
  for (let i = 0; i < board.yItems.length; i++) {
2708
2767
  const yMap = board.yItems.get(i);
2709
2768
  if (!yMap) continue;
@@ -2712,9 +2771,17 @@ function useRealtimeSession(options) {
2712
2771
  }
2713
2772
  const pendingIds = new Set(draft.pendingIds ?? []);
2714
2773
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2774
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2715
2775
  for (const id of allIds) {
2716
2776
  if (!pendingIds.has(id)) {
2717
2777
  board.lastServerConfirmedIds.add(id);
2778
+ const item = allItems.find((candidate) => candidate.id === id);
2779
+ if (item) {
2780
+ board.lastServerConfirmedItemSerializations.set(
2781
+ id,
2782
+ JSON.stringify(item)
2783
+ );
2784
+ }
2718
2785
  }
2719
2786
  }
2720
2787
  }
@@ -2725,6 +2792,7 @@ function useRealtimeSession(options) {
2725
2792
  origin: ORIGIN_BOOTSTRAP
2726
2793
  });
2727
2794
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2795
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2728
2796
  }
2729
2797
  }
2730
2798
  const items = board ? readVectorItems(board.yItems) : draft.items;
@@ -2843,6 +2911,25 @@ function useRealtimeSession(options) {
2843
2911
  setLocalDraftRef.current = setLocalDraft;
2844
2912
  const scheduleDraftPersistenceRef = useRef(scheduleDraftPersistence);
2845
2913
  scheduleDraftPersistenceRef.current = scheduleDraftPersistence;
2914
+ const persistLocalDraftRef = useRef(persistLocalDraft);
2915
+ persistLocalDraftRef.current = persistLocalDraft;
2916
+ useEffect(() => {
2917
+ const persistDraft = () => {
2918
+ persistLocalDraftRef.current();
2919
+ };
2920
+ const handleVisibilityChange = () => {
2921
+ if (window.document.visibilityState === "hidden") persistDraft();
2922
+ };
2923
+ window.addEventListener("pagehide", persistDraft);
2924
+ window.document.addEventListener("visibilitychange", handleVisibilityChange);
2925
+ return () => {
2926
+ window.removeEventListener("pagehide", persistDraft);
2927
+ window.document.removeEventListener(
2928
+ "visibilitychange",
2929
+ handleVisibilityChange
2930
+ );
2931
+ };
2932
+ }, []);
2846
2933
  useEffect(() => {
2847
2934
  if (boardRef.current) {
2848
2935
  boardRef.current.doc.destroy();