@buoy-gg/storage 5.0.0 → 6.0.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/README.md +47 -3
- 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 +5 -5
package/README.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@buoy/storage)
|
|
4
4
|
|
|
5
|
-
Storage browser and monitoring tool for React Native development. Supports
|
|
5
|
+
Storage browser and monitoring tool for React Native development. Supports AsyncStorage, MMKV, and Expo SecureStore.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Storage Browser**: Browse all AsyncStorage and
|
|
9
|
+
- **Storage Browser**: Browse all AsyncStorage, MMKV, and SecureStore keys and values
|
|
10
10
|
- **MMKV Support**: Monitor and debug MMKV instances (including encrypted storage)
|
|
11
|
-
- **
|
|
11
|
+
- **SecureStore Support**: Browse and delete Expo SecureStore values (registered keys — see below)
|
|
12
|
+
- **Multiple Storage Types**: Support for AsyncStorage, MMKV (multiple instances), and SecureStore
|
|
12
13
|
- **Live Event Monitoring**: Track all storage operations in real-time
|
|
13
14
|
- **Edit & Delete**: Modify or remove storage items directly
|
|
14
15
|
- **Add New Items**: Create new storage entries
|
|
@@ -16,6 +17,8 @@ Storage browser and monitoring tool for React Native development. Supports both
|
|
|
16
17
|
- **Type Checking**: Validate storage value types
|
|
17
18
|
- **Value Validation**: Check if values match expected patterns
|
|
18
19
|
- **Search & Filter**: Search through keys and filter by status
|
|
20
|
+
- **Pin & Hide**: Pin keys you're watching to the top (persisted); hide noisy keys into the filter store with one tap
|
|
21
|
+
- **Inline Value Previews**: Short values and color-coded boolean badges visible without expanding cards
|
|
19
22
|
- **Copy Functionality**: Easily copy keys and values
|
|
20
23
|
- **Beautiful UI**: Modern, game-themed interface matching other React Buoy tools
|
|
21
24
|
|
|
@@ -327,6 +330,47 @@ storage.set('user.name', 'John Doe');
|
|
|
327
330
|
secureStorage.set('auth.token', 'secret-token');
|
|
328
331
|
```
|
|
329
332
|
|
|
333
|
+
## Expo SecureStore Support
|
|
334
|
+
|
|
335
|
+
The Storage Explorer can browse and delete Expo SecureStore values (iOS Keychain / Android KeyStore).
|
|
336
|
+
|
|
337
|
+
### Setup Requirements
|
|
338
|
+
|
|
339
|
+
SecureStore has **no key-enumeration API** — there is no way to ask the Keychain "what's stored?". So, like MMKV instances, you register the keys you want visible. You pass the `expo-secure-store` module in, so this package has zero dependency on it (bare RN apps without Expo are unaffected).
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import * as SecureStore from 'expo-secure-store';
|
|
343
|
+
import { registerSecureStoreKeys } from '@buoy/storage';
|
|
344
|
+
|
|
345
|
+
registerSecureStoreKeys(SecureStore, [
|
|
346
|
+
// Bare key names
|
|
347
|
+
'auth.accessToken',
|
|
348
|
+
'auth.refreshToken',
|
|
349
|
+
|
|
350
|
+
// Keys written with options must register the SAME options,
|
|
351
|
+
// or the Keychain returns null on read
|
|
352
|
+
{ key: 'session.deviceKey', keychainService: 'com.myapp.auth' },
|
|
353
|
+
|
|
354
|
+
// Biometric-protected keys are listed but NEVER auto-read,
|
|
355
|
+
// so DevTools won't trigger surprise Face ID / fingerprint prompts
|
|
356
|
+
{ key: 'secure.pin', requireAuthentication: true },
|
|
357
|
+
]);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### `registerSecureStoreKeys(module, keys)`
|
|
361
|
+
|
|
362
|
+
| Param | Type | Description |
|
|
363
|
+
|---|---|---|
|
|
364
|
+
| `module` | `SecureStoreLike` | The `expo-secure-store` module (`import * as SecureStore`) |
|
|
365
|
+
| `keys` | `(string \| { key, keychainService?, requireAuthentication?, description? })[]` | Keys to expose, with the options they were written with |
|
|
366
|
+
|
|
367
|
+
Registered keys that have no stored value show up as **Missing** — handy for spotting initialization bugs. Use `unregisterSecureStoreKey(key)` to remove one from the tool (the stored value is untouched).
|
|
368
|
+
|
|
369
|
+
### Platform notes
|
|
370
|
+
|
|
371
|
+
- iOS Keychain values **persist across app uninstalls** (same bundle ID); Android values don't.
|
|
372
|
+
- Values are read with the exact options each key was registered with — a `keychainService` mismatch reads as missing.
|
|
373
|
+
|
|
330
374
|
## API Reference
|
|
331
375
|
|
|
332
376
|
### Presets
|
package/lib/commonjs/index.js
CHANGED
|
@@ -147,6 +147,12 @@ Object.defineProperty(exports, "registerMMKVInstance", {
|
|
|
147
147
|
return _MMKVInstanceRegistry.registerMMKVInstance;
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
|
+
Object.defineProperty(exports, "registerSecureStoreKeys", {
|
|
151
|
+
enumerable: true,
|
|
152
|
+
get: function () {
|
|
153
|
+
return _SecureStoreRegistry.registerSecureStoreKeys;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
150
156
|
Object.defineProperty(exports, "storageEventStore", {
|
|
151
157
|
enumerable: true,
|
|
152
158
|
get: function () {
|
|
@@ -177,6 +183,12 @@ Object.defineProperty(exports, "unregisterMMKVInstance", {
|
|
|
177
183
|
return _MMKVInstanceRegistry.unregisterMMKVInstance;
|
|
178
184
|
}
|
|
179
185
|
});
|
|
186
|
+
Object.defineProperty(exports, "unregisterSecureStoreKey", {
|
|
187
|
+
enumerable: true,
|
|
188
|
+
get: function () {
|
|
189
|
+
return _SecureStoreRegistry.unregisterSecureStoreKey;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
180
192
|
Object.defineProperty(exports, "useAsyncStorageKeys", {
|
|
181
193
|
enumerable: true,
|
|
182
194
|
get: function () {
|
|
@@ -240,5 +252,6 @@ var _mmkvAvailability = require("./storage/utils/mmkvAvailability");
|
|
|
240
252
|
var _mmkvTypeDetection = require("./storage/utils/mmkvTypeDetection");
|
|
241
253
|
var _storageTimeTravelUtils = require("./storage/utils/storageTimeTravelUtils");
|
|
242
254
|
var _MMKVInstanceRegistry = require("./storage/utils/MMKVInstanceRegistry");
|
|
255
|
+
var _SecureStoreRegistry = require("./storage/utils/SecureStoreRegistry");
|
|
243
256
|
var _storageSyncAdapter = require("./storage/sync/storageSyncAdapter");
|
|
244
257
|
var _storageEventStore = require("./storage/stores/storageEventStore");
|
|
@@ -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
|
};
|