@buoy-gg/storage 7.0.4 → 7.0.6
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/lib/commonjs/index.js +77 -1
- package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +263 -115
- package/lib/commonjs/storage/components/GameUIStorageStats.js +7 -7
- package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/commonjs/storage/components/SelectionActionBar.js +2 -36
- package/lib/commonjs/storage/components/StorageActions.js +6 -6
- package/lib/commonjs/storage/components/StorageBrowserMode.js +16 -2
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +106 -149
- package/lib/commonjs/storage/components/StorageEventListener.js +13 -13
- package/lib/commonjs/storage/components/StorageFilterCards.js +13 -135
- package/lib/commonjs/storage/components/StorageKeyCard.js +1 -1
- package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +392 -0
- package/lib/commonjs/storage/components/StorageKeyRow.js +53 -15
- package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
- package/lib/commonjs/storage/components/StorageKeyStats.js +10 -10
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +272 -129
- package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +46 -0
- package/lib/commonjs/storage/components/StorageTypeFilterBar.js +141 -0
- package/lib/commonjs/storage/components/StorageValueEditModal.js +361 -0
- package/lib/commonjs/storage/hooks/useKeyEditor.js +104 -0
- package/lib/commonjs/storage/hooks/useMeasuredWidth.js +74 -0
- package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +70 -15
- package/lib/commonjs/storage/hooks/useTreeDraft.js +107 -0
- package/lib/commonjs/storage/index.js +29 -0
- package/lib/commonjs/storage/stores/storageEventStore.js +4 -2
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +10 -9
- package/lib/commonjs/storage/utils/MMKVListener.js +40 -4
- package/lib/commonjs/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/commonjs/storage/utils/deleteStorageKeys.js +53 -0
- package/lib/commonjs/storage/utils/index.js +13 -0
- package/lib/commonjs/storage/utils/lineDiff.js +21 -354
- package/lib/commonjs/storage/utils/setStorageValue.js +218 -0
- package/lib/commonjs/storage/utils/storageBrowserRefresh.js +49 -0
- package/lib/commonjs/storage/utils/storageOps.js +53 -0
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +60 -38
- package/lib/commonjs/storage/utils/valueType.js +24 -0
- package/lib/module/index.js +18 -1
- package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/module/storage/components/GameUIStorageBrowser.js +266 -118
- package/lib/module/storage/components/GameUIStorageStats.js +7 -7
- package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/module/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/module/storage/components/SelectionActionBar.js +2 -36
- package/lib/module/storage/components/StorageActions.js +7 -7
- package/lib/module/storage/components/StorageBrowserMode.js +16 -2
- package/lib/module/storage/components/StorageEventDetailContent.js +110 -151
- package/lib/module/storage/components/StorageEventListener.js +14 -14
- package/lib/module/storage/components/StorageFilterCards.js +13 -135
- package/lib/module/storage/components/StorageKeyCard.js +1 -1
- package/lib/module/storage/components/StorageKeyEditorScreen.js +388 -0
- package/lib/module/storage/components/StorageKeyRow.js +55 -17
- package/lib/module/storage/components/StorageKeySection.js +11 -5
- package/lib/module/storage/components/StorageKeyStats.js +11 -10
- package/lib/module/storage/components/StorageModalWithTabs.js +272 -130
- package/lib/module/storage/components/StorageScalarKeyEditor.js +42 -0
- package/lib/module/storage/components/StorageTypeFilterBar.js +136 -0
- package/lib/module/storage/components/StorageValueEditModal.js +357 -0
- package/lib/module/storage/hooks/useKeyEditor.js +100 -0
- package/lib/module/storage/hooks/useMeasuredWidth.js +71 -0
- package/lib/module/storage/hooks/useSecureStoreKeys.js +71 -16
- package/lib/module/storage/hooks/useTreeDraft.js +103 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/stores/storageEventStore.js +4 -2
- package/lib/module/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/module/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/module/storage/utils/MMKVInstanceRegistry.js +8 -7
- package/lib/module/storage/utils/MMKVListener.js +40 -4
- package/lib/module/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/module/storage/utils/deleteStorageKeys.js +49 -0
- package/lib/module/storage/utils/index.js +3 -0
- package/lib/module/storage/utils/lineDiff.js +7 -354
- package/lib/module/storage/utils/setStorageValue.js +209 -0
- package/lib/module/storage/utils/storageBrowserRefresh.js +44 -0
- package/lib/module/storage/utils/storageOps.js +46 -0
- package/lib/module/storage/utils/storageTimeTravelUtils.js +61 -40
- package/lib/module/storage/utils/valueType.js +23 -0
- package/lib/typescript/index.d.ts +12 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts +4 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +7 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +27 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts +17 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +4 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageFilterCards.d.ts +9 -3
- package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +42 -0
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +17 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +28 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +24 -0
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +29 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts +36 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts +51 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts +37 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +8 -1
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts +67 -0
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -0
- package/lib/typescript/storage/index.d.ts +2 -0
- package/lib/typescript/storage/index.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +41 -0
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +15 -0
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVListener.d.ts +7 -0
- package/lib/typescript/storage/utils/MMKVListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts +20 -0
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts +27 -0
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +1 -0
- package/lib/typescript/storage/utils/index.d.ts +1 -0
- package/lib/typescript/storage/utils/index.d.ts.map +1 -1
- package/lib/typescript/storage/utils/lineDiff.d.ts +7 -31
- package/lib/typescript/storage/utils/lineDiff.d.ts.map +1 -1
- package/lib/typescript/storage/utils/setStorageValue.d.ts +64 -0
- package/lib/typescript/storage/utils/setStorageValue.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts +28 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageOps.d.ts +25 -0
- package/lib/typescript/storage/utils/storageOps.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +15 -5
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
- package/lib/typescript/storage/utils/valueType.d.ts +13 -0
- package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -12,6 +12,23 @@ var _SecureStoreRegistry = require("../utils/SecureStoreRegistry");
|
|
|
12
12
|
* auto-reads them — it just shows they exist.
|
|
13
13
|
*/
|
|
14
14
|
const BIOMETRIC_PLACEHOLDER = "•••••• (biometric protected)";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* How often to re-read registered SecureStore values.
|
|
18
|
+
*
|
|
19
|
+
* SecureStore is the one backend with no change feed at all: AsyncStorage and
|
|
20
|
+
* MMKV writes are intercepted (and MMKV has a native value-changed listener),
|
|
21
|
+
* but the keychain gives no notification, so a value written by the app while
|
|
22
|
+
* the tool is open would otherwise stay stale until the browser remounts.
|
|
23
|
+
* Polling is the only option — kept cheap by re-reading a handful of registered
|
|
24
|
+
* keys and only committing state when a value actually changed.
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_POLL_MS = 3000;
|
|
27
|
+
/** Identity signature used to skip no-op state updates. */
|
|
28
|
+
function signature(keys) {
|
|
29
|
+
return JSON.stringify(keys);
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
/**
|
|
16
33
|
* Read all registered SecureStore keys into StorageKeyInfo[] for the browser.
|
|
17
34
|
*
|
|
@@ -19,22 +36,47 @@ const BIOMETRIC_PLACEHOLDER = "•••••• (biometric protected)";
|
|
|
19
36
|
* `registerSecureStoreKeys()` (see SecureStoreRegistry). Values are read with
|
|
20
37
|
* the options each key was registered with (keychainService must match), and
|
|
21
38
|
* biometric-protected keys are listed without being read.
|
|
39
|
+
*
|
|
40
|
+
* Values are re-read on a poll and whenever the registry changes, so the
|
|
41
|
+
* browser tracks writes the app makes while the tool is open.
|
|
22
42
|
*/
|
|
23
|
-
function useSecureStoreKeys() {
|
|
43
|
+
function useSecureStoreKeys(options = {}) {
|
|
44
|
+
const {
|
|
45
|
+
pollMs = DEFAULT_POLL_MS
|
|
46
|
+
} = options;
|
|
24
47
|
const [storageKeys, setStorageKeys] = (0, _react.useState)([]);
|
|
25
48
|
const [isLoading, setIsLoading] = (0, _react.useState)(true);
|
|
26
49
|
const [error, setError] = (0, _react.useState)(null);
|
|
50
|
+
|
|
51
|
+
// Last committed content, so a poll that finds nothing new costs zero
|
|
52
|
+
// renders (this hook feeds a non-virtualized list of every storage key).
|
|
53
|
+
const signatureRef = (0, _react.useRef)(signature([]));
|
|
54
|
+
const hasLoadedRef = (0, _react.useRef)(false);
|
|
55
|
+
const inFlightRef = (0, _react.useRef)(false);
|
|
56
|
+
const commit = (0, _react.useCallback)(keys => {
|
|
57
|
+
const next = signature(keys);
|
|
58
|
+
if (next === signatureRef.current) return;
|
|
59
|
+
signatureRef.current = next;
|
|
60
|
+
setStorageKeys(keys);
|
|
61
|
+
}, []);
|
|
27
62
|
const fetchSecureStoreData = (0, _react.useCallback)(async () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
setStorageKeys([]);
|
|
32
|
-
setIsLoading(false);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
setIsLoading(true);
|
|
36
|
-
setError(null);
|
|
63
|
+
// Reads are async and the poll is unconditional — never stack them.
|
|
64
|
+
if (inFlightRef.current) return;
|
|
65
|
+
inFlightRef.current = true;
|
|
37
66
|
try {
|
|
67
|
+
const module = _SecureStoreRegistry.secureStoreRegistry.getModule();
|
|
68
|
+
const entries = _SecureStoreRegistry.secureStoreRegistry.getAll();
|
|
69
|
+
if (!module || entries.length === 0) {
|
|
70
|
+
commit([]);
|
|
71
|
+
hasLoadedRef.current = true;
|
|
72
|
+
setIsLoading(false);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Only the first read is a "loading" state; polls refresh in place so the
|
|
77
|
+
// browser never flashes its spinner over data it already has.
|
|
78
|
+
if (!hasLoadedRef.current) setIsLoading(true);
|
|
79
|
+
setError(null);
|
|
38
80
|
const keys = [];
|
|
39
81
|
for (const entry of entries) {
|
|
40
82
|
if (entry.options.requireAuthentication) {
|
|
@@ -82,19 +124,32 @@ function useSecureStoreKeys() {
|
|
|
82
124
|
})
|
|
83
125
|
});
|
|
84
126
|
}
|
|
85
|
-
|
|
127
|
+
commit(keys);
|
|
86
128
|
} catch (err) {
|
|
87
129
|
setError(err instanceof Error ? err : new Error("Failed to read SecureStore"));
|
|
88
|
-
|
|
130
|
+
commit([]);
|
|
89
131
|
} finally {
|
|
132
|
+
hasLoadedRef.current = true;
|
|
133
|
+
inFlightRef.current = false;
|
|
90
134
|
setIsLoading(false);
|
|
91
135
|
}
|
|
92
|
-
}, []);
|
|
136
|
+
}, [commit]);
|
|
93
137
|
|
|
94
|
-
// Initial fetch
|
|
138
|
+
// Initial fetch + poll. Registration usually happens at app startup, but the
|
|
139
|
+
// registry is also refilled on the dashboard every time a device syncs, so
|
|
140
|
+
// subscribe as well rather than assuming the key list is final at mount.
|
|
95
141
|
(0, _react.useEffect)(() => {
|
|
96
142
|
fetchSecureStoreData();
|
|
97
|
-
|
|
143
|
+
const unsubscribe = _SecureStoreRegistry.secureStoreRegistry.subscribe(() => {
|
|
144
|
+
fetchSecureStoreData();
|
|
145
|
+
});
|
|
146
|
+
if (pollMs <= 0) return unsubscribe;
|
|
147
|
+
const interval = setInterval(fetchSecureStoreData, pollMs);
|
|
148
|
+
return () => {
|
|
149
|
+
unsubscribe();
|
|
150
|
+
clearInterval(interval);
|
|
151
|
+
};
|
|
152
|
+
}, [fetchSecureStoreData, pollMs]);
|
|
98
153
|
return {
|
|
99
154
|
storageKeys,
|
|
100
155
|
isLoading,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useTreeDraft = useTreeDraft;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
|
|
9
|
+
/**
|
|
10
|
+
* A local draft of a storage value, edited in place and written ONCE.
|
|
11
|
+
*
|
|
12
|
+
* Applying each tree op straight to the device was the obvious first cut and it
|
|
13
|
+
* behaves badly for two compounding reasons:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Every op is a round trip.** The write lands, the device emits a storage
|
|
16
|
+
* event, a snapshot comes back, and the browser re-reads every backend — so
|
|
17
|
+
* a six-tap reorder is six writes and six full refreshes. The tree rebuilds
|
|
18
|
+
* under the user mid-gesture.
|
|
19
|
+
* 2. **The highlight and the value fall out of step.** Selection is local and
|
|
20
|
+
* moves instantly; the value it points at only catches up when the round
|
|
21
|
+
* trip completes, so for a beat the highlight sits on the old contents.
|
|
22
|
+
*
|
|
23
|
+
* Optimistic updates — the React Query pattern of rendering the expected result
|
|
24
|
+
* and reconciling later — fix (2) but not (1): they still write per change.
|
|
25
|
+
* Buffering fixes both, and it's what every editor that owns a document does
|
|
26
|
+
* (a form's dirty state, an IDE's unsaved buffer, JSON Editor's in-memory doc
|
|
27
|
+
* with an explicit save).
|
|
28
|
+
*
|
|
29
|
+
* The trade is that the buffer can go stale if the app writes the same key
|
|
30
|
+
* while you're editing. That's the same last-write-wins a text editor has, and
|
|
31
|
+
* `isDirty` at least makes it visible that you're holding unsaved changes.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param liveValue the value as storage currently holds it (already parsed)
|
|
36
|
+
* @param save persists the serialized draft; rejecting keeps edit mode open
|
|
37
|
+
*/
|
|
38
|
+
function useTreeDraft(liveValue, save) {
|
|
39
|
+
const [draft, setDraft] = (0, _react.useState)(undefined);
|
|
40
|
+
const [isEditing, setIsEditing] = (0, _react.useState)(false);
|
|
41
|
+
const [isSaving, setIsSaving] = (0, _react.useState)(false);
|
|
42
|
+
const [error, setError] = (0, _react.useState)(null);
|
|
43
|
+
const [selectPath, setSelectPath] = (0, _react.useState)();
|
|
44
|
+
/**
|
|
45
|
+
* The value as it was when editing began. Compared against for dirtiness
|
|
46
|
+
* rather than against `liveValue`, which can move underneath us if the app
|
|
47
|
+
* writes the same key — that would otherwise flip a clean draft to dirty.
|
|
48
|
+
*/
|
|
49
|
+
const [baseline, setBaseline] = (0, _react.useState)("");
|
|
50
|
+
const value = isEditing && draft !== undefined ? draft : liveValue;
|
|
51
|
+
const isDirty = isEditing && JSON.stringify(draft) !== baseline;
|
|
52
|
+
const begin = (0, _react.useCallback)(() => {
|
|
53
|
+
setDraft(liveValue);
|
|
54
|
+
setBaseline(JSON.stringify(liveValue));
|
|
55
|
+
setSelectPath(undefined);
|
|
56
|
+
setError(null);
|
|
57
|
+
setIsEditing(true);
|
|
58
|
+
}, [liveValue]);
|
|
59
|
+
const apply = (0, _react.useCallback)(op => {
|
|
60
|
+
// Purely local — this is the whole point. No write, so no event, no
|
|
61
|
+
// snapshot, no refresh, and the tree re-renders from state synchronously.
|
|
62
|
+
setDraft(prev => (0, _dataViewer.applyJsonOp)(prev, op));
|
|
63
|
+
const next = (0, _dataViewer.selectionPathAfterOp)(op);
|
|
64
|
+
if (next !== undefined) setSelectPath(next);
|
|
65
|
+
}, []);
|
|
66
|
+
const selectAt = (0, _react.useCallback)(path => {
|
|
67
|
+
setSelectPath(path);
|
|
68
|
+
}, []);
|
|
69
|
+
const reset = (0, _react.useCallback)(() => {
|
|
70
|
+
setIsEditing(false);
|
|
71
|
+
setDraft(undefined);
|
|
72
|
+
setSelectPath(undefined);
|
|
73
|
+
setError(null);
|
|
74
|
+
}, []);
|
|
75
|
+
const commit = (0, _react.useCallback)(async () => {
|
|
76
|
+
if (!isDirty || !save) {
|
|
77
|
+
reset();
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
setIsSaving(true);
|
|
81
|
+
setError(null);
|
|
82
|
+
try {
|
|
83
|
+
await save(JSON.stringify(draft));
|
|
84
|
+
reset();
|
|
85
|
+
return true;
|
|
86
|
+
} catch (e) {
|
|
87
|
+
// Stay in edit mode so the work isn't lost to a failed write.
|
|
88
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
89
|
+
return false;
|
|
90
|
+
} finally {
|
|
91
|
+
setIsSaving(false);
|
|
92
|
+
}
|
|
93
|
+
}, [draft, isDirty, reset, save]);
|
|
94
|
+
return {
|
|
95
|
+
isEditing,
|
|
96
|
+
value,
|
|
97
|
+
isDirty,
|
|
98
|
+
isSaving,
|
|
99
|
+
error,
|
|
100
|
+
selectPath,
|
|
101
|
+
begin,
|
|
102
|
+
apply,
|
|
103
|
+
selectAt,
|
|
104
|
+
commit,
|
|
105
|
+
discard: reset
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -11,6 +11,10 @@ var _exportNames = {
|
|
|
11
11
|
StorageKeySection: true,
|
|
12
12
|
StorageBrowserMode: true,
|
|
13
13
|
StorageEventsSection: true,
|
|
14
|
+
STORAGE_OPS: true,
|
|
15
|
+
storageOpOf: true,
|
|
16
|
+
storageOpDot: true,
|
|
17
|
+
isInitialScan: true,
|
|
14
18
|
StorageEventCard: true,
|
|
15
19
|
getValueType: true,
|
|
16
20
|
StorageEventDetailContent: true,
|
|
@@ -43,6 +47,12 @@ Object.defineProperty(exports, "MMKVInstanceSelector", {
|
|
|
43
47
|
return _MMKVInstanceSelector.MMKVInstanceSelector;
|
|
44
48
|
}
|
|
45
49
|
});
|
|
50
|
+
Object.defineProperty(exports, "STORAGE_OPS", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
get: function () {
|
|
53
|
+
return _storageOps.STORAGE_OPS;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
46
56
|
Object.defineProperty(exports, "StorageBrowserMode", {
|
|
47
57
|
enumerable: true,
|
|
48
58
|
get: function () {
|
|
@@ -121,6 +131,12 @@ Object.defineProperty(exports, "getValueType", {
|
|
|
121
131
|
return _StorageEventCard.getValueType;
|
|
122
132
|
}
|
|
123
133
|
});
|
|
134
|
+
Object.defineProperty(exports, "isInitialScan", {
|
|
135
|
+
enumerable: true,
|
|
136
|
+
get: function () {
|
|
137
|
+
return _storageOps.isInitialScan;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
124
140
|
Object.defineProperty(exports, "isStorageCapturing", {
|
|
125
141
|
enumerable: true,
|
|
126
142
|
get: function () {
|
|
@@ -139,6 +155,18 @@ Object.defineProperty(exports, "storageEventStore", {
|
|
|
139
155
|
return _storageEventStore.storageEventStore;
|
|
140
156
|
}
|
|
141
157
|
});
|
|
158
|
+
Object.defineProperty(exports, "storageOpDot", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
get: function () {
|
|
161
|
+
return _storageOps.storageOpDot;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(exports, "storageOpOf", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function () {
|
|
167
|
+
return _storageOps.storageOpOf;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
142
170
|
Object.defineProperty(exports, "subscribeToStorageEvents", {
|
|
143
171
|
enumerable: true,
|
|
144
172
|
get: function () {
|
|
@@ -194,6 +222,7 @@ var _StorageKeyStats = require("./components/StorageKeyStats");
|
|
|
194
222
|
var _StorageKeySection = require("./components/StorageKeySection");
|
|
195
223
|
var _StorageBrowserMode = require("./components/StorageBrowserMode");
|
|
196
224
|
var _StorageEventsSection = require("./components/StorageEventsSection");
|
|
225
|
+
var _storageOps = require("./utils/storageOps");
|
|
197
226
|
var _StorageEventCard = require("./components/StorageEventCard");
|
|
198
227
|
var _StorageEventDetailContent = require("./components/StorageEventDetailContent");
|
|
199
228
|
var _useAsyncStorageKeys = require("./hooks/useAsyncStorageKeys");
|
|
@@ -101,7 +101,8 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
|
|
|
101
101
|
data: {
|
|
102
102
|
key,
|
|
103
103
|
value,
|
|
104
|
-
valueType
|
|
104
|
+
valueType,
|
|
105
|
+
initialScan: true
|
|
105
106
|
},
|
|
106
107
|
storageType: "mmkv",
|
|
107
108
|
id: nextStorageEventId()
|
|
@@ -128,7 +129,8 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
|
|
|
128
129
|
timestamp: now,
|
|
129
130
|
data: {
|
|
130
131
|
key,
|
|
131
|
-
value: rawValue ?? undefined
|
|
132
|
+
value: rawValue ?? undefined,
|
|
133
|
+
initialScan: true
|
|
132
134
|
},
|
|
133
135
|
storageType: "async",
|
|
134
136
|
id: nextStorageEventId()
|
|
@@ -9,6 +9,7 @@ var _sharedUi = require("@buoy-gg/shared-ui");
|
|
|
9
9
|
var _asyncStorageCompat = require("../utils/asyncStorageCompat");
|
|
10
10
|
var _storageEventStore = require("../stores/storageEventStore");
|
|
11
11
|
var _clearAllStorage = require("../utils/clearAllStorage");
|
|
12
|
+
var _storageTimeTravelUtils = require("../utils/storageTimeTravelUtils");
|
|
12
13
|
var _MMKVInstanceRegistry = require("../utils/MMKVInstanceRegistry");
|
|
13
14
|
var _mmkvTypeDetection = require("../utils/mmkvTypeDetection");
|
|
14
15
|
var _SecureStoreRegistry = require("../utils/SecureStoreRegistry");
|
|
@@ -17,6 +18,28 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
17
18
|
|
|
18
19
|
/** One MMKV instance in a `mmkv.snapshot` result. */
|
|
19
20
|
|
|
21
|
+
/** One registered key in a `secure.snapshot` result. */
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve an MMKV instance for WRITING, refusing the ones the app registered
|
|
25
|
+
* read-only.
|
|
26
|
+
*
|
|
27
|
+
* The registry has always tracked `readOnly`, but nothing enforced it: any sync
|
|
28
|
+
* client — dashboard or MCP — could write to an instance the app explicitly
|
|
29
|
+
* declared immutable. That was harmless while the tool could only read; it
|
|
30
|
+
* stops being harmless the moment there's an edit UI pointed at it.
|
|
31
|
+
*/
|
|
32
|
+
function assertMMKVWritable(instanceId) {
|
|
33
|
+
const registered = _MMKVInstanceRegistry.mmkvInstanceRegistry.get(instanceId);
|
|
34
|
+
if (!registered) {
|
|
35
|
+
throw new Error(`No MMKV instance registered as "${instanceId}"`);
|
|
36
|
+
}
|
|
37
|
+
if (registered.instance?.isReadOnly) {
|
|
38
|
+
throw new Error(`MMKV instance "${instanceId}" is registered read-only`);
|
|
39
|
+
}
|
|
40
|
+
return registered.instance;
|
|
41
|
+
}
|
|
42
|
+
|
|
20
43
|
/**
|
|
21
44
|
* Sync adapter for the storage tool, consumed by @buoy-gg/external-sync's
|
|
22
45
|
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
@@ -46,6 +69,58 @@ const storageSyncAdapter = exports.storageSyncAdapter = {
|
|
|
46
69
|
},
|
|
47
70
|
/** Clears all app storage keys, preserving dev tools settings. */
|
|
48
71
|
clearAppStorage: () => (0, _clearAllStorage.clearAllAppStorage)(),
|
|
72
|
+
// ── Time travel (same code path as the modal's UNDO / JUMP buttons) ──
|
|
73
|
+
// The UI swallows failures silently; these THROW so a caller (desktop, MCP)
|
|
74
|
+
// gets the actual reason back instead of a no-op.
|
|
75
|
+
|
|
76
|
+
/** Undo one event, addressed by the `id` the store assigned it. */
|
|
77
|
+
"timeTravel.undo": async params => {
|
|
78
|
+
const id = params?.id;
|
|
79
|
+
if (!id) throw new Error("timeTravel.undo requires an event `id`");
|
|
80
|
+
const event = _storageEventStore.storageEventStore.getEvents().find(e => e.id === id);
|
|
81
|
+
if (!event) throw new Error(`No storage event with id "${id}"`);
|
|
82
|
+
if (event.storageType === "mmkv") {
|
|
83
|
+
throw new Error("Time travel supports AsyncStorage events only");
|
|
84
|
+
}
|
|
85
|
+
const asyncEvent = event;
|
|
86
|
+
if (!(0, _storageTimeTravelUtils.canUndo)(asyncEvent)) {
|
|
87
|
+
throw new Error(`Event "${id}" (${asyncEvent.action}) has no previous value captured, so it cannot be undone`);
|
|
88
|
+
}
|
|
89
|
+
await (0, _storageTimeTravelUtils.undoOperation)(asyncEvent);
|
|
90
|
+
return {
|
|
91
|
+
undone: true,
|
|
92
|
+
action: asyncEvent.action,
|
|
93
|
+
key: asyncEvent.data?.key
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
/**
|
|
97
|
+
* Jump storage back to the state as of one event. `scope: "key"` (default)
|
|
98
|
+
* replays only that key's history, matching the modal's JUMP button;
|
|
99
|
+
* `scope: "all"` replays every captured AsyncStorage event.
|
|
100
|
+
*/
|
|
101
|
+
"timeTravel.jump": async params => {
|
|
102
|
+
const {
|
|
103
|
+
id,
|
|
104
|
+
scope = "key"
|
|
105
|
+
} = params ?? {};
|
|
106
|
+
if (!id) throw new Error("timeTravel.jump requires an event `id`");
|
|
107
|
+
|
|
108
|
+
// Oldest-first, which is the order jumpToState replays in.
|
|
109
|
+
const all = _storageEventStore.storageEventStore.getEvents().filter(e => e.storageType !== "mmkv").slice().reverse();
|
|
110
|
+
const target = all.find(e => e.id === id);
|
|
111
|
+
if (!target) throw new Error(`No AsyncStorage event with id "${id}"`);
|
|
112
|
+
const timeline = scope === "all" ? all : all.filter(e => e.data?.key === target.data?.key);
|
|
113
|
+
const index = timeline.findIndex(e => e.id === id);
|
|
114
|
+
if (index === -1) throw new Error(`Event "${id}" is not in the timeline`);
|
|
115
|
+
await (0, _storageTimeTravelUtils.jumpToState)(timeline, index);
|
|
116
|
+
return {
|
|
117
|
+
jumped: true,
|
|
118
|
+
scope,
|
|
119
|
+
replayed: index + 1,
|
|
120
|
+
of: timeline.length,
|
|
121
|
+
key: target.data?.key
|
|
122
|
+
};
|
|
123
|
+
},
|
|
49
124
|
// ── Remote AsyncStorage proxy (desktop browse/edit mode) ──
|
|
50
125
|
// Single-item methods share the same signature across async-storage v2/v3;
|
|
51
126
|
// batch methods go through the compat helpers (which translate to v3's
|
|
@@ -108,16 +183,16 @@ const storageSyncAdapter = exports.storageSyncAdapter = {
|
|
|
108
183
|
key,
|
|
109
184
|
value
|
|
110
185
|
} = params;
|
|
111
|
-
|
|
186
|
+
assertMMKVWritable(instanceId).set(key, value);
|
|
112
187
|
},
|
|
113
188
|
"mmkv.remove": params => {
|
|
114
189
|
const {
|
|
115
190
|
instanceId,
|
|
116
191
|
key
|
|
117
192
|
} = params;
|
|
118
|
-
const instance =
|
|
193
|
+
const instance = assertMMKVWritable(instanceId);
|
|
119
194
|
// v4 renamed delete() to remove() — support both
|
|
120
|
-
if (instance
|
|
195
|
+
if (instance.remove) instance.remove(key);else instance.delete?.(key);
|
|
121
196
|
},
|
|
122
197
|
// ── Remote SecureStore proxy ──
|
|
123
198
|
// Only registered keys are known (SecureStore has no enumeration API).
|
|
@@ -132,6 +207,40 @@ const storageSyncAdapter = exports.storageSyncAdapter = {
|
|
|
132
207
|
keychainService: options.keychainService,
|
|
133
208
|
requireAuthentication: options.requireAuthentication ?? false
|
|
134
209
|
})),
|
|
210
|
+
/**
|
|
211
|
+
* Every registered key WITH its value, in one round trip — the SecureStore
|
|
212
|
+
* counterpart to `mmkv.snapshot`. The dashboard polls this (the keychain
|
|
213
|
+
* has no change feed) and serves the browser from the result, instead of
|
|
214
|
+
* firing one `secure.get` per key per refresh.
|
|
215
|
+
*/
|
|
216
|
+
"secure.snapshot": async () => {
|
|
217
|
+
const module = _SecureStoreRegistry.secureStoreRegistry.getModule();
|
|
218
|
+
const entries = _SecureStoreRegistry.secureStoreRegistry.getAll();
|
|
219
|
+
const result = [];
|
|
220
|
+
for (const {
|
|
221
|
+
key,
|
|
222
|
+
options,
|
|
223
|
+
description
|
|
224
|
+
} of entries) {
|
|
225
|
+
let value = null;
|
|
226
|
+
// Biometric-protected keys are never read — that would fire an auth prompt.
|
|
227
|
+
if (module && !options.requireAuthentication) {
|
|
228
|
+
try {
|
|
229
|
+
value = await module.getItemAsync(key, options);
|
|
230
|
+
} catch {
|
|
231
|
+
// Unreadable (e.g. keychain state changed) — report as unset
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
result.push({
|
|
235
|
+
key,
|
|
236
|
+
description,
|
|
237
|
+
keychainService: options.keychainService,
|
|
238
|
+
requireAuthentication: options.requireAuthentication ?? false,
|
|
239
|
+
value
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
},
|
|
135
244
|
"secure.get": params => {
|
|
136
245
|
const {
|
|
137
246
|
key
|
|
@@ -143,6 +252,19 @@ const storageSyncAdapter = exports.storageSyncAdapter = {
|
|
|
143
252
|
}
|
|
144
253
|
return module.getItemAsync(key, entry.options);
|
|
145
254
|
},
|
|
255
|
+
/**
|
|
256
|
+
* Write a registered SecureStore key. Goes through the registry so the
|
|
257
|
+
* value is written with the SAME options it was registered with — writing
|
|
258
|
+
* with different `keychainService` options creates a second entry the app
|
|
259
|
+
* can't read. Biometric-protected keys throw rather than prompting.
|
|
260
|
+
*/
|
|
261
|
+
"secure.set": params => {
|
|
262
|
+
const {
|
|
263
|
+
key,
|
|
264
|
+
value
|
|
265
|
+
} = params;
|
|
266
|
+
return _SecureStoreRegistry.secureStoreRegistry.setValue(key, value);
|
|
267
|
+
},
|
|
146
268
|
"secure.delete": params => _SecureStoreRegistry.secureStoreRegistry.deleteValue(params.key)
|
|
147
269
|
}
|
|
148
270
|
};
|
|
@@ -9,6 +9,28 @@ var _asyncStorageCompat = require("./asyncStorageCompat");
|
|
|
9
9
|
|
|
10
10
|
// v3-only batch methods (replace multiSet/multiRemove)
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* The pristine (un-swizzled) AsyncStorage methods, stashed on globalThis under a
|
|
14
|
+
* cross-realm Symbol.
|
|
15
|
+
*
|
|
16
|
+
* Why this exists: the listener below is a module-scope singleton. Metro Fast
|
|
17
|
+
* Refresh re-evaluates the module and builds a NEW instance whose `original*`
|
|
18
|
+
* fields are all null — but `AsyncStorage.setItem` still points at the OLD
|
|
19
|
+
* instance's `swizzled_setItem`. Without a stash, `initialize()` sees the
|
|
20
|
+
* existing swizzle, has no way to recover the real methods, and bails; the
|
|
21
|
+
* listener then never attaches, so storage events stop for the rest of the
|
|
22
|
+
* session (writes still work, they just go unrecorded). Keeping the originals
|
|
23
|
+
* off the instance lets a fresh instance re-attach cleanly.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const ORIGINALS_KEY = Symbol.for("@buoy-gg/storage.asyncStorageOriginals");
|
|
27
|
+
function readStashedOriginals() {
|
|
28
|
+
return globalThis[ORIGINALS_KEY] ?? null;
|
|
29
|
+
}
|
|
30
|
+
function stashOriginals(originals) {
|
|
31
|
+
globalThis[ORIGINALS_KEY] = originals;
|
|
32
|
+
}
|
|
33
|
+
|
|
12
34
|
// Event types for AsyncStorage operations
|
|
13
35
|
|
|
14
36
|
/**
|
|
@@ -93,6 +115,25 @@ class AsyncStorageListener {
|
|
|
93
115
|
return false;
|
|
94
116
|
}
|
|
95
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Copy a set of pristine methods onto this instance's `original*` fields.
|
|
120
|
+
*
|
|
121
|
+
* Used both when capturing them for the first time and when adopting the
|
|
122
|
+
* global stash after a Fast Refresh, so `restoreOriginalMethods()` and the
|
|
123
|
+
* swizzles always have real methods to call through to.
|
|
124
|
+
*/
|
|
125
|
+
adoptOriginals(originals) {
|
|
126
|
+
this.originalSetItem = originals.setItem;
|
|
127
|
+
this.originalRemoveItem = originals.removeItem;
|
|
128
|
+
this.originalClear = originals.clear;
|
|
129
|
+
this.originalMergeItem = originals.mergeItem;
|
|
130
|
+
this.originalMultiSet = originals.multiSet;
|
|
131
|
+
this.originalMultiRemove = originals.multiRemove;
|
|
132
|
+
this.originalMultiMerge = originals.multiMerge;
|
|
133
|
+
this.originalSetMany = originals.setMany;
|
|
134
|
+
this.originalRemoveMany = originals.removeMany;
|
|
135
|
+
}
|
|
136
|
+
|
|
96
137
|
/**
|
|
97
138
|
* Initialize the listener by loading AsyncStorage and storing original methods
|
|
98
139
|
*
|
|
@@ -109,29 +150,52 @@ class AsyncStorageListener {
|
|
|
109
150
|
return true;
|
|
110
151
|
}
|
|
111
152
|
|
|
112
|
-
//
|
|
153
|
+
// Already swizzled — by a previous instance of this class that Fast Refresh
|
|
154
|
+
// replaced, or by a duplicate copy of the package. Adopt the stashed
|
|
155
|
+
// originals so this instance can restore + re-swizzle. Bailing here is what
|
|
156
|
+
// used to kill storage events until a full app reload.
|
|
113
157
|
if (_asyncStorageCompat.asyncStorage.setItem.name === "swizzled_setItem") {
|
|
114
|
-
|
|
115
|
-
|
|
158
|
+
const stashed = readStashedOriginals();
|
|
159
|
+
if (!stashed) {
|
|
160
|
+
// Swizzled by something we didn't install and whose originals we never
|
|
161
|
+
// captured — taking the current methods as "originals" would bake the
|
|
162
|
+
// foreign swizzle in permanently, so stay out of the way.
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
this.adoptOriginals(stashed);
|
|
166
|
+
this.isInitialized = true;
|
|
167
|
+
return true;
|
|
116
168
|
}
|
|
117
169
|
|
|
118
170
|
// Store original methods (these should be the real AsyncStorage methods).
|
|
119
171
|
// Single-item + clear are identical across v2/v3.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
172
|
+
const originals = {
|
|
173
|
+
setItem: _asyncStorageCompat.asyncStorage.setItem.bind(_asyncStorageCompat.asyncStorage),
|
|
174
|
+
removeItem: _asyncStorageCompat.asyncStorage.removeItem.bind(_asyncStorageCompat.asyncStorage),
|
|
175
|
+
clear: _asyncStorageCompat.asyncStorage.clear.bind(_asyncStorageCompat.asyncStorage),
|
|
176
|
+
mergeItem: null,
|
|
177
|
+
multiSet: null,
|
|
178
|
+
multiRemove: null,
|
|
179
|
+
multiMerge: null,
|
|
180
|
+
setMany: null,
|
|
181
|
+
removeMany: null
|
|
182
|
+
};
|
|
123
183
|
if (_asyncStorageCompat.asyncStorageCaps.hasLegacyMultiApi) {
|
|
124
184
|
// v2: multiSet/multiRemove + merge API
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
185
|
+
originals.multiSet = _asyncStorageCompat.asyncStorage.multiSet.bind(_asyncStorageCompat.asyncStorage);
|
|
186
|
+
originals.multiRemove = _asyncStorageCompat.asyncStorage.multiRemove.bind(_asyncStorageCompat.asyncStorage);
|
|
187
|
+
originals.mergeItem = _asyncStorageCompat.asyncStorage.mergeItem.bind(_asyncStorageCompat.asyncStorage);
|
|
188
|
+
originals.multiMerge = _asyncStorageCompat.asyncStorage.multiMerge ? _asyncStorageCompat.asyncStorage.multiMerge.bind(_asyncStorageCompat.asyncStorage) : null;
|
|
129
189
|
} else {
|
|
130
190
|
// v3: setMany/removeMany; merge API was removed (nothing to capture)
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
originals.setMany = _asyncStorageCompat.asyncStorage.setMany.bind(_asyncStorageCompat.asyncStorage);
|
|
192
|
+
originals.removeMany = _asyncStorageCompat.asyncStorage.removeMany.bind(_asyncStorageCompat.asyncStorage);
|
|
133
193
|
}
|
|
134
194
|
|
|
195
|
+
// Keep them reachable across a module re-evaluation, then adopt.
|
|
196
|
+
stashOriginals(originals);
|
|
197
|
+
this.adoptOriginals(originals);
|
|
198
|
+
|
|
135
199
|
// Original methods stored successfully
|
|
136
200
|
this.isInitialized = true;
|
|
137
201
|
return true;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.mmkvInstanceRegistry = void 0;
|
|
7
7
|
exports.registerMMKVInstance = registerMMKVInstance;
|
|
8
8
|
exports.unregisterMMKVInstance = unregisterMMKVInstance;
|
|
9
|
+
var _MMKVListener = require("./MMKVListener");
|
|
9
10
|
/**
|
|
10
11
|
* MMKV Instance Registry
|
|
11
12
|
*
|
|
@@ -16,6 +17,13 @@ exports.unregisterMMKVInstance = unregisterMMKVInstance;
|
|
|
16
17
|
* @since 1.0.0
|
|
17
18
|
*/
|
|
18
19
|
|
|
20
|
+
// Static ESM import. This was previously a dynamic `require('./MMKVListener')`
|
|
21
|
+
// inside register/unregister "to avoid a circular dependency" — but MMKVListener
|
|
22
|
+
// imports only ./mmkvTypeDetection, never this file, so there is no cycle. The
|
|
23
|
+
// require() form threw `ReferenceError: require is not defined` when this source
|
|
24
|
+
// is bundled as ESM (the desktop dashboard imports it through Vite), crashing on
|
|
25
|
+
// StorageInline unmount. A static import behaves identically on Metro and Vite.
|
|
26
|
+
|
|
19
27
|
/**
|
|
20
28
|
* Type-safe representation of an MMKV instance.
|
|
21
29
|
* Uses conditional typing to avoid hard dependency on react-native-mmkv.
|
|
@@ -167,11 +175,7 @@ function registerMMKVInstance(id, instance, config) {
|
|
|
167
175
|
|
|
168
176
|
// Add listener for real-time monitoring (CRITICAL: enables event emission)
|
|
169
177
|
// v4: Use ONLY native listener (addOnValueChangedListener), skip method wrapping
|
|
170
|
-
|
|
171
|
-
const {
|
|
172
|
-
mmkvListener
|
|
173
|
-
} = require('./MMKVListener');
|
|
174
|
-
mmkvListener.addInstance(instance, id);
|
|
178
|
+
_MMKVListener.mmkvListener.addInstance(instance, id);
|
|
175
179
|
}
|
|
176
180
|
|
|
177
181
|
/**
|
|
@@ -192,10 +196,7 @@ function registerMMKVInstance(id, instance, config) {
|
|
|
192
196
|
*/
|
|
193
197
|
function unregisterMMKVInstance(id) {
|
|
194
198
|
// Remove listener first
|
|
195
|
-
|
|
196
|
-
mmkvListener
|
|
197
|
-
} = require('./MMKVListener');
|
|
198
|
-
mmkvListener.removeInstance(id);
|
|
199
|
+
_MMKVListener.mmkvListener.removeInstance(id);
|
|
199
200
|
|
|
200
201
|
// Then remove from registry
|
|
201
202
|
mmkvInstanceRegistry.unregister(id);
|