@buoy-gg/storage 5.0.0 → 6.0.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/README.md +45 -572
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +21 -18
- package/lib/commonjs/storage/components/SelectionActionBar.js +8 -0
- package/lib/commonjs/storage/components/StorageEventCard.js +3 -2
- package/lib/commonjs/storage/components/StorageFilterCards.js +1 -2
- package/lib/commonjs/storage/components/StorageKeyRow.js +8 -3
- package/lib/commonjs/storage/hooks/useMMKVInstances.js +9 -20
- package/lib/commonjs/storage/hooks/useMMKVKeys.js +12 -21
- package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +104 -0
- package/lib/commonjs/storage/hooks/useStorageEvents.js +25 -11
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +82 -1
- package/lib/commonjs/storage/utils/SecureStoreRegistry.js +149 -0
- package/lib/module/index.js +5 -0
- package/lib/module/storage/components/GameUIStorageBrowser.js +20 -18
- package/lib/module/storage/components/SelectionActionBar.js +8 -0
- package/lib/module/storage/components/StorageEventCard.js +4 -3
- package/lib/module/storage/components/StorageFilterCards.js +1 -2
- package/lib/module/storage/components/StorageKeyRow.js +7 -2
- package/lib/module/storage/hooks/useMMKVInstances.js +6 -18
- package/lib/module/storage/hooks/useMMKVKeys.js +7 -18
- package/lib/module/storage/hooks/useSecureStoreKeys.js +101 -0
- package/lib/module/storage/hooks/useStorageEvents.js +25 -11
- package/lib/module/storage/sync/storageSyncAdapter.js +83 -1
- package/lib/module/storage/utils/SecureStoreRegistry.js +143 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -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/StorageEventCard.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +1 -1
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useMMKVInstances.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +18 -0
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +24 -0
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts +115 -0
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +1 -0
- package/package.json +6 -6
|
@@ -13,18 +13,15 @@ var _StorageFilterCards = require("./StorageFilterCards");
|
|
|
13
13
|
var _useAsyncStorageKeys = require("../hooks/useAsyncStorageKeys");
|
|
14
14
|
var _AsyncStorageListener = require("../utils/AsyncStorageListener");
|
|
15
15
|
var _useMMKVKeys = require("../hooks/useMMKVKeys");
|
|
16
|
+
var _useSecureStoreKeys = require("../hooks/useSecureStoreKeys");
|
|
16
17
|
var _useMMKVInstances = require("../hooks/useMMKVInstances");
|
|
17
|
-
var _mmkvAvailability = require("../utils/mmkvAvailability");
|
|
18
18
|
var _StorageActionButtons = require("./StorageActionButtons");
|
|
19
19
|
var _SelectionActionBar = require("./SelectionActionBar");
|
|
20
|
+
var _MMKVListener = require("../utils/MMKVListener");
|
|
20
21
|
var _license = require("@buoy-gg/license");
|
|
21
22
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
if ((0, _mmkvAvailability.isMMKVAvailable)()) {
|
|
25
|
-
const listener = require("../utils/MMKVListener");
|
|
26
|
-
addMMKVListener = listener.addMMKVListener;
|
|
27
|
-
}
|
|
23
|
+
// Pure JS (no react-native-mmkv dependency) — works with any registered
|
|
24
|
+
// instance, including remote-backed wrappers on the desktop dashboard.
|
|
28
25
|
|
|
29
26
|
// Import shared Game UI components
|
|
30
27
|
|
|
@@ -123,20 +120,29 @@ function GameUIStorageBrowser({
|
|
|
123
120
|
return allMMKVKeys.filter(k => k.instanceId === selectedMMKVInstance);
|
|
124
121
|
}, [allMMKVKeys, selectedMMKVInstance]);
|
|
125
122
|
|
|
126
|
-
//
|
|
123
|
+
// Use SecureStore hook — only surfaces keys declared via registerSecureStoreKeys()
|
|
124
|
+
const {
|
|
125
|
+
storageKeys: secureStorageKeys,
|
|
126
|
+
isLoading: isLoadingSecure,
|
|
127
|
+
error: secureError,
|
|
128
|
+
refresh: refreshSecure
|
|
129
|
+
} = (0, _useSecureStoreKeys.useSecureStoreKeys)();
|
|
130
|
+
|
|
131
|
+
// Merge storage keys from AsyncStorage, MMKV, and SecureStore
|
|
127
132
|
const allStorageKeys = (0, _react.useMemo)(() => {
|
|
128
|
-
return [...asyncStorageKeys, ...mmkvStorageKeys];
|
|
129
|
-
}, [asyncStorageKeys, mmkvStorageKeys]);
|
|
133
|
+
return [...asyncStorageKeys, ...mmkvStorageKeys, ...secureStorageKeys];
|
|
134
|
+
}, [asyncStorageKeys, mmkvStorageKeys, secureStorageKeys]);
|
|
130
135
|
|
|
131
136
|
// Determine loading and error states
|
|
132
|
-
const isLoading = isLoadingAsync || isLoadingMMKV;
|
|
133
|
-
const error = asyncError || mmkvError;
|
|
137
|
+
const isLoading = isLoadingAsync || isLoadingMMKV || isLoadingSecure;
|
|
138
|
+
const error = asyncError || mmkvError || secureError;
|
|
134
139
|
|
|
135
140
|
// Combined refresh function
|
|
136
141
|
const refresh = (0, _react.useCallback)(() => {
|
|
137
142
|
refreshAsync();
|
|
138
143
|
refreshMMKV();
|
|
139
|
-
|
|
144
|
+
refreshSecure();
|
|
145
|
+
}, [refreshAsync, refreshMMKV, refreshSecure]);
|
|
140
146
|
|
|
141
147
|
// Generate unique identifier for a storage key
|
|
142
148
|
const getKeyIdentifier = (0, _react.useCallback)(storageKey => {
|
|
@@ -249,13 +255,10 @@ function GameUIStorageBrowser({
|
|
|
249
255
|
};
|
|
250
256
|
}, [refreshAsync]);
|
|
251
257
|
|
|
252
|
-
// Auto-refresh on MMKV storage events
|
|
258
|
+
// Auto-refresh on MMKV storage events
|
|
253
259
|
(0, _react.useEffect)(() => {
|
|
254
|
-
if (!(0, _mmkvAvailability.isMMKVAvailable)() || !addMMKVListener) {
|
|
255
|
-
return; // Skip if MMKV not available
|
|
256
|
-
}
|
|
257
260
|
let timeoutId;
|
|
258
|
-
const unsubscribe = addMMKVListener(event => {
|
|
261
|
+
const unsubscribe = (0, _MMKVListener.addMMKVListener)(event => {
|
|
259
262
|
// Only refresh if event is for the selected instance
|
|
260
263
|
if (event.instanceId === selectedMMKVInstance) {
|
|
261
264
|
clearTimeout(timeoutId);
|
|
@@ -9,6 +9,7 @@ var _reactNative = require("react-native");
|
|
|
9
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
10
|
var _license = require("@buoy-gg/license");
|
|
11
11
|
var _asyncStorageCompat = require("../utils/asyncStorageCompat");
|
|
12
|
+
var _SecureStoreRegistry = require("../utils/SecureStoreRegistry");
|
|
12
13
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
14
|
function SelectionActionBar({
|
|
14
15
|
selectedKeys,
|
|
@@ -71,12 +72,19 @@ function SelectionActionBar({
|
|
|
71
72
|
// Group keys by storage type
|
|
72
73
|
const asyncKeys = selectedKeys.filter(k => k.storageType === "async");
|
|
73
74
|
const mmkvKeys = selectedKeys.filter(k => k.storageType === "mmkv");
|
|
75
|
+
const secureKeys = selectedKeys.filter(k => k.storageType === "secure");
|
|
74
76
|
|
|
75
77
|
// Delete AsyncStorage keys
|
|
76
78
|
if (asyncKeys.length > 0) {
|
|
77
79
|
await (0, _asyncStorageCompat.removeMany)(asyncKeys.map(k => k.key));
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
// Delete SecureStore keys (via the registry, which knows each
|
|
83
|
+
// key's keychainService options)
|
|
84
|
+
for (const k of secureKeys) {
|
|
85
|
+
await _SecureStoreRegistry.secureStoreRegistry.deleteValue(k.key);
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
// Delete MMKV keys
|
|
81
89
|
if (mmkvKeys.length > 0) {
|
|
82
90
|
// Group by instance
|
|
@@ -87,7 +87,6 @@ const StorageEventCard = exports.StorageEventCard = /*#__PURE__*/(0, _react.memo
|
|
|
87
87
|
const actionColor = getActionColor(data.lastAction);
|
|
88
88
|
const actionLabel = (0, _storageActionHelpers.translateStorageAction)(data.lastAction);
|
|
89
89
|
const totalOps = data.totalOperations ?? 1;
|
|
90
|
-
const relativeTime = (0, _sharedUi.useRelativeTime)(data.lastEventTimestamp);
|
|
91
90
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CompactRow
|
|
92
91
|
// Status section (left) - shows "Storage" with action-colored dot
|
|
93
92
|
, {
|
|
@@ -104,7 +103,9 @@ const StorageEventCard = exports.StorageEventCard = /*#__PURE__*/(0, _react.memo
|
|
|
104
103
|
showChevron: showChevron
|
|
105
104
|
// Bottom right - timestamp
|
|
106
105
|
,
|
|
107
|
-
bottomRightText:
|
|
106
|
+
bottomRightText: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RelativeTime, {
|
|
107
|
+
timestamp: data.lastEventTimestamp
|
|
108
|
+
})
|
|
108
109
|
// Interaction
|
|
109
110
|
,
|
|
110
111
|
isSelected: isSelected,
|
|
@@ -10,7 +10,6 @@ var _mmkvAvailability = require("../utils/mmkvAvailability");
|
|
|
10
10
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
11
|
// Feature flags - set to true when implemented
|
|
12
12
|
const IS_MMKV_IMPLEMENTED = true; // ✅ MMKV support is now implemented
|
|
13
|
-
const IS_SECURE_STORE_IMPLEMENTED = false;
|
|
14
13
|
|
|
15
14
|
// Runtime availability checks
|
|
16
15
|
const IS_MMKV_AVAILABLE = (0, _mmkvAvailability.isMMKVAvailable)();
|
|
@@ -183,7 +182,7 @@ function StorageFilterCards({
|
|
|
183
182
|
} : {
|
|
184
183
|
color: _sharedUi.macOSColors.text.muted
|
|
185
184
|
}],
|
|
186
|
-
children:
|
|
185
|
+
children: displayStats.secureCount
|
|
187
186
|
})]
|
|
188
187
|
})]
|
|
189
188
|
})]
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.StorageKeyRow =
|
|
6
|
+
exports.StorageKeyRow = void 0;
|
|
7
|
+
var _react = require("react");
|
|
7
8
|
var _reactNative = require("react-native");
|
|
8
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
10
|
var _storageQueryUtils = require("../utils/storageQueryUtils");
|
|
@@ -73,7 +74,11 @@ const formatValue = value => {
|
|
|
73
74
|
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
74
75
|
return str;
|
|
75
76
|
};
|
|
76
|
-
|
|
77
|
+
|
|
78
|
+
// Memo justified by profiling (tool-bench 2026-07-06): the browser tab
|
|
79
|
+
// renders every key row on each parent render (ScrollView, no
|
|
80
|
+
// virtualization) — memo keeps unchanged keys from re-rendering.
|
|
81
|
+
const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(function StorageKeyRow({
|
|
77
82
|
storageKey,
|
|
78
83
|
isExpanded,
|
|
79
84
|
onPress,
|
|
@@ -315,7 +320,7 @@ function StorageKeyRow({
|
|
|
315
320
|
})]
|
|
316
321
|
})]
|
|
317
322
|
});
|
|
318
|
-
}
|
|
323
|
+
});
|
|
319
324
|
const getStorageTypeColor = storageType => {
|
|
320
325
|
switch (storageType) {
|
|
321
326
|
case "mmkv":
|
|
@@ -7,7 +7,7 @@ exports.useMMKVInstance = useMMKVInstance;
|
|
|
7
7
|
exports.useMMKVInstanceExists = useMMKVInstanceExists;
|
|
8
8
|
exports.useMMKVInstances = useMMKVInstances;
|
|
9
9
|
var _react = require("react");
|
|
10
|
-
var
|
|
10
|
+
var _MMKVInstanceRegistry = require("../utils/MMKVInstanceRegistry");
|
|
11
11
|
/**
|
|
12
12
|
* useMMKVInstances Hook
|
|
13
13
|
*
|
|
@@ -22,12 +22,9 @@ var _mmkvAvailability = require("../utils/mmkvAvailability");
|
|
|
22
22
|
* @see MMKVInstanceRegistry.ts for instance registration
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const registry = require('../utils/MMKVInstanceRegistry');
|
|
29
|
-
mmkvInstanceRegistry = registry.mmkvInstanceRegistry;
|
|
30
|
-
}
|
|
25
|
+
// The registry is pure JS (no react-native-mmkv dependency) — import it
|
|
26
|
+
// unconditionally so registered instances show up even without the native
|
|
27
|
+
// module (e.g. remote-backed wrappers on the desktop dashboard).
|
|
31
28
|
|
|
32
29
|
/**
|
|
33
30
|
* Metadata about an MMKV instance with additional runtime information
|
|
@@ -72,13 +69,7 @@ function useMMKVInstances(autoRefresh = false) {
|
|
|
72
69
|
const fetchInstances = () => {
|
|
73
70
|
setIsLoading(true);
|
|
74
71
|
try {
|
|
75
|
-
|
|
76
|
-
if (!(0, _mmkvAvailability.isMMKVAvailable)() || !mmkvInstanceRegistry) {
|
|
77
|
-
setInstances([]);
|
|
78
|
-
setIsLoading(false);
|
|
79
|
-
return; // Silently return empty when MMKV not available
|
|
80
|
-
}
|
|
81
|
-
const allInstances = mmkvInstanceRegistry.getAll();
|
|
72
|
+
const allInstances = _MMKVInstanceRegistry.mmkvInstanceRegistry.getAll();
|
|
82
73
|
|
|
83
74
|
// Enhance with metadata
|
|
84
75
|
const instancesWithMetadata = allInstances.map(instanceInfo => {
|
|
@@ -165,12 +156,11 @@ function useMMKVInstances(autoRefresh = false) {
|
|
|
165
156
|
function useMMKVInstance(instanceId) {
|
|
166
157
|
const [instance, setInstance] = (0, _react.useState)(null);
|
|
167
158
|
(0, _react.useEffect)(() => {
|
|
168
|
-
|
|
169
|
-
if (!(0, _mmkvAvailability.isMMKVAvailable)() || !mmkvInstanceRegistry) {
|
|
159
|
+
if (!_MMKVInstanceRegistry.mmkvInstanceRegistry) {
|
|
170
160
|
setInstance(null);
|
|
171
161
|
return;
|
|
172
162
|
}
|
|
173
|
-
const instanceInfo = mmkvInstanceRegistry.get(instanceId);
|
|
163
|
+
const instanceInfo = _MMKVInstanceRegistry.mmkvInstanceRegistry.get(instanceId);
|
|
174
164
|
if (!instanceInfo) {
|
|
175
165
|
setInstance(null);
|
|
176
166
|
return;
|
|
@@ -210,12 +200,11 @@ function useMMKVInstance(instanceId) {
|
|
|
210
200
|
function useMMKVInstanceExists(instanceId) {
|
|
211
201
|
const [exists, setExists] = (0, _react.useState)(false);
|
|
212
202
|
(0, _react.useEffect)(() => {
|
|
213
|
-
|
|
214
|
-
if (!(0, _mmkvAvailability.isMMKVAvailable)() || !mmkvInstanceRegistry) {
|
|
203
|
+
if (!_MMKVInstanceRegistry.mmkvInstanceRegistry) {
|
|
215
204
|
setExists(false);
|
|
216
205
|
return;
|
|
217
206
|
}
|
|
218
|
-
setExists(mmkvInstanceRegistry.has(instanceId));
|
|
207
|
+
setExists(_MMKVInstanceRegistry.mmkvInstanceRegistry.has(instanceId));
|
|
219
208
|
}, [instanceId]);
|
|
220
209
|
return exists;
|
|
221
210
|
}
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.useMMKVKeys = useMMKVKeys;
|
|
7
7
|
exports.useMultiMMKVKeys = useMultiMMKVKeys;
|
|
8
8
|
var _react = require("react");
|
|
9
|
-
var
|
|
9
|
+
var _mmkvTypeDetection = require("../utils/mmkvTypeDetection");
|
|
10
10
|
/**
|
|
11
11
|
* useMMKVKeys Hook
|
|
12
12
|
*
|
|
@@ -20,17 +20,13 @@ var _mmkvAvailability = require("../utils/mmkvAvailability");
|
|
|
20
20
|
* - No multiGet - must fetch keys individually
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
// Pure-JS utilities (no react-native-mmkv dependency) — safe to import
|
|
24
|
+
// unconditionally. They operate on whatever instance objects are registered,
|
|
25
|
+
// including remote-backed wrappers on the desktop dashboard.
|
|
26
|
+
|
|
23
27
|
// Conditionally import MMKV types
|
|
24
28
|
// Use 'any' to avoid hard dependency on react-native-mmkv
|
|
25
29
|
|
|
26
|
-
// Conditionally import utilities only if MMKV is available
|
|
27
|
-
let detectMMKVType;
|
|
28
|
-
let isTypeMatch;
|
|
29
|
-
if ((0, _mmkvAvailability.isMMKVAvailable)()) {
|
|
30
|
-
const typeDetection = require('../utils/mmkvTypeDetection');
|
|
31
|
-
detectMMKVType = typeDetection.detectMMKVType;
|
|
32
|
-
isTypeMatch = typeDetection.isTypeMatch;
|
|
33
|
-
}
|
|
34
30
|
/**
|
|
35
31
|
* Hook to fetch and monitor keys from an MMKV instance
|
|
36
32
|
*
|
|
@@ -89,14 +85,9 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
|
|
|
89
85
|
setIsLoading(true);
|
|
90
86
|
setError(null);
|
|
91
87
|
try {
|
|
92
|
-
// Guard: Check if
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
setIsLoading(false);
|
|
96
|
-
return; // Silently return empty when MMKV not available
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Guard: Check if instance is provided
|
|
88
|
+
// Guard: Check if instance is provided. (No isMMKVAvailable() gate —
|
|
89
|
+
// registered instances may be remote-backed wrappers with no native
|
|
90
|
+
// module, e.g. on the desktop dashboard.)
|
|
100
91
|
if (!instance) {
|
|
101
92
|
setStorageKeys([]);
|
|
102
93
|
setIsLoading(false);
|
|
@@ -137,7 +128,7 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
|
|
|
137
128
|
const {
|
|
138
129
|
value,
|
|
139
130
|
type
|
|
140
|
-
} = detectMMKVType(instance, key);
|
|
131
|
+
} = (0, _mmkvTypeDetection.detectMMKVType)(instance, key);
|
|
141
132
|
|
|
142
133
|
// Check if this is a required key
|
|
143
134
|
const requiredConfig = requiredStorageKeys.find(req => {
|
|
@@ -155,7 +146,7 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
|
|
|
155
146
|
status = String(value) === String(requiredConfig.expectedValue) ? 'required_present' : 'required_wrong_value';
|
|
156
147
|
} else if (typeof requiredConfig === 'object' && 'expectedType' in requiredConfig) {
|
|
157
148
|
// Type validation
|
|
158
|
-
status = isTypeMatch(type, requiredConfig.expectedType) ? 'required_present' : 'required_wrong_type';
|
|
149
|
+
status = (0, _mmkvTypeDetection.isTypeMatch)(type, requiredConfig.expectedType) ? 'required_present' : 'required_wrong_type';
|
|
159
150
|
} else {
|
|
160
151
|
// Key exists and is required (no specific validation)
|
|
161
152
|
status = 'required_present';
|
|
@@ -291,7 +282,7 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
|
|
|
291
282
|
const {
|
|
292
283
|
value,
|
|
293
284
|
type
|
|
294
|
-
} = detectMMKVType(instance, key);
|
|
285
|
+
} = (0, _mmkvTypeDetection.detectMMKVType)(instance, key);
|
|
295
286
|
const requiredConfig = requiredStorageKeys.find(req => {
|
|
296
287
|
if (typeof req === 'string') return req === key;
|
|
297
288
|
return req.key === key;
|
|
@@ -303,7 +294,7 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
|
|
|
303
294
|
} else if (typeof requiredConfig === 'object' && 'expectedValue' in requiredConfig) {
|
|
304
295
|
status = String(value) === String(requiredConfig.expectedValue) ? 'required_present' : 'required_wrong_value';
|
|
305
296
|
} else if (typeof requiredConfig === 'object' && 'expectedType' in requiredConfig) {
|
|
306
|
-
status = isTypeMatch(type, requiredConfig.expectedType) ? 'required_present' : 'required_wrong_type';
|
|
297
|
+
status = (0, _mmkvTypeDetection.isTypeMatch)(type, requiredConfig.expectedType) ? 'required_present' : 'required_wrong_type';
|
|
307
298
|
} else {
|
|
308
299
|
status = 'required_present';
|
|
309
300
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useSecureStoreKeys = useSecureStoreKeys;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _SecureStoreRegistry = require("../utils/SecureStoreRegistry");
|
|
9
|
+
/**
|
|
10
|
+
* Placeholder value shown for `requireAuthentication` keys. Reading those
|
|
11
|
+
* triggers a biometric (Face ID / fingerprint) prompt, so the tool never
|
|
12
|
+
* auto-reads them — it just shows they exist.
|
|
13
|
+
*/
|
|
14
|
+
const BIOMETRIC_PLACEHOLDER = "•••••• (biometric protected)";
|
|
15
|
+
/**
|
|
16
|
+
* Read all registered SecureStore keys into StorageKeyInfo[] for the browser.
|
|
17
|
+
*
|
|
18
|
+
* SecureStore has no enumeration API, so this only surfaces keys declared via
|
|
19
|
+
* `registerSecureStoreKeys()` (see SecureStoreRegistry). Values are read with
|
|
20
|
+
* the options each key was registered with (keychainService must match), and
|
|
21
|
+
* biometric-protected keys are listed without being read.
|
|
22
|
+
*/
|
|
23
|
+
function useSecureStoreKeys() {
|
|
24
|
+
const [storageKeys, setStorageKeys] = (0, _react.useState)([]);
|
|
25
|
+
const [isLoading, setIsLoading] = (0, _react.useState)(true);
|
|
26
|
+
const [error, setError] = (0, _react.useState)(null);
|
|
27
|
+
const fetchSecureStoreData = (0, _react.useCallback)(async () => {
|
|
28
|
+
const module = _SecureStoreRegistry.secureStoreRegistry.getModule();
|
|
29
|
+
const entries = _SecureStoreRegistry.secureStoreRegistry.getAll();
|
|
30
|
+
if (!module || entries.length === 0) {
|
|
31
|
+
setStorageKeys([]);
|
|
32
|
+
setIsLoading(false);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
setIsLoading(true);
|
|
36
|
+
setError(null);
|
|
37
|
+
try {
|
|
38
|
+
const keys = [];
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
if (entry.options.requireAuthentication) {
|
|
41
|
+
// Never auto-read: reading would fire a biometric prompt.
|
|
42
|
+
keys.push({
|
|
43
|
+
key: entry.key,
|
|
44
|
+
value: BIOMETRIC_PLACEHOLDER,
|
|
45
|
+
storageType: "secure",
|
|
46
|
+
status: "optional_present",
|
|
47
|
+
category: "optional",
|
|
48
|
+
description: entry.description ?? "Protected by biometric authentication — value not read by DevTools",
|
|
49
|
+
...(entry.options.keychainService && {
|
|
50
|
+
instanceId: entry.options.keychainService
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
let rawValue = null;
|
|
56
|
+
try {
|
|
57
|
+
rawValue = await module.getItemAsync(entry.key, entry.options);
|
|
58
|
+
} catch {
|
|
59
|
+
// Key unreadable (e.g. keychain state changed) — treat as missing.
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Parse JSON values for the DataViewer, same as the AsyncStorage hook.
|
|
63
|
+
let parsedValue = rawValue;
|
|
64
|
+
if (rawValue) {
|
|
65
|
+
try {
|
|
66
|
+
parsedValue = JSON.parse(rawValue);
|
|
67
|
+
} catch {
|
|
68
|
+
parsedValue = rawValue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
keys.push({
|
|
72
|
+
key: entry.key,
|
|
73
|
+
value: rawValue === null ? undefined : parsedValue,
|
|
74
|
+
storageType: "secure",
|
|
75
|
+
status: rawValue === null ? "required_missing" : "optional_present",
|
|
76
|
+
category: rawValue === null ? "required" : "optional",
|
|
77
|
+
...(entry.description && {
|
|
78
|
+
description: entry.description
|
|
79
|
+
}),
|
|
80
|
+
...(entry.options.keychainService && {
|
|
81
|
+
instanceId: entry.options.keychainService
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
setStorageKeys(keys);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
setError(err instanceof Error ? err : new Error("Failed to read SecureStore"));
|
|
88
|
+
setStorageKeys([]);
|
|
89
|
+
} finally {
|
|
90
|
+
setIsLoading(false);
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
// Initial fetch (registration happens at app startup, before the tool opens)
|
|
95
|
+
(0, _react.useEffect)(() => {
|
|
96
|
+
fetchSecureStoreData();
|
|
97
|
+
}, [fetchSecureStoreData]);
|
|
98
|
+
return {
|
|
99
|
+
storageKeys,
|
|
100
|
+
isLoading,
|
|
101
|
+
error,
|
|
102
|
+
refresh: fetchSecureStoreData
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -51,24 +51,38 @@ function useStorageEvents(options = {}) {
|
|
|
51
51
|
setIsCapturing(false);
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
-
const unsubscribe = _storageEventStore.storageEventStore.subscribeToEvents(allEvents => {
|
|
55
|
-
// Filter by storage type if specified
|
|
56
|
-
let filtered = allEvents;
|
|
57
|
-
if (storageType !== "all") {
|
|
58
|
-
filtered = allEvents.filter(e => e.storageType === storageType);
|
|
59
|
-
}
|
|
60
54
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
// Frame-coalesced (same pattern as network's useNetworkEventList):
|
|
56
|
+
// bursts of storage writes land as ONE commit per frame instead of
|
|
57
|
+
// one per emission — the dominant live-churn cost measured on the
|
|
58
|
+
// network tool (tool-bench 2026-07-06).
|
|
59
|
+
let rafId = null;
|
|
60
|
+
let pending = null;
|
|
61
|
+
const unsubscribe = _storageEventStore.storageEventStore.subscribeToEvents(allEvents => {
|
|
62
|
+
pending = allEvents;
|
|
63
|
+
if (rafId != null) return;
|
|
64
|
+
rafId = requestAnimationFrame(() => {
|
|
65
|
+
rafId = null;
|
|
66
|
+
if (!pending) return;
|
|
67
|
+
// Filter by storage type if specified
|
|
68
|
+
let filtered = pending;
|
|
69
|
+
pending = null;
|
|
70
|
+
if (storageType !== "all") {
|
|
71
|
+
filtered = filtered.filter(e => e.storageType === storageType);
|
|
72
|
+
}
|
|
73
|
+
// Limit events
|
|
74
|
+
if (filtered.length > maxEvents) {
|
|
75
|
+
filtered = filtered.slice(0, maxEvents);
|
|
76
|
+
}
|
|
77
|
+
setEvents(filtered);
|
|
78
|
+
});
|
|
66
79
|
});
|
|
67
80
|
|
|
68
81
|
// Mark as capturing after successful subscription
|
|
69
82
|
setIsCapturing(true);
|
|
70
83
|
return () => {
|
|
71
84
|
unsubscribe();
|
|
85
|
+
if (rafId != null) cancelAnimationFrame(rafId);
|
|
72
86
|
// Note: Don't set isCapturing=false here because we might be
|
|
73
87
|
// re-subscribing due to dependency change, not actually stopping
|
|
74
88
|
};
|
|
@@ -9,7 +9,14 @@ 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 _MMKVInstanceRegistry = require("../utils/MMKVInstanceRegistry");
|
|
13
|
+
var _mmkvTypeDetection = require("../utils/mmkvTypeDetection");
|
|
14
|
+
var _SecureStoreRegistry = require("../utils/SecureStoreRegistry");
|
|
12
15
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
|
+
/** One MMKV entry in a `mmkv.snapshot` result. */
|
|
17
|
+
|
|
18
|
+
/** One MMKV instance in a `mmkv.snapshot` result. */
|
|
19
|
+
|
|
13
20
|
/**
|
|
14
21
|
* Sync adapter for the storage tool, consumed by @buoy-gg/external-sync's
|
|
15
22
|
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
@@ -62,6 +69,80 @@ const storageSyncAdapter = exports.storageSyncAdapter = {
|
|
|
62
69
|
"async.removeItem": params => _asyncStorage.default.removeItem(params.key),
|
|
63
70
|
"async.multiRemove": params => (0, _asyncStorageCompat.removeMany)(params.keys),
|
|
64
71
|
"async.multiSet": params => (0, _asyncStorageCompat.writeMany)(params.pairs),
|
|
65
|
-
"async.clear": () => _asyncStorage.default.clear()
|
|
72
|
+
"async.clear": () => _asyncStorage.default.clear(),
|
|
73
|
+
// ── Remote MMKV proxy (desktop browse mode + MCP) ──
|
|
74
|
+
// MMKV has no remote presence off-device; these read/write the registered
|
|
75
|
+
// instances so sync clients can browse them like AsyncStorage.
|
|
76
|
+
"mmkv.snapshot": () => _MMKVInstanceRegistry.mmkvInstanceRegistry.getAll().map(({
|
|
77
|
+
id,
|
|
78
|
+
instance,
|
|
79
|
+
encrypted,
|
|
80
|
+
readOnly
|
|
81
|
+
}) => {
|
|
82
|
+
let entries = [];
|
|
83
|
+
try {
|
|
84
|
+
entries = instance.getAllKeys().filter(key => !(0, _sharedUi.isDevToolsStorageKey)(key)).map(key => {
|
|
85
|
+
const {
|
|
86
|
+
value,
|
|
87
|
+
type: valueType
|
|
88
|
+
} = (0, _mmkvTypeDetection.detectMMKVType)(instance, key);
|
|
89
|
+
return {
|
|
90
|
+
key,
|
|
91
|
+
value,
|
|
92
|
+
valueType
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
} catch {
|
|
96
|
+
// Instance unreadable — return it with no entries rather than failing
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
id,
|
|
100
|
+
encrypted,
|
|
101
|
+
readOnly,
|
|
102
|
+
entries
|
|
103
|
+
};
|
|
104
|
+
}),
|
|
105
|
+
"mmkv.set": params => {
|
|
106
|
+
const {
|
|
107
|
+
instanceId,
|
|
108
|
+
key,
|
|
109
|
+
value
|
|
110
|
+
} = params;
|
|
111
|
+
_MMKVInstanceRegistry.mmkvInstanceRegistry.get(instanceId)?.instance.set(key, value);
|
|
112
|
+
},
|
|
113
|
+
"mmkv.remove": params => {
|
|
114
|
+
const {
|
|
115
|
+
instanceId,
|
|
116
|
+
key
|
|
117
|
+
} = params;
|
|
118
|
+
const instance = _MMKVInstanceRegistry.mmkvInstanceRegistry.get(instanceId)?.instance;
|
|
119
|
+
// v4 renamed delete() to remove() — support both
|
|
120
|
+
if (instance?.remove) instance.remove(key);else instance?.delete?.(key);
|
|
121
|
+
},
|
|
122
|
+
// ── Remote SecureStore proxy ──
|
|
123
|
+
// Only registered keys are known (SecureStore has no enumeration API).
|
|
124
|
+
// Biometric-protected keys are never read — reads would fire an auth prompt.
|
|
125
|
+
"secure.keys": () => _SecureStoreRegistry.secureStoreRegistry.getAll().map(({
|
|
126
|
+
key,
|
|
127
|
+
options,
|
|
128
|
+
description
|
|
129
|
+
}) => ({
|
|
130
|
+
key,
|
|
131
|
+
description,
|
|
132
|
+
keychainService: options.keychainService,
|
|
133
|
+
requireAuthentication: options.requireAuthentication ?? false
|
|
134
|
+
})),
|
|
135
|
+
"secure.get": params => {
|
|
136
|
+
const {
|
|
137
|
+
key
|
|
138
|
+
} = params;
|
|
139
|
+
const entry = _SecureStoreRegistry.secureStoreRegistry.getAll().find(e => e.key === key);
|
|
140
|
+
const module = _SecureStoreRegistry.secureStoreRegistry.getModule();
|
|
141
|
+
if (!entry || !module || entry.options.requireAuthentication) {
|
|
142
|
+
return Promise.resolve(null);
|
|
143
|
+
}
|
|
144
|
+
return module.getItemAsync(key, entry.options);
|
|
145
|
+
},
|
|
146
|
+
"secure.delete": params => _SecureStoreRegistry.secureStoreRegistry.deleteValue(params.key)
|
|
66
147
|
}
|
|
67
148
|
};
|