react-mirrorstate 0.3.0 → 0.3.1
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/connection-manager.d.ts +0 -1
- package/dist/connection-manager.js +22 -31
- package/dist/index.js +0 -4
- package/package.json +1 -1
|
@@ -3,7 +3,6 @@ class WebSocketConnectionManager {
|
|
|
3
3
|
isConnecting = false;
|
|
4
4
|
listeners = new Map();
|
|
5
5
|
currentStates = new Map();
|
|
6
|
-
clientId = null;
|
|
7
6
|
lastSeq = new Map();
|
|
8
7
|
queuedUpdates = new Map();
|
|
9
8
|
async getWebSocketConfig() {
|
|
@@ -43,38 +42,36 @@ class WebSocketConnectionManager {
|
|
|
43
42
|
this.ws = new WebSocket(wsUrl);
|
|
44
43
|
this.ws.onopen = () => {
|
|
45
44
|
this.isConnecting = false;
|
|
45
|
+
// Flush any queued updates
|
|
46
|
+
this.queuedUpdates.forEach((state, name) => {
|
|
47
|
+
this.updateState(name, state);
|
|
48
|
+
});
|
|
49
|
+
this.queuedUpdates.clear();
|
|
46
50
|
};
|
|
47
51
|
this.ws.onclose = () => {
|
|
48
52
|
this.cleanup();
|
|
49
53
|
};
|
|
50
|
-
this.ws.onerror = () => {
|
|
51
|
-
console.error("WebSocket error");
|
|
54
|
+
this.ws.onerror = (e) => {
|
|
55
|
+
console.error("WebSocket error", e);
|
|
52
56
|
this.cleanup();
|
|
53
57
|
};
|
|
54
58
|
this.ws.onmessage = (event) => {
|
|
59
|
+
let data;
|
|
55
60
|
try {
|
|
56
|
-
|
|
57
|
-
if (data.type === "connected") {
|
|
58
|
-
this.clientId = data.clientId;
|
|
59
|
-
// Flush any queued updates
|
|
60
|
-
this.queuedUpdates.forEach((state, name) => {
|
|
61
|
-
this.updateState(name, state);
|
|
62
|
-
});
|
|
63
|
-
this.queuedUpdates.clear();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (data.type === "fileChange") {
|
|
67
|
-
// Only apply updates with a higher sequence number
|
|
68
|
-
const currentSeq = this.lastSeq.get(data.name) ?? -1;
|
|
69
|
-
if (data.seq !== undefined && data.seq > currentSeq) {
|
|
70
|
-
this.lastSeq.set(data.name, data.seq);
|
|
71
|
-
this.currentStates.set(data.name, data.state);
|
|
72
|
-
this.notifyListeners(data.name, data.state);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
61
|
+
data = JSON.parse(event.data);
|
|
75
62
|
}
|
|
76
63
|
catch (error) {
|
|
77
64
|
console.error("Error handling server message:", error);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (data.type === "fileChange") {
|
|
68
|
+
// Only apply updates with a higher sequence number
|
|
69
|
+
const currentSeq = this.lastSeq.get(data.name) ?? -1;
|
|
70
|
+
if (data.seq !== undefined && data.seq > currentSeq) {
|
|
71
|
+
this.lastSeq.set(data.name, data.seq);
|
|
72
|
+
this.currentStates.set(data.name, data.state);
|
|
73
|
+
this.notifyListeners(data.name, data.state);
|
|
74
|
+
}
|
|
78
75
|
}
|
|
79
76
|
};
|
|
80
77
|
}
|
|
@@ -83,12 +80,7 @@ class WebSocketConnectionManager {
|
|
|
83
80
|
this.listeners.set(name, new Set());
|
|
84
81
|
}
|
|
85
82
|
this.listeners.get(name).add(listener);
|
|
86
|
-
// Connect if not already connected (dev mode)
|
|
87
83
|
this.connect();
|
|
88
|
-
// Don't immediately notify with currentStates here - it might be stale
|
|
89
|
-
// and could revert optimistic updates. The component initializes from
|
|
90
|
-
// INITIAL_STATES, and the server will send initialState messages when
|
|
91
|
-
// the connection is established, which will update all subscribers.
|
|
92
84
|
return () => {
|
|
93
85
|
const nameListeners = this.listeners.get(name);
|
|
94
86
|
if (nameListeners) {
|
|
@@ -101,7 +93,7 @@ class WebSocketConnectionManager {
|
|
|
101
93
|
}
|
|
102
94
|
pendingUpdates = new Map();
|
|
103
95
|
updateState(name, state) {
|
|
104
|
-
if (this.ws?.readyState !== WebSocket.OPEN
|
|
96
|
+
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
105
97
|
this.queuedUpdates.set(name, state);
|
|
106
98
|
return;
|
|
107
99
|
}
|
|
@@ -112,13 +104,12 @@ class WebSocketConnectionManager {
|
|
|
112
104
|
}
|
|
113
105
|
// Debounce rapid updates
|
|
114
106
|
const timeout = setTimeout(() => {
|
|
115
|
-
if (!this.ws
|
|
107
|
+
if (!this.ws) {
|
|
116
108
|
return;
|
|
117
109
|
}
|
|
118
110
|
this.ws.send(JSON.stringify({
|
|
119
|
-
clientId: this.clientId,
|
|
120
111
|
name,
|
|
121
|
-
state
|
|
112
|
+
state,
|
|
122
113
|
}));
|
|
123
114
|
this.currentStates.set(name, state);
|
|
124
115
|
this.pendingUpdates.delete(name);
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,11 @@ import { connectionManager } from "./connection-manager";
|
|
|
4
4
|
import { INITIAL_STATES } from "virtual:mirrorstate/initial-states";
|
|
5
5
|
export function useMirrorState(name, initialValue) {
|
|
6
6
|
// Capture initialValue once on first render to make it stable
|
|
7
|
-
// This prevents re-renders when users pass inline objects/arrays
|
|
8
7
|
const initialValueRef = useRef(initialValue);
|
|
9
8
|
const [state, setState] = useState(() => INITIAL_STATES?.[name] ?? initialValueRef.current);
|
|
10
9
|
const hasCreatedFile = useRef(false);
|
|
11
10
|
useEffect(() => {
|
|
12
11
|
// Subscribe to state changes for this name
|
|
13
|
-
// Echo filtering happens in connection-manager using vector clocks
|
|
14
12
|
const unsubscribe = connectionManager.subscribe(name, (newState) => {
|
|
15
13
|
setState(newState);
|
|
16
14
|
});
|
|
@@ -28,9 +26,7 @@ export function useMirrorState(name, initialValue) {
|
|
|
28
26
|
const updateMirrorState = (updater) => {
|
|
29
27
|
const currentState = connectionManager.getCurrentState(name) ?? state;
|
|
30
28
|
const newState = produce(currentState, updater);
|
|
31
|
-
// Send update to connection manager (includes vector clock tracking)
|
|
32
29
|
connectionManager.updateState(name, newState);
|
|
33
|
-
// Optimistic local update for immediate UI feedback
|
|
34
30
|
setState(newState);
|
|
35
31
|
};
|
|
36
32
|
return [state, updateMirrorState];
|