react-mirrorstate 0.5.0 → 0.6.0
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 +4 -0
- package/dist/connection-manager.js +55 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12 -1
- package/package.json +1 -1
|
@@ -6,6 +6,9 @@ declare class WebSocketConnectionManager {
|
|
|
6
6
|
private currentStates;
|
|
7
7
|
private lastSeq;
|
|
8
8
|
private queuedUpdates;
|
|
9
|
+
private getStorageObject;
|
|
10
|
+
private saveToLocalStorage;
|
|
11
|
+
loadFromLocalStorage(name: string): any | undefined;
|
|
9
12
|
private getWebSocketConfig;
|
|
10
13
|
private buildWebSocketURL;
|
|
11
14
|
private cleanup;
|
|
@@ -14,6 +17,7 @@ declare class WebSocketConnectionManager {
|
|
|
14
17
|
private pendingUpdates;
|
|
15
18
|
updateState(name: string, state: any): void;
|
|
16
19
|
getCurrentState(name: string): any;
|
|
20
|
+
getStatesHash(): string;
|
|
17
21
|
private notifyListeners;
|
|
18
22
|
}
|
|
19
23
|
export declare const connectionManager: WebSocketConnectionManager;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { STATES_HASH, PROJECT_NAME } from "virtual:mirrorstate/initial-states";
|
|
2
|
+
const LOCALSTORAGE_KEY = `mirrorstate:${PROJECT_NAME}`;
|
|
1
3
|
class WebSocketConnectionManager {
|
|
2
4
|
ws = null;
|
|
3
5
|
isConnecting = false;
|
|
@@ -5,6 +7,51 @@ class WebSocketConnectionManager {
|
|
|
5
7
|
currentStates = new Map();
|
|
6
8
|
lastSeq = new Map();
|
|
7
9
|
queuedUpdates = new Map();
|
|
10
|
+
getStorageObject() {
|
|
11
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const stored = window.localStorage.getItem(LOCALSTORAGE_KEY);
|
|
16
|
+
if (stored !== null) {
|
|
17
|
+
const obj = JSON.parse(stored);
|
|
18
|
+
// Check hash - if mismatch, discard and return null
|
|
19
|
+
if (obj.__hash__ !== STATES_HASH) {
|
|
20
|
+
window.localStorage.removeItem(LOCALSTORAGE_KEY);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return obj;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Ignore parse errors
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
saveToLocalStorage(name, state) {
|
|
32
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
// Load existing object or create new one
|
|
37
|
+
let obj = this.getStorageObject();
|
|
38
|
+
if (!obj) {
|
|
39
|
+
obj = { __hash__: STATES_HASH };
|
|
40
|
+
}
|
|
41
|
+
obj[name] = state;
|
|
42
|
+
window.localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(obj));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Ignore localStorage errors (quota exceeded, etc.)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
loadFromLocalStorage(name) {
|
|
49
|
+
const obj = this.getStorageObject();
|
|
50
|
+
if (obj && name in obj) {
|
|
51
|
+
return obj[name];
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
8
55
|
async getWebSocketConfig() {
|
|
9
56
|
try {
|
|
10
57
|
const config = await import("virtual:mirrorstate/config");
|
|
@@ -97,6 +144,11 @@ class WebSocketConnectionManager {
|
|
|
97
144
|
this.currentStates.set(name, state);
|
|
98
145
|
// Notify all local subscribers immediately (for same-page component sync)
|
|
99
146
|
this.notifyListeners(name, state);
|
|
147
|
+
// In production, persist to localStorage instead of WebSocket
|
|
148
|
+
if (process.env.NODE_ENV === "production") {
|
|
149
|
+
this.saveToLocalStorage(name, state);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
100
152
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
101
153
|
this.queuedUpdates.set(name, state);
|
|
102
154
|
return;
|
|
@@ -122,6 +174,9 @@ class WebSocketConnectionManager {
|
|
|
122
174
|
getCurrentState(name) {
|
|
123
175
|
return this.currentStates.get(name);
|
|
124
176
|
}
|
|
177
|
+
getStatesHash() {
|
|
178
|
+
return STATES_HASH;
|
|
179
|
+
}
|
|
125
180
|
notifyListeners(name, state) {
|
|
126
181
|
const nameListeners = this.listeners.get(name);
|
|
127
182
|
if (nameListeners) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Draft } from "immer";
|
|
1
|
+
import { produce, type Draft } from "immer";
|
|
2
|
+
export { type Draft, type Immutable } from "immer";
|
|
3
|
+
export { produce };
|
|
2
4
|
export declare function useMirrorState<T>(name: string): [T | undefined, (updater: (draft: Draft<T>) => void) => void];
|
|
3
5
|
export declare function useMirrorState<T>(name: string, initialValue: T): [T, (updater: (draft: Draft<T>) => void) => void];
|
|
4
6
|
export default useMirrorState;
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { useEffect, useState, useRef } from "react";
|
|
|
2
2
|
import { produce } from "immer";
|
|
3
3
|
import { connectionManager } from "./connection-manager";
|
|
4
4
|
import { INITIAL_STATES } from "virtual:mirrorstate/initial-states";
|
|
5
|
+
export { produce };
|
|
5
6
|
// Batching state for each mirror state name
|
|
6
7
|
const batchQueues = new Map();
|
|
7
8
|
const batchPending = new Map();
|
|
@@ -35,7 +36,17 @@ function scheduleBatchFlush(name) {
|
|
|
35
36
|
export function useMirrorState(name, initialValue) {
|
|
36
37
|
// Capture initialValue once on first render to make it stable
|
|
37
38
|
const initialValueRef = useRef(initialValue);
|
|
38
|
-
const [state, setState] = useState(() =>
|
|
39
|
+
const [state, setState] = useState(() => {
|
|
40
|
+
// In production, check localStorage first for persisted state
|
|
41
|
+
if (process.env.NODE_ENV === "production") {
|
|
42
|
+
const storedState = connectionManager.loadFromLocalStorage(name);
|
|
43
|
+
if (storedState !== undefined) {
|
|
44
|
+
return storedState;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Fall back to build-time initial states or user-provided initialValue
|
|
48
|
+
return INITIAL_STATES?.[name] ?? initialValueRef.current;
|
|
49
|
+
});
|
|
39
50
|
const hasCreatedFile = useRef(false);
|
|
40
51
|
useEffect(() => {
|
|
41
52
|
// Subscribe to state changes for this name
|