@buoy-gg/storage 7.0.39 → 7.0.41

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.
Files changed (96) hide show
  1. package/README.md +4 -0
  2. package/lib/commonjs/index.js +8 -1
  3. package/lib/commonjs/preset.js +9 -4
  4. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +3 -2
  5. package/lib/commonjs/storage/components/MMKVInstanceSelector.js +3 -2
  6. package/lib/commonjs/storage/components/StorageActionButtons.js +6 -5
  7. package/lib/commonjs/storage/components/StorageEventCard.js +2 -1
  8. package/lib/commonjs/storage/components/StorageEventFilterView.js +7 -6
  9. package/lib/commonjs/storage/components/StorageKeyStats.js +5 -4
  10. package/lib/commonjs/storage/components/StorageTypeFilterBar.js +4 -3
  11. package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +55 -12
  12. package/lib/commonjs/storage/sync/storageSyncAdapter.js +15 -1
  13. package/lib/commonjs/storage/utils/AsyncStorageListener.web.js +89 -0
  14. package/lib/commonjs/storage/utils/browserStorageLifecycle.js +10 -0
  15. package/lib/commonjs/storage/utils/browserStorageLifecycle.web.js +70 -0
  16. package/lib/commonjs/storage/utils/mmkvAvailability.web.js +21 -0
  17. package/lib/commonjs/storage/utils/storageLabels.js +13 -0
  18. package/lib/commonjs/storage/utils/storageLabels.web.js +13 -0
  19. package/lib/module/index.js +3 -1
  20. package/lib/module/preset.js +11 -5
  21. package/lib/module/storage/components/GameUIStorageBrowser.js +3 -2
  22. package/lib/module/storage/components/MMKVInstanceSelector.js +4 -3
  23. package/lib/module/storage/components/StorageActionButtons.js +6 -5
  24. package/lib/module/storage/components/StorageEventCard.js +2 -1
  25. package/lib/module/storage/components/StorageEventFilterView.js +7 -6
  26. package/lib/module/storage/components/StorageKeyStats.js +5 -4
  27. package/lib/module/storage/components/StorageTypeFilterBar.js +4 -3
  28. package/lib/module/storage/hooks/useSecureStoreKeys.js +55 -12
  29. package/lib/module/storage/sync/storageSyncAdapter.js +12 -0
  30. package/lib/module/storage/utils/AsyncStorageListener.web.js +76 -0
  31. package/lib/module/storage/utils/browserStorageLifecycle.js +6 -0
  32. package/lib/module/storage/utils/browserStorageLifecycle.web.js +66 -0
  33. package/lib/module/storage/utils/mmkvAvailability.web.js +15 -0
  34. package/lib/module/storage/utils/storageLabels.js +9 -0
  35. package/lib/module/storage/utils/storageLabels.web.js +9 -0
  36. package/lib/typescript/index.d.ts +1 -0
  37. package/lib/typescript/index.d.ts.map +1 -1
  38. package/lib/typescript/preset.d.ts.map +1 -1
  39. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  40. package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts +0 -8
  41. package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts.map +1 -1
  42. package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +1 -1
  43. package/lib/typescript/storage/components/StorageEventCard.d.ts +0 -7
  44. package/lib/typescript/storage/components/StorageEventCard.d.ts.map +1 -1
  45. package/lib/typescript/storage/components/StorageEventFilterView.d.ts.map +1 -1
  46. package/lib/typescript/storage/components/StorageKeyRow.d.ts +2 -6
  47. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
  48. package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -1
  49. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +0 -15
  50. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +1 -1
  51. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +2 -2
  52. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -1
  53. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +7 -13
  54. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
  55. package/lib/typescript/storage/utils/AsyncStorageListener.web.d.ts +24 -0
  56. package/lib/typescript/storage/utils/AsyncStorageListener.web.d.ts.map +1 -0
  57. package/lib/typescript/storage/utils/browserStorageLifecycle.d.ts +3 -0
  58. package/lib/typescript/storage/utils/browserStorageLifecycle.d.ts.map +1 -0
  59. package/lib/typescript/storage/utils/browserStorageLifecycle.web.d.ts +2 -0
  60. package/lib/typescript/storage/utils/browserStorageLifecycle.web.d.ts.map +1 -0
  61. package/lib/typescript/storage/utils/mmkvAvailability.web.d.ts +4 -0
  62. package/lib/typescript/storage/utils/mmkvAvailability.web.d.ts.map +1 -0
  63. package/lib/typescript/storage/utils/storageLabels.d.ts +8 -0
  64. package/lib/typescript/storage/utils/storageLabels.d.ts.map +1 -0
  65. package/lib/typescript/storage/utils/storageLabels.web.d.ts +8 -0
  66. package/lib/typescript/storage/utils/storageLabels.web.d.ts.map +1 -0
  67. package/lib/web/index.mjs +5 -0
  68. package/package.json +40 -14
  69. package/lib/commonjs/storage/components/DiffViewer/DiffOptionsPanel.js +0 -356
  70. package/lib/commonjs/storage/components/DiffViewer/components/DiffSummary.js +0 -122
  71. package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +0 -414
  72. package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +0 -61
  73. package/lib/commonjs/storage/components/StorageValueEditModal.js +0 -24
  74. package/lib/commonjs/storage/hooks/useKeyEditor.js +0 -31
  75. package/lib/commonjs/storage/hooks/useTreeDraft.js +0 -25
  76. package/lib/module/storage/components/DiffViewer/DiffOptionsPanel.js +0 -352
  77. package/lib/module/storage/components/DiffViewer/components/DiffSummary.js +0 -118
  78. package/lib/module/storage/components/StorageKeyEditorScreen.js +0 -410
  79. package/lib/module/storage/components/StorageScalarKeyEditor.js +0 -57
  80. package/lib/module/storage/components/StorageValueEditModal.js +0 -20
  81. package/lib/module/storage/hooks/useKeyEditor.js +0 -27
  82. package/lib/module/storage/hooks/useTreeDraft.js +0 -21
  83. package/lib/typescript/storage/components/DiffViewer/DiffOptionsPanel.d.ts +0 -18
  84. package/lib/typescript/storage/components/DiffViewer/DiffOptionsPanel.d.ts.map +0 -1
  85. package/lib/typescript/storage/components/DiffViewer/components/DiffSummary.d.ts +0 -12
  86. package/lib/typescript/storage/components/DiffViewer/components/DiffSummary.d.ts.map +0 -1
  87. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +0 -42
  88. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +0 -1
  89. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +0 -24
  90. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +0 -1
  91. package/lib/typescript/storage/components/StorageValueEditModal.d.ts +0 -14
  92. package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +0 -1
  93. package/lib/typescript/storage/hooks/useKeyEditor.d.ts +0 -27
  94. package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +0 -1
  95. package/lib/typescript/storage/hooks/useTreeDraft.d.ts +0 -16
  96. package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +0 -1
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.storageLabels = void 0;
7
+ const storageLabels = exports.storageLabels = {
8
+ async: 'Local',
9
+ asyncFull: 'localStorage',
10
+ mmkv: 'Session',
11
+ secure: 'Secure',
12
+ hasSecure: false
13
+ };
@@ -109,4 +109,6 @@ export { requestStorageBrowserRefresh } from "./storage/utils/storageBrowserRefr
109
109
  // - addMMKVListener, removeAllMMKVListeners, isMMKVInstanceMonitored
110
110
  // - getMonitoredMMKVInstances, getMMKVInstanceCount, getMMKVListenerCount, isMMKVListening
111
111
  // - mmkvListener instance
112
- // - mmkvInstanceRegistry instance
112
+ // - mmkvInstanceRegistry instance
113
+
114
+ export { startBrowserStorageCapture } from './storage/utils/browserStorageLifecycle';
@@ -20,7 +20,8 @@
20
20
  import { StorageIcon } from "@buoy-gg/floating-tools-core";
21
21
  import { createToolFactory, defineToolPreset } from "@buoy-gg/shared-ui";
22
22
  import { StorageModalWithTabs } from "./storage/components/StorageModalWithTabs";
23
- import { jsx as _jsx } from "react/jsx-runtime";
23
+ import { setToolRequiredStorageKeys } from "./storage/sync/storageSyncAdapter";
24
+
24
25
  /**
25
26
  * Pre-configured AsyncStorage browser tool for FloatingDevTools.
26
27
  * Includes:
@@ -29,6 +30,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
29
30
  * - Live storage event monitoring
30
31
  * - Required key validation
31
32
  */
33
+ import { jsx as _jsx } from "react/jsx-runtime";
32
34
  export const storageToolPreset = defineToolPreset({
33
35
  id: "storage",
34
36
  name: "STORAGE",
@@ -70,7 +72,11 @@ export const storageToolPreset = defineToolPreset({
70
72
  * });
71
73
  * ```
72
74
  */
73
- export const createStorageTool = createToolFactory(storageToolPreset, options => ({
74
- requiredStorageKeys: options.requiredStorageKeys || [],
75
- enableSharedModalDimensions: options.enableSharedModalDimensions ?? false
76
- }));
75
+ export const createStorageTool = createToolFactory(storageToolPreset, options => {
76
+ // Shared with the sync adapter so the desktop panel checks the same keys.
77
+ setToolRequiredStorageKeys(options.requiredStorageKeys || []);
78
+ return {
79
+ requiredStorageKeys: options.requiredStorageKeys || [],
80
+ enableSharedModalDimensions: options.enableSharedModalDimensions ?? false
81
+ };
82
+ });
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { storageLabels } from "../utils/storageLabels";
3
4
  import { useMemo, useCallback, useState, useEffect, useRef } from "react";
4
5
  import { StyleSheet, Text, View, TouchableOpacity, ScrollView, Alert } from "react-native";
5
6
  import { absoluteFill, useLimit, TIER_LIMITS, monoFont } from "@buoy-gg/shared-ui";
@@ -186,7 +187,7 @@ export function GameUIStorageBrowser({
186
187
  isLoading: isLoadingSecure,
187
188
  error: secureError,
188
189
  refresh: refreshSecure
189
- } = useSecureStoreKeys();
190
+ } = useSecureStoreKeys(requiredStorageKeys);
190
191
 
191
192
  // Merge storage keys from AsyncStorage, MMKV, and SecureStore
192
193
  const allStorageKeys = useMemo(() => {
@@ -752,7 +753,7 @@ export function GameUIStorageBrowser({
752
753
  onPress: () => setSelectedMMKVInstance(null),
753
754
  testID: "buoy-storage-instance-all",
754
755
  accessibilityRole: "button",
755
- accessibilityLabel: "All MMKV instances",
756
+ accessibilityLabel: `All ${storageLabels.mmkv} instances`,
756
757
  style: [styles.instanceNavbarButton, selectedMMKVInstance === null && styles.instanceNavbarButtonActive],
757
758
  children: [/*#__PURE__*/_jsx(HardDrive, {
758
759
  size: 12,
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { storageLabels } from "../utils/storageLabels";
3
4
  /**
4
5
  * MMKVInstanceSelector Component
5
6
  *
@@ -14,7 +15,7 @@ import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal } from 'rea
14
15
  import { macOSColors, night, monoFont } from '@buoy-gg/shared-ui';
15
16
  import { useMMKVInstances } from '../hooks/useMMKVInstances';
16
17
  import { isMMKVAvailable, getMMKVUnavailableMessage } from '../utils/mmkvAvailability';
17
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
18
19
  /**
19
20
  * MMKV Instance Selector Component
20
21
  *
@@ -61,9 +62,9 @@ export function MMKVInstanceSelector({
61
62
  style: styles.container,
62
63
  children: /*#__PURE__*/_jsxs(View, {
63
64
  style: styles.unavailableState,
64
- children: [/*#__PURE__*/_jsx(Text, {
65
+ children: [/*#__PURE__*/_jsxs(Text, {
65
66
  style: styles.unavailableTitle,
66
- children: "MMKV Not Available"
67
+ children: [storageLabels.mmkv, " Not Available"]
67
68
  }), /*#__PURE__*/_jsx(Text, {
68
69
  style: styles.unavailableDescription,
69
70
  children: getMMKVUnavailableMessage()
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { storageLabels } from "../utils/storageLabels";
3
4
  import { useState, useEffect } from "react";
4
5
  import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native";
5
6
  import { Trash2, CopyButton, SquareDashed, X, ProUpgradeModal, monoFont } from "@buoy-gg/shared-ui";
@@ -31,7 +32,7 @@ export function StorageActionButtons({
31
32
  setShowUpgradeModal(true);
32
33
  return;
33
34
  }
34
- Alert.alert("Clear AsyncStorage", "Delete all AsyncStorage keys? This cannot be undone.", [{
35
+ Alert.alert(`Clear ${storageLabels.asyncFull}`, `Delete all ${storageLabels.asyncFull} keys? This cannot be undone.`, [{
35
36
  text: "Cancel",
36
37
  style: "cancel"
37
38
  }, {
@@ -78,7 +79,7 @@ export function StorageActionButtons({
78
79
  setShowUpgradeModal(true);
79
80
  return;
80
81
  }
81
- Alert.alert("Clear All MMKV", `Delete all keys from ${mmkvInstances.length} instances? This cannot be undone.`, [{
82
+ Alert.alert(`Clear All ${storageLabels.mmkv}`, `Delete all keys from ${mmkvInstances.length} instances? This cannot be undone.`, [{
82
83
  text: "Cancel",
83
84
  style: "cancel"
84
85
  }, {
@@ -169,7 +170,7 @@ export function StorageActionButtons({
169
170
  }), !isSelectMode && showClearAsync && /*#__PURE__*/_jsx(TouchableOpacity, {
170
171
  onPress: handleClearAsyncStorage,
171
172
  testID: "buoy-storage-clear-async",
172
- accessibilityLabel: "Clear AsyncStorage",
173
+ accessibilityLabel: `Clear ${storageLabels.asyncFull}`,
173
174
  accessibilityRole: "button",
174
175
  style: styles.actionButton,
175
176
  children: /*#__PURE__*/_jsx(Trash2, {
@@ -179,7 +180,7 @@ export function StorageActionButtons({
179
180
  }), !isSelectMode && showClearAllMMKV && /*#__PURE__*/_jsx(TouchableOpacity, {
180
181
  onPress: handleClearAllMMKV,
181
182
  testID: "buoy-storage-clear-mmkv-all",
182
- accessibilityLabel: "Clear all MMKV instances",
183
+ accessibilityLabel: `Clear all ${storageLabels.mmkv} instances`,
183
184
  accessibilityRole: "button",
184
185
  style: styles.actionButton,
185
186
  children: /*#__PURE__*/_jsx(Trash2, {
@@ -189,7 +190,7 @@ export function StorageActionButtons({
189
190
  }), !isSelectMode && showClearMMKVIndividual && mmkvInstances.length === 1 && /*#__PURE__*/_jsx(TouchableOpacity, {
190
191
  onPress: () => handleClearMMKV(mmkvInstances[0].id, mmkvInstances[0].id),
191
192
  testID: "buoy-storage-clear-mmkv",
192
- accessibilityLabel: "Clear MMKV instance",
193
+ accessibilityLabel: `Clear ${storageLabels.mmkv} instance`,
193
194
  accessibilityRole: "button",
194
195
  style: styles.actionButton,
195
196
  children: /*#__PURE__*/_jsx(Trash2, {
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { storageLabels } from '../utils/storageLabels';
3
4
  /**
4
5
  * StorageEventCard
5
6
  *
@@ -70,7 +71,7 @@ function getActionColor(action) {
70
71
  * Get storage type label for sublabel
71
72
  */
72
73
  function getStorageTypeSublabel(storageTypes, totalOps) {
73
- const types = Array.from(storageTypes).map(t => t === "async" ? "Async" : "MMKV").join("/");
74
+ const types = Array.from(storageTypes).map(t => t === "async" ? storageLabels.async : storageLabels.mmkv).join("/");
74
75
  return `${types} · ${totalOps} op${totalOps !== 1 ? "s" : ""}`;
75
76
  }
76
77
  export const StorageEventCard = /*#__PURE__*/memo(function StorageEventCard({
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { storageLabels } from '../utils/storageLabels';
3
4
  import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
4
5
  import { Filter, DynamicFilterView, Database, HardDrive, Shield, macOSColors, night } from "@buoy-gg/shared-ui";
5
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -28,7 +29,7 @@ export function StorageEventFilterView({
28
29
  enabled: true,
29
30
  title: "HOW EVENT FILTERS WORK",
30
31
  description: "Patterns hide matching storage keys from the event list. Storage type toggles control which storage backends are monitored.",
31
- examples: ["• react_buoy → hides keys containing react_buoy", "• @temp → hides @temp_user, @temp_data", "• redux → hides redux-persist:root", "• AsyncStorage → show/hide AsyncStorage events", "• MMKV → show/hide MMKV events"],
32
+ examples: ["• react_buoy → hides keys containing react_buoy", "• @temp → hides @temp_user, @temp_data", "• redux → hides redux-persist:root", `• ${storageLabels.asyncFull}: show or hide captured events`, `• ${storageLabels.mmkv}: show or hide captured events`],
32
33
  icon: Filter
33
34
  },
34
35
  onPatternToggle: onTogglePattern,
@@ -51,7 +52,7 @@ export function StorageEventFilterView({
51
52
  onPress: () => onToggleStorageType('async'),
52
53
  testID: "buoy-storage-backend-async",
53
54
  accessibilityRole: "button",
54
- accessibilityLabel: "Toggle AsyncStorage capture",
55
+ accessibilityLabel: `Toggle ${storageLabels.asyncFull} capture`,
55
56
  accessibilityState: {
56
57
  selected: enabledStorageTypes.has('async')
57
58
  },
@@ -62,7 +63,7 @@ export function StorageEventFilterView({
62
63
  style: [styles.storageTypeButtonText, enabledStorageTypes.has('async') && {
63
64
  color: macOSColors.semantic.warning
64
65
  }],
65
- children: "AsyncStorage"
66
+ children: storageLabels.asyncFull
66
67
  })]
67
68
  }), /*#__PURE__*/_jsxs(TouchableOpacity, {
68
69
  style: [styles.storageTypeButton, enabledStorageTypes.has('mmkv') && styles.storageTypeButtonActive, {
@@ -71,7 +72,7 @@ export function StorageEventFilterView({
71
72
  onPress: () => onToggleStorageType('mmkv'),
72
73
  testID: "buoy-storage-backend-mmkv",
73
74
  accessibilityRole: "button",
74
- accessibilityLabel: "Toggle MMKV capture",
75
+ accessibilityLabel: `Toggle ${storageLabels.mmkv} capture`,
75
76
  accessibilityState: {
76
77
  selected: enabledStorageTypes.has('mmkv')
77
78
  },
@@ -82,9 +83,9 @@ export function StorageEventFilterView({
82
83
  style: [styles.storageTypeButtonText, enabledStorageTypes.has('mmkv') && {
83
84
  color: macOSColors.semantic.info
84
85
  }],
85
- children: "MMKV"
86
+ children: storageLabels.mmkv
86
87
  })]
87
- }), /*#__PURE__*/_jsxs(TouchableOpacity, {
88
+ }), storageLabels.hasSecure && /*#__PURE__*/_jsxs(TouchableOpacity, {
88
89
  style: [styles.storageTypeButton, enabledStorageTypes.has('secure') && styles.storageTypeButtonActive, {
89
90
  borderColor: macOSColors.semantic.success + '40'
90
91
  }],
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { storageLabels } from "../utils/storageLabels";
3
4
  import { View, Text, StyleSheet } from "react-native";
4
5
  import { AlertCircle, CheckCircle2, XCircle, Eye, Database, Shield } from "@buoy-gg/shared-ui";
5
6
  import { night } from "@buoy-gg/shared-ui";
@@ -51,13 +52,13 @@ const variableTypeData = [{
51
52
  // Storage type breakdown data
52
53
  const storageTypeData = [{
53
54
  key: "mmkv",
54
- label: "MMKV",
55
- description: "High-performance key-value storage",
55
+ label: storageLabels.mmkv,
56
+ description: storageLabels.hasSecure ? "Synchronous key-value storage" : "Storage for this browser tab",
56
57
  icon: Database
57
58
  }, {
58
59
  key: "async",
59
- label: "AsyncStorage",
60
- description: "React Native async storage",
60
+ label: storageLabels.asyncFull,
61
+ description: storageLabels.hasSecure ? "React Native async storage" : "Persistent storage for this origin",
61
62
  icon: Database
62
63
  }, {
63
64
  key: "secure",
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { storageLabels } from '../utils/storageLabels';
3
4
  /**
4
5
  * "Which backend?" — the pill row shared by the Keys and Events tabs.
5
6
  *
@@ -30,11 +31,11 @@ const SEGMENTS = [{
30
31
  color: null
31
32
  }, {
32
33
  id: "async",
33
- label: "Async",
34
+ label: storageLabels.async,
34
35
  color: macOSColors.semantic.warning
35
36
  }, {
36
37
  id: "mmkv",
37
- label: "MMKV",
38
+ label: storageLabels.mmkv,
38
39
  color: macOSColors.semantic.info
39
40
  }, {
40
41
  id: "secure",
@@ -48,7 +49,7 @@ export function StorageTypeFilterBar({
48
49
  }) {
49
50
  return /*#__PURE__*/_jsx(View, {
50
51
  style: styles.row,
51
- children: SEGMENTS.map(segment => {
52
+ children: SEGMENTS.filter(segment => storageLabels.hasSecure || segment.id !== 'secure').map(segment => {
52
53
  const isActive = active === segment.id;
53
54
  const accent = segment.color ?? night.color.accent;
54
55
  return /*#__PURE__*/_jsxs(TouchableOpacity, {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import { useState, useEffect, useCallback, useRef } from "react";
4
+ import { requiredKeyAppliesTo } from "../utils/requiredStorageKeyScope";
4
5
  import { secureStoreRegistry } from "../utils/SecureStoreRegistry";
5
6
 
6
7
  /**
@@ -37,10 +38,14 @@ function signature(keys) {
37
38
  * Values are re-read on a poll and whenever the registry changes, so the
38
39
  * browser tracks writes the app makes while the tool is open.
39
40
  */
40
- export function useSecureStoreKeys(options = {}) {
41
+ export function useSecureStoreKeys(requiredStorageKeys = [], options = {}) {
41
42
  const {
42
43
  pollMs = DEFAULT_POLL_MS
43
44
  } = options;
45
+ // Read through a ref so a fresh array literal each render doesn't restart the poll.
46
+ const requiredRef = useRef(requiredStorageKeys);
47
+ requiredRef.current = requiredStorageKeys;
48
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
44
49
  const [storageKeys, setStorageKeys] = useState([]);
45
50
  const [isLoading, setIsLoading] = useState(true);
46
51
  const [error, setError] = useState(null);
@@ -63,7 +68,12 @@ export function useSecureStoreKeys(options = {}) {
63
68
  try {
64
69
  const module = secureStoreRegistry.getModule();
65
70
  const entries = secureStoreRegistry.getAll();
66
- if (!module || entries.length === 0) {
71
+ // Required keys scoped to SecureStore. Registration is what lets the tool
72
+ // read a key; `requiredStorageKeys` is what says the app needs it. A
73
+ // registered key with no value that nobody requires is simply absent.
74
+ const required = requiredRef.current.filter(req => requiredKeyAppliesTo(req, "secure"));
75
+ const requiredFor = key => required.find(req => (typeof req === "string" ? req : req.key) === key);
76
+ if (!module || entries.length === 0 && required.length === 0) {
67
77
  commit([]);
68
78
  hasLoadedRef.current = true;
69
79
  setIsLoading(false);
@@ -75,16 +85,36 @@ export function useSecureStoreKeys(options = {}) {
75
85
  if (!hasLoadedRef.current) setIsLoading(true);
76
86
  setError(null);
77
87
  const keys = [];
78
- for (const entry of entries) {
88
+
89
+ // Required keys that were never registered are still readable by name,
90
+ // with the default keychain options.
91
+ const toRead = [...entries, ...required.map(req => typeof req === "string" ? req : req.key).filter(key => !entries.some(e => e.key === key)).map(key => ({
92
+ key,
93
+ options: {},
94
+ description: undefined
95
+ }))];
96
+ for (const entry of toRead) {
97
+ const req = requiredFor(entry.key);
98
+ const reqObj = typeof req === "object" ? req : undefined;
99
+ const expectations = {
100
+ ...(reqObj?.expectedValue !== undefined && {
101
+ expectedValue: reqObj.expectedValue
102
+ }),
103
+ ...(reqObj?.expectedType !== undefined && {
104
+ expectedType: reqObj.expectedType
105
+ })
106
+ };
107
+ const description = reqObj?.description ?? entry.description;
79
108
  if (entry.options.requireAuthentication) {
80
109
  // Never auto-read: reading would fire a biometric prompt.
81
110
  keys.push({
82
111
  key: entry.key,
83
112
  value: BIOMETRIC_PLACEHOLDER,
84
113
  storageType: "secure",
85
- status: "optional_present",
86
- category: "optional",
87
- description: entry.description ?? "Protected by biometric authentication — value not read by DevTools",
114
+ status: req ? "required_present" : "optional_present",
115
+ category: req ? "required" : "optional",
116
+ description: description ?? "Protected by biometric authentication — value not read by DevTools",
117
+ ...expectations,
88
118
  ...(entry.options.keychainService && {
89
119
  instanceId: entry.options.keychainService
90
120
  })
@@ -95,8 +125,9 @@ export function useSecureStoreKeys(options = {}) {
95
125
  try {
96
126
  rawValue = await module.getItemAsync(entry.key, entry.options);
97
127
  } catch {
98
- // Key unreadable (e.g. keychain state changed) — treat as missing.
128
+ // Key unreadable (e.g. keychain state changed) — treat as absent.
99
129
  }
130
+ if (rawValue === null && !req) continue;
100
131
 
101
132
  // Parse JSON values for the DataViewer, same as the AsyncStorage hook.
102
133
  let parsedValue = rawValue;
@@ -107,15 +138,26 @@ export function useSecureStoreKeys(options = {}) {
107
138
  parsedValue = rawValue;
108
139
  }
109
140
  }
141
+ let status = "optional_present";
142
+ if (req) {
143
+ if (rawValue === null) status = "required_missing";else if (reqObj?.expectedValue !== undefined) status = String(parsedValue) === String(reqObj.expectedValue) ? "required_present" : "required_wrong_value";else if (reqObj?.expectedType !== undefined) {
144
+ const actualType = parsedValue === null ? "null" : Array.isArray(parsedValue) ? "array" : typeof parsedValue;
145
+ // A keychain value is always stored as a string, so a string is
146
+ // acceptable wherever the parsed form would be.
147
+ const want = reqObj.expectedType.toLowerCase();
148
+ status = actualType === want || want === "string" && typeof rawValue === "string" ? "required_present" : "required_wrong_type";
149
+ } else status = "required_present";
150
+ }
110
151
  keys.push({
111
152
  key: entry.key,
112
153
  value: rawValue === null ? undefined : parsedValue,
113
154
  storageType: "secure",
114
- status: rawValue === null ? "required_missing" : "optional_present",
115
- category: rawValue === null ? "required" : "optional",
116
- ...(entry.description && {
117
- description: entry.description
155
+ status,
156
+ category: req ? "required" : "optional",
157
+ ...(description && {
158
+ description
118
159
  }),
160
+ ...expectations,
119
161
  ...(entry.options.keychainService && {
120
162
  instanceId: entry.options.keychainService
121
163
  })
@@ -130,7 +172,8 @@ export function useSecureStoreKeys(options = {}) {
130
172
  inFlightRef.current = false;
131
173
  setIsLoading(false);
132
174
  }
133
- }, [commit]);
175
+ // eslint-disable-next-line react-hooks/exhaustive-deps
176
+ }, [commit, requiredSignature]);
134
177
 
135
178
  // Initial fetch + poll. Registration usually happens at app startup, but the
136
179
  // registry is also refilled on the dashboard every time a device syncs, so
@@ -193,6 +193,16 @@ function assertMMKVWritable(instanceId) {
193
193
  * v2: oversized / unserializable event values are replaced with
194
194
  * VALUE_ON_DEVICE. Dashboards fetch them via `getEventDetail`.
195
195
  */
196
+ let toolRequiredStorageKeys = [];
197
+
198
+ /**
199
+ * Called by `createStorageTool` so remote dashboards validate against the
200
+ * same required keys as the on-device tool. The desktop panel used to have no
201
+ * way to learn them and showed every key as unchecked.
202
+ */
203
+ export function setToolRequiredStorageKeys(keys) {
204
+ toolRequiredStorageKeys = keys;
205
+ }
196
206
  export const storageSyncAdapter = {
197
207
  // v3: a total snapshot budget, so a burst of individually-legal values
198
208
  // degrades the oldest events instead of the emit layer dropping the panel.
@@ -210,6 +220,8 @@ export const storageSyncAdapter = {
210
220
  clearEvents: () => {
211
221
  storageEventStore.clearEvents();
212
222
  },
223
+ /** The app's required storage keys, as given to `createStorageTool`. */
224
+ getRequiredKeys: () => toolRequiredStorageKeys,
213
225
  /**
214
226
  * On-demand detail: one event's real value/prevValue/pairs.
215
227
  * The per-snapshot stream stays payload-light; this is the explicit,
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ import { subscribeToBrowserStorage } from '@buoy-gg/shared-ui/utils';
4
+ const listeners = new Set();
5
+ let stop;
6
+ let paused = false;
7
+ const ignoredKeys = new Set(['@react_buoy_storage_event_filters', '@react_buoy_storage_key_filters', '@react_buoy_storage_is_monitoring', 'REACT_QUERY_OFFLINE_CACHE']);
8
+ const ignored = key => [...ignoredKeys].some(prefix => key.startsWith(prefix));
9
+ export async function startListening() {
10
+ if (stop || typeof window === 'undefined') return;
11
+ stop = subscribeToBrowserStorage(change => {
12
+ if (paused || change.area !== 'localStorage' || change.key !== undefined && ignored(change.key)) return;
13
+ const {
14
+ area: _,
15
+ action,
16
+ ...data
17
+ } = change;
18
+ if (data.prevPairs) data.prevPairs = data.prevPairs.filter(([key]) => !ignored(key));
19
+ const event = {
20
+ action,
21
+ data,
22
+ timestamp: new Date()
23
+ };
24
+ for (const listener of [...listeners]) {
25
+ try {
26
+ listener(event);
27
+ } catch {/* Keep observing other consumers. */}
28
+ }
29
+ });
30
+ }
31
+ export function stopListening() {
32
+ stop?.();
33
+ stop = undefined;
34
+ }
35
+ export function addListener(listener) {
36
+ listeners.add(listener);
37
+ return () => {
38
+ listeners.delete(listener);
39
+ };
40
+ }
41
+ export function removeAllListeners() {
42
+ listeners.clear();
43
+ }
44
+ export function isListening() {
45
+ return !!stop;
46
+ }
47
+ export function getListenerCount() {
48
+ return listeners.size;
49
+ }
50
+ export function pauseCapture() {
51
+ paused = true;
52
+ }
53
+ export function resumeCapture() {
54
+ paused = false;
55
+ }
56
+ export function isPaused() {
57
+ return paused;
58
+ }
59
+ const asyncStorageListener = {
60
+ startListening,
61
+ stopListening,
62
+ addListener,
63
+ removeAllListeners,
64
+ pause: pauseCapture,
65
+ resume: resumeCapture,
66
+ get isActive() {
67
+ return isListening();
68
+ },
69
+ get listenerCount() {
70
+ return listeners.size;
71
+ },
72
+ get paused() {
73
+ return paused;
74
+ }
75
+ };
76
+ export default asyncStorageListener;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+
3
+ /** Native backends are registered by the host application. */
4
+ export function startBrowserStorageCapture() {
5
+ return () => {};
6
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ import { getBrowserStorage, subscribeToBrowserStorage } from '@buoy-gg/shared-ui/utils';
4
+ import { registerMMKVInstance, unregisterMMKVInstance } from './MMKVInstanceRegistry';
5
+ let owners = 0;
6
+ const storage = () => getBrowserStorage('sessionStorage');
7
+ const keys = () => Array.from({
8
+ length: storage().length
9
+ }, (_, index) => storage().key(index)).filter(key => key !== null);
10
+ // Reuse the synchronous storage editor and snapshot provider with Web Storage strings.
11
+ const sessionBackend = {
12
+ set(key, value) {
13
+ storage().setItem(key, String(value));
14
+ },
15
+ getString(key) {
16
+ return storage().getItem(key) ?? undefined;
17
+ },
18
+ getNumber() {
19
+ return undefined;
20
+ },
21
+ getBoolean() {
22
+ return undefined;
23
+ },
24
+ getBuffer() {
25
+ return undefined;
26
+ },
27
+ contains(key) {
28
+ return storage().getItem(key) !== null;
29
+ },
30
+ delete(key) {
31
+ const found = this.contains(key);
32
+ storage().removeItem(key);
33
+ return found;
34
+ },
35
+ getAllKeys: keys,
36
+ clearAll() {
37
+ storage().clear();
38
+ },
39
+ get size() {
40
+ return keys().reduce((bytes, key) => bytes + 2 * (key.length + (storage().getItem(key)?.length ?? 0)), 0);
41
+ },
42
+ addOnValueChangedListener(listener) {
43
+ return {
44
+ remove: subscribeToBrowserStorage(change => {
45
+ if (change.area !== 'sessionStorage') return;
46
+ if (change.key !== undefined) listener(change.key);else change.prevPairs?.forEach(([key]) => listener(key));
47
+ })
48
+ };
49
+ }
50
+ };
51
+ export function startBrowserStorageCapture() {
52
+ if (typeof window === 'undefined') return () => {};
53
+ try {
54
+ storage();
55
+ } catch {
56
+ return () => {};
57
+ }
58
+ if (++owners === 1) registerMMKVInstance('sessionStorage', sessionBackend);
59
+ let active = true;
60
+ return () => {
61
+ if (active) {
62
+ active = false;
63
+ if (--owners === 0) unregisterMMKVInstance('sessionStorage');
64
+ }
65
+ };
66
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ export function isMMKVAvailable() {
4
+ try {
5
+ return typeof window !== 'undefined' && !!window.sessionStorage;
6
+ } catch {
7
+ return false;
8
+ }
9
+ }
10
+ export function getMMKVClass() {
11
+ return null;
12
+ }
13
+ export function getMMKVUnavailableMessage() {
14
+ return 'Session storage is unavailable in this browser context.';
15
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ export const storageLabels = {
4
+ async: 'Async',
5
+ asyncFull: 'AsyncStorage',
6
+ mmkv: 'MMKV',
7
+ secure: 'Secure',
8
+ hasSecure: true
9
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ export const storageLabels = {
4
+ async: 'Local',
5
+ asyncFull: 'localStorage',
6
+ mmkv: 'Session',
7
+ secure: 'Secure',
8
+ hasSecure: false
9
+ };
@@ -51,4 +51,5 @@ export { storageEventStore, isValueOnDevice, batchPairsOf, } from "./storage/sto
51
51
  * @internal
52
52
  */
53
53
  export { requestStorageBrowserRefresh } from "./storage/utils/storageBrowserRefresh";
54
+ export { startBrowserStorageCapture } from './storage/utils/browserStorageLifecycle';
54
55
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}