canvu-react 0.3.11 → 0.3.13
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/index.cjs +94 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -48
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +94 -48
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +94 -48
- package/dist/react.js.map +1 -1
- package/dist/realtime.cjs +88 -55
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +88 -55
- package/dist/realtime.js.map +1 -1
- package/package.json +1 -1
package/dist/realtime.js
CHANGED
|
@@ -2072,6 +2072,10 @@ function useRealtimeSession(options) {
|
|
|
2072
2072
|
}, []);
|
|
2073
2073
|
const applyDocument = useCallback(
|
|
2074
2074
|
(snapshot, options2) => {
|
|
2075
|
+
const currentSnapshot = latestDocumentRef.current;
|
|
2076
|
+
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2075
2079
|
currentRevisionRef.current = snapshot.revision;
|
|
2076
2080
|
latestDocumentRef.current = snapshot;
|
|
2077
2081
|
setDocument(snapshot);
|
|
@@ -2198,7 +2202,7 @@ function useRealtimeSession(options) {
|
|
|
2198
2202
|
items: preparedItems.items,
|
|
2199
2203
|
serialized: preparedItems.serialized
|
|
2200
2204
|
};
|
|
2201
|
-
const didSend =
|
|
2205
|
+
const didSend = sendRawRef.current({
|
|
2202
2206
|
type: "document:update",
|
|
2203
2207
|
roomId,
|
|
2204
2208
|
clientId: clientIdRef.current,
|
|
@@ -2210,7 +2214,7 @@ function useRealtimeSession(options) {
|
|
|
2210
2214
|
outboundInFlightRef.current = null;
|
|
2211
2215
|
setHasPendingDocumentSync(true);
|
|
2212
2216
|
}
|
|
2213
|
-
}, [clearDocumentFlushSchedule, roomId
|
|
2217
|
+
}, [clearDocumentFlushSchedule, roomId]);
|
|
2214
2218
|
const scheduleDocumentFlush = useCallback(() => {
|
|
2215
2219
|
clearDocumentFlushSchedule();
|
|
2216
2220
|
documentFlushTimerRef.current = window.setTimeout(() => {
|
|
@@ -2233,9 +2237,9 @@ function useRealtimeSession(options) {
|
|
|
2233
2237
|
queuedItemsRef.current = items;
|
|
2234
2238
|
setHasPendingDocumentSync(true);
|
|
2235
2239
|
if (conflictRef.current) return;
|
|
2236
|
-
|
|
2240
|
+
scheduleDocumentFlushRef.current();
|
|
2237
2241
|
},
|
|
2238
|
-
[roomId,
|
|
2242
|
+
[roomId, scheduleDraftPersistence, setLocalDraft]
|
|
2239
2243
|
);
|
|
2240
2244
|
const applyDraftSnapshot = useCallback(
|
|
2241
2245
|
(draft, options2) => {
|
|
@@ -2253,7 +2257,7 @@ function useRealtimeSession(options) {
|
|
|
2253
2257
|
return false;
|
|
2254
2258
|
}
|
|
2255
2259
|
if (sameSerializedItems(localDraft.items, serverDocument.items)) {
|
|
2256
|
-
|
|
2260
|
+
clearLocalDraftRef.current();
|
|
2257
2261
|
setHasPendingDocumentSync(false);
|
|
2258
2262
|
setConflictState(null);
|
|
2259
2263
|
applyDocument(serverDocument, options2);
|
|
@@ -2280,13 +2284,7 @@ function useRealtimeSession(options) {
|
|
|
2280
2284
|
});
|
|
2281
2285
|
return true;
|
|
2282
2286
|
},
|
|
2283
|
-
[
|
|
2284
|
-
applyDocument,
|
|
2285
|
-
applyDraftSnapshot,
|
|
2286
|
-
clearLocalDraft,
|
|
2287
|
-
scheduleDocumentFlush,
|
|
2288
|
-
setConflictState
|
|
2289
|
-
]
|
|
2287
|
+
[applyDocument, applyDraftSnapshot, scheduleDocumentFlush, setConflictState]
|
|
2290
2288
|
);
|
|
2291
2289
|
const sendPresenceUpdate = useCallback(() => {
|
|
2292
2290
|
sendRaw({
|
|
@@ -2373,6 +2371,26 @@ function useRealtimeSession(options) {
|
|
|
2373
2371
|
setLocalDraft
|
|
2374
2372
|
]
|
|
2375
2373
|
);
|
|
2374
|
+
const setConflictStateRef = useRef(setConflictState);
|
|
2375
|
+
setConflictStateRef.current = setConflictState;
|
|
2376
|
+
const updateConnectionRef = useRef(updateConnection);
|
|
2377
|
+
updateConnectionRef.current = updateConnection;
|
|
2378
|
+
const applyDocumentRef = useRef(applyDocument);
|
|
2379
|
+
applyDocumentRef.current = applyDocument;
|
|
2380
|
+
const clearLocalDraftRef = useRef(clearLocalDraft);
|
|
2381
|
+
clearLocalDraftRef.current = clearLocalDraft;
|
|
2382
|
+
const collapsePeersToSelfRef = useRef(collapsePeersToSelf);
|
|
2383
|
+
collapsePeersToSelfRef.current = collapsePeersToSelf;
|
|
2384
|
+
const applyPeersRef = useRef(applyPeers);
|
|
2385
|
+
applyPeersRef.current = applyPeers;
|
|
2386
|
+
const resolveAuthoritativeDocumentRef = useRef(resolveAuthoritativeDocument);
|
|
2387
|
+
resolveAuthoritativeDocumentRef.current = resolveAuthoritativeDocument;
|
|
2388
|
+
const scheduleDocumentFlushRef = useRef(scheduleDocumentFlush);
|
|
2389
|
+
scheduleDocumentFlushRef.current = scheduleDocumentFlush;
|
|
2390
|
+
const scheduleReconnectRef = useRef(scheduleReconnect);
|
|
2391
|
+
scheduleReconnectRef.current = scheduleReconnect;
|
|
2392
|
+
const sendRawRef = useRef(sendRaw);
|
|
2393
|
+
sendRawRef.current = sendRaw;
|
|
2376
2394
|
useEffect(() => {
|
|
2377
2395
|
if (!roomId) {
|
|
2378
2396
|
clearDocumentFlushSchedule();
|
|
@@ -2426,8 +2444,8 @@ function useRealtimeSession(options) {
|
|
|
2426
2444
|
queuedItemsRef.current = localDraftRef.current?.items ?? null;
|
|
2427
2445
|
outboundInFlightRef.current = null;
|
|
2428
2446
|
setHasPendingDocumentSync(localDraftRef.current != null);
|
|
2429
|
-
|
|
2430
|
-
|
|
2447
|
+
collapsePeersToSelfRef.current("offline");
|
|
2448
|
+
updateConnectionRef.current((prev) => ({
|
|
2431
2449
|
...prev,
|
|
2432
2450
|
state: "offline",
|
|
2433
2451
|
connected: false,
|
|
@@ -2439,8 +2457,8 @@ function useRealtimeSession(options) {
|
|
|
2439
2457
|
manualDisconnectRef.current = false;
|
|
2440
2458
|
const socketUrl = normalizeSocketUrl(url);
|
|
2441
2459
|
if (!socketUrl) {
|
|
2442
|
-
|
|
2443
|
-
|
|
2460
|
+
collapsePeersToSelfRef.current("offline");
|
|
2461
|
+
updateConnectionRef.current((prev) => ({
|
|
2444
2462
|
...prev,
|
|
2445
2463
|
state: "offline",
|
|
2446
2464
|
connected: false,
|
|
@@ -2450,7 +2468,7 @@ function useRealtimeSession(options) {
|
|
|
2450
2468
|
return;
|
|
2451
2469
|
}
|
|
2452
2470
|
if (!isValidSocketUrl(socketUrl)) {
|
|
2453
|
-
|
|
2471
|
+
updateConnectionRef.current((prev) => ({
|
|
2454
2472
|
...prev,
|
|
2455
2473
|
state: "error",
|
|
2456
2474
|
connected: false,
|
|
@@ -2461,7 +2479,7 @@ function useRealtimeSession(options) {
|
|
|
2461
2479
|
return;
|
|
2462
2480
|
}
|
|
2463
2481
|
let disposed = false;
|
|
2464
|
-
|
|
2482
|
+
updateConnectionRef.current((prev) => ({
|
|
2465
2483
|
...prev,
|
|
2466
2484
|
state: retryCountRef.current > 0 ? "reconnecting" : "connecting",
|
|
2467
2485
|
connected: false,
|
|
@@ -2479,7 +2497,7 @@ function useRealtimeSession(options) {
|
|
|
2479
2497
|
socket.addEventListener("open", () => {
|
|
2480
2498
|
if (disposed) return;
|
|
2481
2499
|
clearConnectTimeout();
|
|
2482
|
-
|
|
2500
|
+
sendRawRef.current({
|
|
2483
2501
|
type: "session:join",
|
|
2484
2502
|
roomId,
|
|
2485
2503
|
peer: {
|
|
@@ -2492,7 +2510,7 @@ function useRealtimeSession(options) {
|
|
|
2492
2510
|
});
|
|
2493
2511
|
clearHeartbeatTimer();
|
|
2494
2512
|
heartbeatTimerRef.current = window.setInterval(() => {
|
|
2495
|
-
|
|
2513
|
+
sendRawRef.current({
|
|
2496
2514
|
type: "session:ping",
|
|
2497
2515
|
roomId,
|
|
2498
2516
|
clientId: clientIdRef.current,
|
|
@@ -2512,13 +2530,13 @@ function useRealtimeSession(options) {
|
|
|
2512
2530
|
}
|
|
2513
2531
|
const parsed = parseRealtimeServerMessage(payload);
|
|
2514
2532
|
if (!parsed) return;
|
|
2515
|
-
|
|
2533
|
+
updateConnectionRef.current((prev) => ({
|
|
2516
2534
|
...prev,
|
|
2517
2535
|
lastMessageAt: nowMs()
|
|
2518
2536
|
}));
|
|
2519
2537
|
if (parsed.type === "session:welcome") {
|
|
2520
2538
|
retryCountRef.current = 0;
|
|
2521
|
-
|
|
2539
|
+
updateConnectionRef.current((prev) => ({
|
|
2522
2540
|
...prev,
|
|
2523
2541
|
state: "connected",
|
|
2524
2542
|
connected: true,
|
|
@@ -2527,8 +2545,8 @@ function useRealtimeSession(options) {
|
|
|
2527
2545
|
lastConnectedAt: nowMs(),
|
|
2528
2546
|
lastError: null
|
|
2529
2547
|
}));
|
|
2530
|
-
|
|
2531
|
-
const handledByDraft =
|
|
2548
|
+
applyPeersRef.current(parsed.peers);
|
|
2549
|
+
const handledByDraft = resolveAuthoritativeDocumentRef.current(
|
|
2532
2550
|
sanitizeRealtimeSnapshot(parsed.document),
|
|
2533
2551
|
{
|
|
2534
2552
|
suppressSubscriberNotify: localDraftRef.current != null && sameSerializedItems(
|
|
@@ -2542,11 +2560,11 @@ function useRealtimeSession(options) {
|
|
|
2542
2560
|
outboundInFlightRef.current = null;
|
|
2543
2561
|
setHasPendingDocumentSync(false);
|
|
2544
2562
|
}
|
|
2545
|
-
|
|
2563
|
+
scheduleDocumentFlushRef.current();
|
|
2546
2564
|
return;
|
|
2547
2565
|
}
|
|
2548
2566
|
if (parsed.type === "presence:sync") {
|
|
2549
|
-
|
|
2567
|
+
applyPeersRef.current(parsed.peers);
|
|
2550
2568
|
return;
|
|
2551
2569
|
}
|
|
2552
2570
|
if (parsed.type === "session:peer-joined") {
|
|
@@ -2568,14 +2586,14 @@ function useRealtimeSession(options) {
|
|
|
2568
2586
|
return;
|
|
2569
2587
|
}
|
|
2570
2588
|
if (parsed.type === "session:pong") {
|
|
2571
|
-
|
|
2589
|
+
updateConnectionRef.current((prev) => ({
|
|
2572
2590
|
...prev,
|
|
2573
2591
|
lastPongAt: parsed.serverTime
|
|
2574
2592
|
}));
|
|
2575
2593
|
return;
|
|
2576
2594
|
}
|
|
2577
2595
|
if (parsed.type === "session:error") {
|
|
2578
|
-
|
|
2596
|
+
updateConnectionRef.current((prev) => ({
|
|
2579
2597
|
...prev,
|
|
2580
2598
|
state: prev.connected ? prev.state : "error",
|
|
2581
2599
|
lastError: parsed.message
|
|
@@ -2591,42 +2609,46 @@ function useRealtimeSession(options) {
|
|
|
2591
2609
|
outboundInFlightRef.current = null;
|
|
2592
2610
|
queuedItemsRef.current = localDraftRef.current?.items ?? null;
|
|
2593
2611
|
setHasPendingDocumentSync(queuedItemsRef.current != null);
|
|
2594
|
-
|
|
2612
|
+
resolveAuthoritativeDocumentRef.current(
|
|
2613
|
+
sanitizeRealtimeSnapshot(parsed.document)
|
|
2614
|
+
);
|
|
2595
2615
|
return;
|
|
2596
2616
|
}
|
|
2597
2617
|
const shouldSuppress = inFlight != null && sameSerializedItems(inFlight.items, parsed.document.items);
|
|
2598
2618
|
outboundInFlightRef.current = null;
|
|
2599
|
-
|
|
2619
|
+
applyDocumentRef.current(sanitizeRealtimeSnapshot(parsed.document), {
|
|
2600
2620
|
suppressSubscriberNotify: shouldSuppress
|
|
2601
2621
|
});
|
|
2602
2622
|
if (queuedItemsRef.current) {
|
|
2603
2623
|
if (sameSerializedItems(queuedItemsRef.current, parsed.document.items)) {
|
|
2604
2624
|
queuedItemsRef.current = null;
|
|
2605
2625
|
} else {
|
|
2606
|
-
|
|
2626
|
+
scheduleDocumentFlushRef.current();
|
|
2607
2627
|
}
|
|
2608
2628
|
}
|
|
2609
2629
|
if (!queuedItemsRef.current) {
|
|
2610
|
-
|
|
2630
|
+
clearLocalDraftRef.current();
|
|
2611
2631
|
}
|
|
2612
2632
|
setHasPendingDocumentSync(queuedItemsRef.current != null);
|
|
2613
|
-
|
|
2633
|
+
setConflictStateRef.current(null);
|
|
2614
2634
|
return;
|
|
2615
2635
|
}
|
|
2616
2636
|
if (parsed.type === "document:resync-required") {
|
|
2617
2637
|
outboundInFlightRef.current = null;
|
|
2618
2638
|
queuedItemsRef.current = localDraftRef.current?.items ?? null;
|
|
2619
2639
|
setHasPendingDocumentSync(queuedItemsRef.current != null);
|
|
2620
|
-
|
|
2640
|
+
updateConnectionRef.current((prev) => ({
|
|
2621
2641
|
...prev,
|
|
2622
2642
|
lastError: parsed.reason
|
|
2623
2643
|
}));
|
|
2624
|
-
|
|
2644
|
+
resolveAuthoritativeDocumentRef.current(
|
|
2645
|
+
sanitizeRealtimeSnapshot(parsed.document)
|
|
2646
|
+
);
|
|
2625
2647
|
}
|
|
2626
2648
|
});
|
|
2627
2649
|
socket.addEventListener("error", () => {
|
|
2628
2650
|
if (disposed) return;
|
|
2629
|
-
|
|
2651
|
+
updateConnectionRef.current((prev) => ({
|
|
2630
2652
|
...prev,
|
|
2631
2653
|
state: prev.connected ? prev.state : "error",
|
|
2632
2654
|
lastError: "Falha de conex\xE3o websocket."
|
|
@@ -2637,17 +2659,17 @@ function useRealtimeSession(options) {
|
|
|
2637
2659
|
clearHeartbeatTimer();
|
|
2638
2660
|
clearConnectTimeout();
|
|
2639
2661
|
wsRef.current = null;
|
|
2640
|
-
|
|
2662
|
+
collapsePeersToSelfRef.current(
|
|
2641
2663
|
manualDisconnectRef.current || !enabled ? "offline" : "reconnecting"
|
|
2642
2664
|
);
|
|
2643
|
-
|
|
2665
|
+
updateConnectionRef.current((prev) => ({
|
|
2644
2666
|
...prev,
|
|
2645
2667
|
connected: false,
|
|
2646
2668
|
clientId: prev.clientId,
|
|
2647
2669
|
state: manualDisconnectRef.current || !enabled ? "offline" : prev.state
|
|
2648
2670
|
}));
|
|
2649
2671
|
if (!manualDisconnectRef.current && enabled) {
|
|
2650
|
-
|
|
2672
|
+
scheduleReconnectRef.current();
|
|
2651
2673
|
}
|
|
2652
2674
|
});
|
|
2653
2675
|
return () => {
|
|
@@ -2659,14 +2681,10 @@ function useRealtimeSession(options) {
|
|
|
2659
2681
|
socket.close();
|
|
2660
2682
|
};
|
|
2661
2683
|
}, [
|
|
2662
|
-
applyDocument,
|
|
2663
|
-
applyPeers,
|
|
2664
2684
|
clearConnectTimeout,
|
|
2685
|
+
clearDocumentFlushSchedule,
|
|
2665
2686
|
clearHeartbeatTimer,
|
|
2666
|
-
clearLocalDraft,
|
|
2667
2687
|
clearReconnectTimer,
|
|
2668
|
-
collapsePeersToSelf,
|
|
2669
|
-
clearDocumentFlushSchedule,
|
|
2670
2688
|
connectSequence,
|
|
2671
2689
|
connectTimeoutMs,
|
|
2672
2690
|
enabled,
|
|
@@ -2675,13 +2693,7 @@ function useRealtimeSession(options) {
|
|
|
2675
2693
|
peer.displayName,
|
|
2676
2694
|
peer.id,
|
|
2677
2695
|
peer.image,
|
|
2678
|
-
resolveAuthoritativeDocument,
|
|
2679
2696
|
roomId,
|
|
2680
|
-
scheduleDocumentFlush,
|
|
2681
|
-
scheduleReconnect,
|
|
2682
|
-
sendRaw,
|
|
2683
|
-
setConflictState,
|
|
2684
|
-
updateConnection,
|
|
2685
2697
|
url
|
|
2686
2698
|
]);
|
|
2687
2699
|
useEffect(() => {
|
|
@@ -2987,7 +2999,12 @@ function useRealtimeCanvasDocument(options) {
|
|
|
2987
2999
|
} = options;
|
|
2988
3000
|
const [loading, setLoading] = useState(false);
|
|
2989
3001
|
const lastAppliedRevisionRef = useRef(null);
|
|
3002
|
+
const inFlightRevisionRef = useRef(null);
|
|
2990
3003
|
const realtimeEnabled = enabled && session != null;
|
|
3004
|
+
const documentRevision = session?.document?.revision ?? null;
|
|
3005
|
+
const documentItems = session?.document?.items;
|
|
3006
|
+
const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
|
|
3007
|
+
const connectionClientId = session?.connection.clientId ?? null;
|
|
2991
3008
|
const applyIncomingItems = useCallback(
|
|
2992
3009
|
async (nextItems) => {
|
|
2993
3010
|
const normalizedItems = normalizeItems ? normalizeItems(nextItems) : [...nextItems];
|
|
@@ -3010,22 +3027,38 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3010
3027
|
);
|
|
3011
3028
|
useEffect(() => {
|
|
3012
3029
|
if (!realtimeEnabled || !onItemsChange || !session?.document) return;
|
|
3013
|
-
if (
|
|
3014
|
-
if (
|
|
3030
|
+
if (documentUpdatedByClientId === connectionClientId) return;
|
|
3031
|
+
if (documentRevision == null) return;
|
|
3032
|
+
if (lastAppliedRevisionRef.current === documentRevision) return;
|
|
3033
|
+
if (inFlightRevisionRef.current === documentRevision) return;
|
|
3034
|
+
inFlightRevisionRef.current = documentRevision;
|
|
3015
3035
|
let cancelled = false;
|
|
3016
3036
|
setLoading(true);
|
|
3017
|
-
void applyIncomingItems(
|
|
3037
|
+
void applyIncomingItems(documentItems ?? []).then((resolvedItems) => {
|
|
3018
3038
|
if (cancelled) return;
|
|
3019
|
-
|
|
3039
|
+
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
3040
|
+
lastAppliedRevisionRef.current = documentRevision;
|
|
3020
3041
|
onItemsChange(resolvedItems);
|
|
3021
3042
|
}).finally(() => {
|
|
3043
|
+
if (inFlightRevisionRef.current === documentRevision) {
|
|
3044
|
+
inFlightRevisionRef.current = null;
|
|
3045
|
+
}
|
|
3022
3046
|
if (cancelled) return;
|
|
3023
3047
|
setLoading(false);
|
|
3024
3048
|
});
|
|
3025
3049
|
return () => {
|
|
3026
3050
|
cancelled = true;
|
|
3027
3051
|
};
|
|
3028
|
-
}, [
|
|
3052
|
+
}, [
|
|
3053
|
+
applyIncomingItems,
|
|
3054
|
+
connectionClientId,
|
|
3055
|
+
documentItems,
|
|
3056
|
+
documentRevision,
|
|
3057
|
+
documentUpdatedByClientId,
|
|
3058
|
+
onItemsChange,
|
|
3059
|
+
realtimeEnabled,
|
|
3060
|
+
session?.document
|
|
3061
|
+
]);
|
|
3029
3062
|
return useMemo(
|
|
3030
3063
|
() => ({
|
|
3031
3064
|
items,
|