@buoy-gg/storage 7.0.32 → 7.0.34
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/preset.js +8 -22
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +54 -13
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +61 -155
- package/lib/commonjs/storage/hooks/useStorageEvents.js +15 -52
- package/lib/commonjs/storage/utils/snapshotProvider.js +37 -100
- package/lib/module/preset.js +7 -20
- package/lib/module/storage/components/GameUIStorageBrowser.js +55 -14
- package/lib/module/storage/components/StorageModalWithTabs.js +63 -157
- package/lib/module/storage/hooks/useStorageEvents.js +16 -53
- package/lib/module/storage/utils/snapshotProvider.js +38 -101
- package/lib/typescript/preset.d.ts +2 -8
- package/lib/typescript/preset.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -108,24 +108,12 @@ async function capture() {
|
|
|
108
108
|
}
|
|
109
109
|
async function restore(data, options) {
|
|
110
110
|
const snapshot = data;
|
|
111
|
-
const
|
|
112
|
-
const includedSet = options?.includeKeys ? new Set(options.includeKeys) : null;
|
|
111
|
+
const scope = (0, _sharedUi.createRestoreScope)(options);
|
|
113
112
|
/** True when this item may be touched (scope + exclusions agree). */
|
|
114
|
-
const touch =
|
|
115
|
-
|
|
116
|
-
const result = {
|
|
117
|
-
ok: true,
|
|
118
|
-
applied: 0,
|
|
119
|
-
skipped: [],
|
|
120
|
-
warnings: []
|
|
121
|
-
};
|
|
113
|
+
const touch = scope.touch;
|
|
114
|
+
const result = (0, _sharedUi.emptyRestoreResult)();
|
|
122
115
|
if (!snapshot) {
|
|
123
|
-
return
|
|
124
|
-
ok: false,
|
|
125
|
-
applied: 0,
|
|
126
|
-
skipped: [],
|
|
127
|
-
warnings: ["empty storage snapshot"]
|
|
128
|
-
};
|
|
116
|
+
return (0, _sharedUi.failedRestore)("empty storage snapshot");
|
|
129
117
|
}
|
|
130
118
|
|
|
131
119
|
// ── AsyncStorage: delete app keys missing from the snapshot, write the rest ──
|
|
@@ -134,7 +122,7 @@ async function restore(data, options) {
|
|
|
134
122
|
const snapKeys = new Set(snapPairs.map(([k]) => k));
|
|
135
123
|
const liveKeys = (await _asyncStorage.default.getAllKeys()).filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k));
|
|
136
124
|
const toRemove = liveKeys.filter(k => !snapKeys.has(k) && touch(`async:${k}`));
|
|
137
|
-
|
|
125
|
+
scope.countExcluded((snapshot.async ?? []).length - snapPairs.length);
|
|
138
126
|
if (toRemove.length > 0) await (0, _asyncStorageCompat.removeMany)(toRemove);
|
|
139
127
|
if (snapPairs.length > 0) await (0, _asyncStorageCompat.writeMany)(snapPairs);
|
|
140
128
|
result.applied += snapPairs.length + toRemove.length;
|
|
@@ -167,10 +155,7 @@ async function restore(data, options) {
|
|
|
167
155
|
const snapKeys = new Set(entries.map(e => e.key));
|
|
168
156
|
const liveKeys = instance.getAllKeys().filter(key => !(0, _sharedUi.isDevToolsStorageKey)(key));
|
|
169
157
|
for (const key of liveKeys) {
|
|
170
|
-
if (!
|
|
171
|
-
excludedHits += 1;
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
158
|
+
if (!scope.allows(`mmkv:${id}/${key}`)) continue;
|
|
174
159
|
if (!snapKeys.has(key)) {
|
|
175
160
|
deleteMMKVKey(instance, key);
|
|
176
161
|
result.applied += 1;
|
|
@@ -201,10 +186,7 @@ async function restore(data, options) {
|
|
|
201
186
|
key,
|
|
202
187
|
options
|
|
203
188
|
} of _SecureStoreRegistry.secureStoreRegistry.getAll()) {
|
|
204
|
-
if (!
|
|
205
|
-
excludedHits += 1;
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
189
|
+
if (!scope.allows(`secure:${key}`)) continue;
|
|
208
190
|
if (options.requireAuthentication) {
|
|
209
191
|
if (key in secureSnap) {
|
|
210
192
|
result.skipped.push({
|
|
@@ -237,20 +219,14 @@ async function restore(data, options) {
|
|
|
237
219
|
});
|
|
238
220
|
}
|
|
239
221
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
222
|
+
const excludedWarning = scope.excludedWarning("item(s)");
|
|
223
|
+
if (excludedWarning) result.warnings.push(excludedWarning);
|
|
243
224
|
return result;
|
|
244
225
|
}
|
|
245
226
|
|
|
246
227
|
/** Wipe to fresh-install state (Buoy keys preserved). Used by baseline restore. */
|
|
247
228
|
async function clear() {
|
|
248
|
-
const result =
|
|
249
|
-
ok: true,
|
|
250
|
-
applied: 0,
|
|
251
|
-
skipped: [],
|
|
252
|
-
warnings: []
|
|
253
|
-
};
|
|
229
|
+
const result = (0, _sharedUi.emptyRestoreResult)();
|
|
254
230
|
try {
|
|
255
231
|
await (0, _clearAllStorage.clearAllAppStorage)();
|
|
256
232
|
result.applied += 1;
|
|
@@ -301,40 +277,24 @@ async function clear() {
|
|
|
301
277
|
return result;
|
|
302
278
|
}
|
|
303
279
|
const ITEM_RENDER_CHAR_LIMIT = 256 * 1024;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Storage values are strings (or MMKV primitives): identity equality and a
|
|
283
|
+
* character-length size gate are the right tests, not a deep walk.
|
|
284
|
+
*/
|
|
304
285
|
function classifyValue(key, label, oldValue, newValue, inOld, inNew) {
|
|
305
|
-
|
|
286
|
+
return (0, _sharedUi.classifyPreviewItem)({
|
|
306
287
|
key,
|
|
307
288
|
label,
|
|
308
|
-
verdict: "removed",
|
|
309
|
-
oldValue
|
|
310
|
-
};
|
|
311
|
-
if (!inOld) return {
|
|
312
|
-
key,
|
|
313
|
-
label,
|
|
314
|
-
verdict: "added",
|
|
315
|
-
newValue
|
|
316
|
-
};
|
|
317
|
-
if (Object.is(oldValue, newValue)) return {
|
|
318
|
-
key,
|
|
319
|
-
label,
|
|
320
|
-
verdict: "unchanged"
|
|
321
|
-
};
|
|
322
|
-
const tooLarge = typeof oldValue === "string" && oldValue.length > ITEM_RENDER_CHAR_LIMIT || typeof newValue === "string" && newValue.length > ITEM_RENDER_CHAR_LIMIT;
|
|
323
|
-
if (tooLarge) {
|
|
324
|
-
return {
|
|
325
|
-
key,
|
|
326
|
-
label,
|
|
327
|
-
verdict: "too-large",
|
|
328
|
-
reason: "value too large to render a diff — restore still applies it"
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
key,
|
|
333
|
-
label,
|
|
334
|
-
verdict: "changed",
|
|
335
289
|
oldValue,
|
|
336
|
-
newValue
|
|
337
|
-
|
|
290
|
+
newValue,
|
|
291
|
+
inOld,
|
|
292
|
+
inNew
|
|
293
|
+
}, {
|
|
294
|
+
equal: Object.is,
|
|
295
|
+
tooLarge: value => typeof value === "string" && value.length > ITEM_RENDER_CHAR_LIMIT,
|
|
296
|
+
tooLargeReason: "value too large to render a diff — restore still applies it"
|
|
297
|
+
});
|
|
338
298
|
}
|
|
339
299
|
|
|
340
300
|
/** Per-key restore preview across all three backends. */
|
|
@@ -356,46 +316,31 @@ async function previewItems(target, base) {
|
|
|
356
316
|
// ── AsyncStorage ──
|
|
357
317
|
const oldAsync = new Map(baseData.async ?? []);
|
|
358
318
|
const newAsync = new Map(targetData.async ?? []);
|
|
359
|
-
for (const key of
|
|
319
|
+
for (const key of (0, _sharedUi.unionKeys)(oldAsync, newAsync)) {
|
|
360
320
|
if ((0, _sharedUi.isDevToolsStorageKey)(key)) continue;
|
|
361
321
|
items.push(classifyValue(`async:${key}`, key, oldAsync.get(key), newAsync.get(key), oldAsync.has(key), newAsync.has(key)));
|
|
362
322
|
}
|
|
363
323
|
|
|
364
324
|
// ── MMKV ──
|
|
365
|
-
const
|
|
366
|
-
for (const id of instanceIds) {
|
|
325
|
+
for (const id of (0, _sharedUi.unionKeys)(baseData.mmkv, targetData.mmkv)) {
|
|
367
326
|
const registered = liveMode ? _MMKVInstanceRegistry.mmkvInstanceRegistry.get(id) : null;
|
|
368
327
|
const instanceUnavailable = liveMode && !registered;
|
|
369
328
|
const readOnly = liveMode && registered?.instance.isReadOnly === true;
|
|
370
329
|
const oldEntries = new Map((baseData.mmkv?.[id] ?? []).map(e => [e.key, e]));
|
|
371
330
|
const newEntries = new Map((targetData.mmkv?.[id] ?? []).map(e => [e.key, e]));
|
|
372
|
-
for (const key of
|
|
331
|
+
for (const key of (0, _sharedUi.unionKeys)(oldEntries, newEntries)) {
|
|
373
332
|
if ((0, _sharedUi.isDevToolsStorageKey)(key)) continue;
|
|
374
333
|
const itemKey = `mmkv:${id}/${key}`;
|
|
375
334
|
const label = `${id} · ${key}`;
|
|
376
335
|
const oldE = oldEntries.get(key);
|
|
377
336
|
const newE = newEntries.get(key);
|
|
378
337
|
let item = classifyValue(itemKey, label, oldE?.value, newE?.value, oldEntries.has(key), newEntries.has(key));
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
};
|
|
386
|
-
} else if (readOnly) {
|
|
387
|
-
item = {
|
|
388
|
-
...item,
|
|
389
|
-
verdict: "wont-apply",
|
|
390
|
-
reason: "MMKV instance registered read-only"
|
|
391
|
-
};
|
|
392
|
-
} else if (newE?.valueType === "buffer" || oldE?.valueType === "buffer") {
|
|
393
|
-
item = {
|
|
394
|
-
...item,
|
|
395
|
-
verdict: "wont-apply",
|
|
396
|
-
reason: "ArrayBuffer values cannot be restored"
|
|
397
|
-
};
|
|
398
|
-
}
|
|
338
|
+
if (instanceUnavailable) {
|
|
339
|
+
item = (0, _sharedUi.wontApply)(item, "MMKV instance not registered");
|
|
340
|
+
} else if (readOnly) {
|
|
341
|
+
item = (0, _sharedUi.wontApply)(item, "MMKV instance registered read-only");
|
|
342
|
+
} else if (newE?.valueType === "buffer" || oldE?.valueType === "buffer") {
|
|
343
|
+
item = (0, _sharedUi.wontApply)(item, "ArrayBuffer values cannot be restored");
|
|
399
344
|
}
|
|
400
345
|
items.push(item);
|
|
401
346
|
}
|
|
@@ -405,22 +350,14 @@ async function previewItems(target, base) {
|
|
|
405
350
|
const oldSecure = baseData.secure ?? {};
|
|
406
351
|
const newSecure = targetData.secure ?? {};
|
|
407
352
|
const registeredSecure = liveMode ? new Map(_SecureStoreRegistry.secureStoreRegistry.getAll().map(e => [e.key, e])) : null;
|
|
408
|
-
for (const key of
|
|
353
|
+
for (const key of (0, _sharedUi.unionKeys)(oldSecure, newSecure)) {
|
|
409
354
|
let item = classifyValue(`secure:${key}`, key, oldSecure[key], newSecure[key], key in oldSecure, key in newSecure);
|
|
410
|
-
if (liveMode
|
|
355
|
+
if (liveMode) {
|
|
411
356
|
const entry = registeredSecure?.get(key);
|
|
412
357
|
if (!entry) {
|
|
413
|
-
item =
|
|
414
|
-
...item,
|
|
415
|
-
verdict: "wont-apply",
|
|
416
|
-
reason: "key no longer registered with registerSecureStoreKeys"
|
|
417
|
-
};
|
|
358
|
+
item = (0, _sharedUi.wontApply)(item, "key no longer registered with registerSecureStoreKeys");
|
|
418
359
|
} else if (entry.options.requireAuthentication) {
|
|
419
|
-
item =
|
|
420
|
-
...item,
|
|
421
|
-
verdict: "wont-apply",
|
|
422
|
-
reason: "biometric-protected keys are never written"
|
|
423
|
-
};
|
|
360
|
+
item = (0, _sharedUi.wontApply)(item, "biometric-protected keys are never written");
|
|
424
361
|
}
|
|
425
362
|
}
|
|
426
363
|
items.push(item);
|
package/lib/module/preset.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { StorageIcon } from "@buoy-gg/floating-tools-core";
|
|
21
|
+
import { createToolFactory, defineToolPreset } from "@buoy-gg/shared-ui";
|
|
21
22
|
import { StorageModalWithTabs } from "./storage/components/StorageModalWithTabs";
|
|
22
23
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
23
24
|
/**
|
|
@@ -28,7 +29,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
28
29
|
* - Live storage event monitoring
|
|
29
30
|
* - Required key validation
|
|
30
31
|
*/
|
|
31
|
-
export const storageToolPreset = {
|
|
32
|
+
export const storageToolPreset = defineToolPreset({
|
|
32
33
|
id: "storage",
|
|
33
34
|
name: "STORAGE",
|
|
34
35
|
description: "AsyncStorage browser",
|
|
@@ -43,7 +44,7 @@ export const storageToolPreset = {
|
|
|
43
44
|
requiredStorageKeys: [],
|
|
44
45
|
enableSharedModalDimensions: false
|
|
45
46
|
}
|
|
46
|
-
};
|
|
47
|
+
});
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Create a custom AsyncStorage browser tool configuration.
|
|
@@ -69,21 +70,7 @@ export const storageToolPreset = {
|
|
|
69
70
|
* });
|
|
70
71
|
* ```
|
|
71
72
|
*/
|
|
72
|
-
export
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
description: options?.description || "AsyncStorage browser",
|
|
77
|
-
slot: "both",
|
|
78
|
-
icon: ({
|
|
79
|
-
size
|
|
80
|
-
}) => /*#__PURE__*/_jsx(StorageIcon, {
|
|
81
|
-
size: size
|
|
82
|
-
}),
|
|
83
|
-
component: StorageModalWithTabs,
|
|
84
|
-
props: {
|
|
85
|
-
requiredStorageKeys: options?.requiredStorageKeys || [],
|
|
86
|
-
enableSharedModalDimensions: options?.enableSharedModalDimensions !== undefined ? options.enableSharedModalDimensions : false
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
}
|
|
73
|
+
export const createStorageTool = createToolFactory(storageToolPreset, options => ({
|
|
74
|
+
requiredStorageKeys: options.requiredStorageKeys || [],
|
|
75
|
+
enableSharedModalDimensions: options.enableSharedModalDimensions ?? false
|
|
76
|
+
}));
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useMemo, useCallback, useState, useEffect, useRef } from "react";
|
|
4
4
|
import { StyleSheet, Text, View, TouchableOpacity, ScrollView, Alert } from "react-native";
|
|
5
5
|
import { absoluteFill, useLimit, TIER_LIMITS } from "@buoy-gg/shared-ui";
|
|
6
|
-
import { Database, Zap, ProBadge, ProUpgradeModal } from "@buoy-gg/shared-ui";
|
|
6
|
+
import { Database, Zap, ProBadge, ProUpgradeModal, ToolEmptyState, resolveListEmptyState } from "@buoy-gg/shared-ui";
|
|
7
7
|
import { isDevToolsStorageKey } from "@buoy-gg/shared-ui";
|
|
8
8
|
import { StorageKeySection, storageRowId } from "./StorageKeySection";
|
|
9
9
|
import { StorageKeyDetailScreen } from "./StorageKeyDetailScreen";
|
|
@@ -859,19 +859,60 @@ export function GameUIStorageBrowser({
|
|
|
859
859
|
children: searchQuery ? "Upgrade to Pro to search your full storage" : "Upgrade to Pro to unlock full storage access"
|
|
860
860
|
})]
|
|
861
861
|
}) : null;
|
|
862
|
-
const emptyStateNode =
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
862
|
+
const emptyStateNode = (() => {
|
|
863
|
+
// Short-circuits on the first real key, so this costs nothing.
|
|
864
|
+
const hasAnyKeys = allStorageKeys.some(k => !isDevToolsStorageKey(k.key));
|
|
865
|
+
|
|
866
|
+
// Three causes here are unlike any other tool's: an empty MISSING or
|
|
867
|
+
// ISSUES list is a PASS, not a failure, and an empty VALID list means
|
|
868
|
+
// everything is broken. Those skip the shared resolver and say so.
|
|
869
|
+
let content;
|
|
870
|
+
if (searchQuery || !hasAnyKeys) {
|
|
871
|
+
content = resolveListEmptyState({
|
|
872
|
+
subject: "keys",
|
|
873
|
+
verb: "registered",
|
|
874
|
+
search: searchQuery,
|
|
875
|
+
total: hasAnyKeys ? 1 : 0,
|
|
876
|
+
waiting: {
|
|
877
|
+
// Previously this fell into the "all" branch and claimed every key
|
|
878
|
+
// had an issue — of the zero keys that exist.
|
|
879
|
+
otto: "empty",
|
|
880
|
+
title: "No storage keys",
|
|
881
|
+
message: "Nothing is registered for this tool to read."
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
if (searchQuery && hasAnyKeys) {
|
|
885
|
+
// No count: the pre-search total would mean re-running the whole key
|
|
886
|
+
// pipeline a second time for a number the sentence does not need.
|
|
887
|
+
content = {
|
|
888
|
+
...content,
|
|
889
|
+
message: "No keys match. Clear the search to see them."
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
} else if (activeFilter === "missing") {
|
|
893
|
+
content = {
|
|
894
|
+
otto: "thumbsup",
|
|
895
|
+
title: "No missing keys",
|
|
896
|
+
message: "All required keys are present"
|
|
897
|
+
};
|
|
898
|
+
} else if (activeFilter === "issues") {
|
|
899
|
+
content = {
|
|
900
|
+
otto: "cool",
|
|
901
|
+
title: "No issues found",
|
|
902
|
+
message: "All storage keys are correctly configured"
|
|
903
|
+
};
|
|
904
|
+
} else {
|
|
905
|
+
content = {
|
|
906
|
+
otto: "broken",
|
|
907
|
+
title: "No valid keys found",
|
|
908
|
+
message: "All keys have issues or are missing"
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
return /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
912
|
+
...content,
|
|
913
|
+
testID: "buoy-storage-keys-empty"
|
|
914
|
+
});
|
|
915
|
+
})();
|
|
875
916
|
const upgradeModalNode = /*#__PURE__*/_jsx(ProUpgradeModal, {
|
|
876
917
|
visible: showUpgradeModal,
|
|
877
918
|
onClose: () => setShowUpgradeModal(false),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { View, TouchableOpacity, StyleSheet, FlatList, Alert } from "react-native";
|
|
5
5
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
6
|
-
import { JsModal, ModalHeader, TabSelector, parseValue, devToolsStorageKeys, macOSColors, night,
|
|
6
|
+
import { JsModal, ModalHeader, TabSelector, parseValue, devToolsStorageKeys, macOSColors, night, ToolEmptyState, Filter, RefreshCw, Search, SearchBar, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider, useLimit, TIER_LIMITS, windowDragProps, usePersistedState } from "@buoy-gg/shared-ui";
|
|
7
7
|
import { StorageBrowserMode } from "./StorageBrowserMode";
|
|
8
8
|
import { StorageTypeFilterBar, countByStorageType } from "./StorageTypeFilterBar";
|
|
9
9
|
import { clearAllAppStorage, clearAllStorageIncludingDevTools } from "../utils/clearAllStorage";
|
|
@@ -24,6 +24,41 @@ import { useIsPro } from "@buoy-gg/license";
|
|
|
24
24
|
*/
|
|
25
25
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
26
26
|
export const FREE_TIER_EVENT_LIMIT = TIER_LIMITS["storage.events"].free;
|
|
27
|
+
/**
|
|
28
|
+
* Persistence recipes. These keys were always written straight to
|
|
29
|
+
* AsyncStorage (not the FileSystem-first persistentStorage), so they keep
|
|
30
|
+
* that backend — a user's saved tab, filters and pins survive the upgrade.
|
|
31
|
+
*/
|
|
32
|
+
const ASYNC_STORAGE_BACKEND = {
|
|
33
|
+
getItem: key => AsyncStorage.getItem(key),
|
|
34
|
+
setItem: (key, value) => AsyncStorage.setItem(key, value)
|
|
35
|
+
};
|
|
36
|
+
const TAB_PERSISTENCE = {
|
|
37
|
+
storage: ASYNC_STORAGE_BACKEND,
|
|
38
|
+
// Stored as the bare tab name, not JSON.
|
|
39
|
+
parse: raw => raw === "browser" || raw === "events" ? raw : undefined,
|
|
40
|
+
serialize: tab => tab
|
|
41
|
+
};
|
|
42
|
+
const BOOLEAN_PERSISTENCE = {
|
|
43
|
+
storage: ASYNC_STORAGE_BACKEND,
|
|
44
|
+
// Stored as "true" / "false".
|
|
45
|
+
parse: raw => raw === "true",
|
|
46
|
+
serialize: value => value.toString()
|
|
47
|
+
};
|
|
48
|
+
const STRING_SET_PERSISTENCE = {
|
|
49
|
+
storage: ASYNC_STORAGE_BACKEND,
|
|
50
|
+
parse: raw => new Set(JSON.parse(raw)),
|
|
51
|
+
serialize: set => JSON.stringify(Array.from(set))
|
|
52
|
+
};
|
|
53
|
+
const IGNORED_PATTERNS_PERSISTENCE = {
|
|
54
|
+
...STRING_SET_PERSISTENCE,
|
|
55
|
+
parse: raw => {
|
|
56
|
+
const filters = JSON.parse(raw);
|
|
57
|
+
// Always ensure @react_buoy is included to hide dev tool keys
|
|
58
|
+
if (!filters.includes("@react_buoy")) filters.push("@react_buoy");
|
|
59
|
+
return new Set(filters);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
27
62
|
export function StorageModalWithTabs({
|
|
28
63
|
visible,
|
|
29
64
|
onClose,
|
|
@@ -38,7 +73,7 @@ export function StorageModalWithTabs({
|
|
|
38
73
|
}) {
|
|
39
74
|
const isPro = useIsPro();
|
|
40
75
|
const eventLimit = useLimit("storage.events");
|
|
41
|
-
const [activeTab, setActiveTab] =
|
|
76
|
+
const [activeTab, setActiveTab] = usePersistedState(devToolsStorageKeys.storage.activeTab(), "browser", TAB_PERSISTENCE);
|
|
42
77
|
useEffect(() => {
|
|
43
78
|
onTabChange?.(activeTab);
|
|
44
79
|
}, [activeTab, onTabChange]);
|
|
@@ -54,7 +89,7 @@ export function StorageModalWithTabs({
|
|
|
54
89
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
55
90
|
|
|
56
91
|
// Local state to control subscription (true = subscribed, false = unsubscribed)
|
|
57
|
-
const [isListeningEnabled, setIsListeningEnabled] =
|
|
92
|
+
const [isListeningEnabled, setIsListeningEnabled] = usePersistedState(devToolsStorageKeys.storage.isMonitoring(), true, BOOLEAN_PERSISTENCE);
|
|
58
93
|
|
|
59
94
|
// Event Listener state - using centralized store via hook
|
|
60
95
|
// Pass enabled option to control subscription - this allows events tool to
|
|
@@ -71,8 +106,9 @@ export function StorageModalWithTabs({
|
|
|
71
106
|
const [selectedHistoryEventIndex, setSelectedHistoryEventIndex] = useState(null);
|
|
72
107
|
const [selectedEventIndex, setSelectedEventIndex] = useState(0);
|
|
73
108
|
const [showFilters, setShowFilters] = useState(false);
|
|
74
|
-
const [ignoredPatterns, setIgnoredPatterns] =
|
|
75
|
-
|
|
109
|
+
const [ignoredPatterns, setIgnoredPatterns] = usePersistedState(devToolsStorageKeys.storage.eventFilters(),
|
|
110
|
+
// Auto-hide dev tool keys by default
|
|
111
|
+
new Set(["@react_buoy", "@RNAsyncStorage", "redux-persist", "persist:"]), IGNORED_PATTERNS_PERSISTENCE);
|
|
76
112
|
/**
|
|
77
113
|
* Which backend both tabs are looking at.
|
|
78
114
|
*
|
|
@@ -88,12 +124,8 @@ export function StorageModalWithTabs({
|
|
|
88
124
|
const [storageTypeFilter, setStorageTypeFilter] = useState("all");
|
|
89
125
|
const [enabledStorageTypes, setEnabledStorageTypes] = useState(new Set(['async', 'mmkv', 'secure']) // All enabled by default
|
|
90
126
|
);
|
|
91
|
-
const [pinnedKeys, setPinnedKeys] =
|
|
127
|
+
const [pinnedKeys, setPinnedKeys] = usePersistedState(devToolsStorageKeys.storage.pinnedKeys(), new Set(), STRING_SET_PERSISTENCE);
|
|
92
128
|
const lastEventRef = useRef(null);
|
|
93
|
-
const hasLoadedFilters = useRef(false);
|
|
94
|
-
const hasLoadedPins = useRef(false);
|
|
95
|
-
const hasLoadedTabState = useRef(false);
|
|
96
|
-
const hasLoadedMonitoringState = useRef(false);
|
|
97
129
|
|
|
98
130
|
// isListening = enabled and capturing (has subscribers)
|
|
99
131
|
const isListening = isListeningEnabled && isCapturing;
|
|
@@ -108,140 +140,6 @@ export function StorageModalWithTabs({
|
|
|
108
140
|
|
|
109
141
|
// Timer removed - using useTickEveryMinute hook instead
|
|
110
142
|
|
|
111
|
-
// Load persisted tab state on mount
|
|
112
|
-
useEffect(() => {
|
|
113
|
-
if (!visible || hasLoadedTabState.current) return;
|
|
114
|
-
const loadTabState = async () => {
|
|
115
|
-
try {
|
|
116
|
-
const storedTab = await AsyncStorage.getItem(devToolsStorageKeys.storage.activeTab());
|
|
117
|
-
if (storedTab && (storedTab === "browser" || storedTab === "events")) {
|
|
118
|
-
setActiveTab(storedTab);
|
|
119
|
-
}
|
|
120
|
-
hasLoadedTabState.current = true;
|
|
121
|
-
} catch (error) {
|
|
122
|
-
// Failed to load tab state
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
loadTabState();
|
|
126
|
-
}, [visible]);
|
|
127
|
-
|
|
128
|
-
// Load persisted monitoring state on mount
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
if (!visible || hasLoadedMonitoringState.current) return;
|
|
131
|
-
const loadMonitoringState = async () => {
|
|
132
|
-
try {
|
|
133
|
-
const storedMonitoring = await AsyncStorage.getItem(devToolsStorageKeys.storage.isMonitoring());
|
|
134
|
-
if (storedMonitoring !== null) {
|
|
135
|
-
const shouldMonitor = storedMonitoring === "true";
|
|
136
|
-
setIsListeningEnabled(shouldMonitor);
|
|
137
|
-
}
|
|
138
|
-
hasLoadedMonitoringState.current = true;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
// Failed to load monitoring state
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
loadMonitoringState();
|
|
144
|
-
}, [visible]);
|
|
145
|
-
|
|
146
|
-
// Note: Conversations will appear when storage events are triggered
|
|
147
|
-
// Click on any conversation to see the unified view with toggle cards
|
|
148
|
-
|
|
149
|
-
// Save tab state when it changes
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
if (!hasLoadedTabState.current) return; // Don't save on initial load
|
|
152
|
-
|
|
153
|
-
const saveTabState = async () => {
|
|
154
|
-
try {
|
|
155
|
-
await AsyncStorage.setItem(devToolsStorageKeys.storage.activeTab(), activeTab);
|
|
156
|
-
} catch (error) {
|
|
157
|
-
// Failed to save tab state
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
saveTabState();
|
|
161
|
-
}, [activeTab]);
|
|
162
|
-
|
|
163
|
-
// Save monitoring state when it changes
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
if (!hasLoadedMonitoringState.current) return; // Don't save on initial load
|
|
166
|
-
|
|
167
|
-
const saveMonitoringState = async () => {
|
|
168
|
-
try {
|
|
169
|
-
await AsyncStorage.setItem(devToolsStorageKeys.storage.isMonitoring(), isListening.toString());
|
|
170
|
-
} catch (error) {
|
|
171
|
-
// Failed to save monitoring state
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
saveMonitoringState();
|
|
175
|
-
}, [isListening]);
|
|
176
|
-
|
|
177
|
-
// Load persisted filters on mount
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
if (!visible || hasLoadedFilters.current) return;
|
|
180
|
-
const loadFilters = async () => {
|
|
181
|
-
try {
|
|
182
|
-
const storedFilters = await AsyncStorage.getItem(devToolsStorageKeys.storage.eventFilters());
|
|
183
|
-
if (storedFilters) {
|
|
184
|
-
const filters = JSON.parse(storedFilters);
|
|
185
|
-
// Always ensure @react_buoy is included to hide dev tool keys
|
|
186
|
-
if (!filters.includes("@react_buoy")) {
|
|
187
|
-
filters.push("@react_buoy");
|
|
188
|
-
}
|
|
189
|
-
setIgnoredPatterns(new Set(filters));
|
|
190
|
-
}
|
|
191
|
-
hasLoadedFilters.current = true;
|
|
192
|
-
} catch (error) {
|
|
193
|
-
// Failed to load filters
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
loadFilters();
|
|
197
|
-
}, [visible]);
|
|
198
|
-
|
|
199
|
-
// Save filters when they change
|
|
200
|
-
useEffect(() => {
|
|
201
|
-
if (!hasLoadedFilters.current) return; // Don't save on initial load
|
|
202
|
-
|
|
203
|
-
const saveFilters = async () => {
|
|
204
|
-
try {
|
|
205
|
-
const filters = Array.from(ignoredPatterns);
|
|
206
|
-
await AsyncStorage.setItem(devToolsStorageKeys.storage.eventFilters(), JSON.stringify(filters));
|
|
207
|
-
} catch (error) {
|
|
208
|
-
// Failed to save filters
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
saveFilters();
|
|
212
|
-
}, [ignoredPatterns]);
|
|
213
|
-
|
|
214
|
-
// Load persisted pinned keys on mount
|
|
215
|
-
useEffect(() => {
|
|
216
|
-
if (!visible || hasLoadedPins.current) return;
|
|
217
|
-
const loadPins = async () => {
|
|
218
|
-
try {
|
|
219
|
-
const stored = await AsyncStorage.getItem(devToolsStorageKeys.storage.pinnedKeys());
|
|
220
|
-
if (stored) {
|
|
221
|
-
setPinnedKeys(new Set(JSON.parse(stored)));
|
|
222
|
-
}
|
|
223
|
-
hasLoadedPins.current = true;
|
|
224
|
-
} catch (error) {
|
|
225
|
-
// Failed to load pinned keys
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
loadPins();
|
|
229
|
-
}, [visible]);
|
|
230
|
-
|
|
231
|
-
// Save pinned keys when they change
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
if (!hasLoadedPins.current) return; // Don't save on initial load
|
|
234
|
-
|
|
235
|
-
const savePins = async () => {
|
|
236
|
-
try {
|
|
237
|
-
await AsyncStorage.setItem(devToolsStorageKeys.storage.pinnedKeys(), JSON.stringify(Array.from(pinnedKeys)));
|
|
238
|
-
} catch (error) {
|
|
239
|
-
// Failed to save pinned keys
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
savePins();
|
|
243
|
-
}, [pinnedKeys]);
|
|
244
|
-
|
|
245
143
|
// Event listener setup - now handled by useStorageEvents hook
|
|
246
144
|
// The hook subscribes to the centralized storageEventStore
|
|
247
145
|
|
|
@@ -671,18 +569,26 @@ export function StorageModalWithTabs({
|
|
|
671
569
|
const filtered = storageTypeFilter !== "all";
|
|
672
570
|
return /*#__PURE__*/_jsxs(View, {
|
|
673
571
|
style: styles.flexFill,
|
|
674
|
-
children: [eventTypeFilterBar, /*#__PURE__*/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
572
|
+
children: [eventTypeFilterBar, /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
573
|
+
testID: "buoy-storage-events-empty"
|
|
574
|
+
// Three literal causes, no search on this tab — so it renders
|
|
575
|
+
// through the shared shell without the shared resolver. The
|
|
576
|
+
// backend pills need their own sentence naming the pill that
|
|
577
|
+
// undoes them, which the generic "Filtered Out" would drop.
|
|
578
|
+
,
|
|
579
|
+
...(!isListening ? {
|
|
580
|
+
otto: "paused",
|
|
581
|
+
title: "Event listener is paused",
|
|
582
|
+
message: "Press play to start monitoring"
|
|
583
|
+
} : filtered ? {
|
|
584
|
+
otto: "filtering",
|
|
585
|
+
title: `No ${storageTypeFilter} events`,
|
|
586
|
+
message: "Other backends have events — pick All Types to see them"
|
|
587
|
+
} : {
|
|
588
|
+
otto: "listening",
|
|
589
|
+
title: "No storage events yet",
|
|
590
|
+
message: "Storage operations will appear here"
|
|
591
|
+
})
|
|
686
592
|
})]
|
|
687
593
|
});
|
|
688
594
|
}
|