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