react-mirrorstate 0.2.3 → 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 +2 -1
- package/dist/connection-manager.js +26 -24
- package/dist/index.js +10 -9
- package/package.json +1 -1
|
@@ -4,12 +4,13 @@ declare class WebSocketConnectionManager {
|
|
|
4
4
|
private isConnecting;
|
|
5
5
|
private listeners;
|
|
6
6
|
private currentStates;
|
|
7
|
+
private lastSeq;
|
|
8
|
+
private queuedUpdates;
|
|
7
9
|
private getWebSocketConfig;
|
|
8
10
|
private buildWebSocketURL;
|
|
9
11
|
private cleanup;
|
|
10
12
|
connect(): Promise<void>;
|
|
11
13
|
subscribe(name: string, listener: StateListener): () => void;
|
|
12
|
-
private lastSentState;
|
|
13
14
|
private pendingUpdates;
|
|
14
15
|
updateState(name: string, state: any): void;
|
|
15
16
|
getCurrentState(name: string): any;
|
|
@@ -3,6 +3,8 @@ class WebSocketConnectionManager {
|
|
|
3
3
|
isConnecting = false;
|
|
4
4
|
listeners = new Map();
|
|
5
5
|
currentStates = new Map();
|
|
6
|
+
lastSeq = new Map();
|
|
7
|
+
queuedUpdates = new Map();
|
|
6
8
|
async getWebSocketConfig() {
|
|
7
9
|
try {
|
|
8
10
|
const config = await import("virtual:mirrorstate/config");
|
|
@@ -40,28 +42,36 @@ class WebSocketConnectionManager {
|
|
|
40
42
|
this.ws = new WebSocket(wsUrl);
|
|
41
43
|
this.ws.onopen = () => {
|
|
42
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();
|
|
43
50
|
};
|
|
44
51
|
this.ws.onclose = () => {
|
|
45
52
|
this.cleanup();
|
|
46
53
|
};
|
|
47
|
-
this.ws.onerror = () => {
|
|
48
|
-
console.error("WebSocket error");
|
|
54
|
+
this.ws.onerror = (e) => {
|
|
55
|
+
console.error("WebSocket error", e);
|
|
49
56
|
this.cleanup();
|
|
50
57
|
};
|
|
51
58
|
this.ws.onmessage = (event) => {
|
|
59
|
+
let data;
|
|
52
60
|
try {
|
|
53
|
-
|
|
54
|
-
if (data.type === "initialState") {
|
|
55
|
-
this.currentStates.set(data.name, data.state);
|
|
56
|
-
this.notifyListeners(data.name, data.state);
|
|
57
|
-
}
|
|
58
|
-
if (data.type === "fileChange") {
|
|
59
|
-
this.currentStates.set(data.name, data.state);
|
|
60
|
-
this.notifyListeners(data.name, data.state);
|
|
61
|
-
}
|
|
61
|
+
data = JSON.parse(event.data);
|
|
62
62
|
}
|
|
63
63
|
catch (error) {
|
|
64
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
|
+
}
|
|
65
75
|
}
|
|
66
76
|
};
|
|
67
77
|
}
|
|
@@ -70,12 +80,7 @@ class WebSocketConnectionManager {
|
|
|
70
80
|
this.listeners.set(name, new Set());
|
|
71
81
|
}
|
|
72
82
|
this.listeners.get(name).add(listener);
|
|
73
|
-
// Connect if not already connected (dev mode)
|
|
74
83
|
this.connect();
|
|
75
|
-
// If we already have state for this name, notify immediately
|
|
76
|
-
if (this.currentStates.has(name)) {
|
|
77
|
-
listener(this.currentStates.get(name));
|
|
78
|
-
}
|
|
79
84
|
return () => {
|
|
80
85
|
const nameListeners = this.listeners.get(name);
|
|
81
86
|
if (nameListeners) {
|
|
@@ -86,10 +91,10 @@ class WebSocketConnectionManager {
|
|
|
86
91
|
}
|
|
87
92
|
};
|
|
88
93
|
}
|
|
89
|
-
lastSentState = new Map();
|
|
90
94
|
pendingUpdates = new Map();
|
|
91
95
|
updateState(name, state) {
|
|
92
96
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
97
|
+
this.queuedUpdates.set(name, state);
|
|
93
98
|
return;
|
|
94
99
|
}
|
|
95
100
|
// Cancel any pending update for this state name
|
|
@@ -97,19 +102,16 @@ class WebSocketConnectionManager {
|
|
|
97
102
|
if (pendingUpdate) {
|
|
98
103
|
clearTimeout(pendingUpdate);
|
|
99
104
|
}
|
|
100
|
-
// Check if this is actually a different state
|
|
101
|
-
const lastState = this.lastSentState.get(name);
|
|
102
|
-
if (lastState === state) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
105
|
// Debounce rapid updates
|
|
106
106
|
const timeout = setTimeout(() => {
|
|
107
107
|
if (!this.ws) {
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
this.ws.send(JSON.stringify({
|
|
110
|
+
this.ws.send(JSON.stringify({
|
|
111
|
+
name,
|
|
112
|
+
state,
|
|
113
|
+
}));
|
|
111
114
|
this.currentStates.set(name, state);
|
|
112
|
-
this.lastSentState.set(name, state);
|
|
113
115
|
this.pendingUpdates.delete(name);
|
|
114
116
|
}, 10);
|
|
115
117
|
this.pendingUpdates.set(name, timeout);
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,9 @@ import { produce } from "immer";
|
|
|
3
3
|
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
|
+
const initialValueRef = useRef(initialValue);
|
|
8
|
+
const [state, setState] = useState(() => INITIAL_STATES?.[name] ?? initialValueRef.current);
|
|
7
9
|
const hasCreatedFile = useRef(false);
|
|
8
10
|
useEffect(() => {
|
|
9
11
|
// Subscribe to state changes for this name
|
|
@@ -12,21 +14,20 @@ export function useMirrorState(name, initialValue) {
|
|
|
12
14
|
});
|
|
13
15
|
// If file doesn't exist and initialValue was provided, create it
|
|
14
16
|
if (INITIAL_STATES?.[name] === undefined &&
|
|
15
|
-
|
|
17
|
+
initialValueRef.current !== undefined &&
|
|
16
18
|
!hasCreatedFile.current) {
|
|
17
19
|
hasCreatedFile.current = true;
|
|
18
|
-
connectionManager.updateState(name,
|
|
20
|
+
connectionManager.updateState(name, initialValueRef.current);
|
|
19
21
|
}
|
|
20
22
|
return () => {
|
|
21
23
|
unsubscribe();
|
|
22
24
|
};
|
|
23
|
-
}, [name
|
|
25
|
+
}, [name]);
|
|
24
26
|
const updateMirrorState = (updater) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
27
|
+
const currentState = connectionManager.getCurrentState(name) ?? state;
|
|
28
|
+
const newState = produce(currentState, updater);
|
|
29
|
+
connectionManager.updateState(name, newState);
|
|
30
|
+
setState(newState);
|
|
30
31
|
};
|
|
31
32
|
return [state, updateMirrorState];
|
|
32
33
|
}
|