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.
@@ -4,7 +4,6 @@ declare class WebSocketConnectionManager {
4
4
  private isConnecting;
5
5
  private listeners;
6
6
  private currentStates;
7
- private clientId;
8
7
  private lastSeq;
9
8
  private queuedUpdates;
10
9
  private getWebSocketConfig;
@@ -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
- const data = JSON.parse(event.data);
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 || !this.clientId) {
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 || !this.clientId) {
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];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mirrorstate",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "React library for bidirectional state synchronization with MirrorState",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",