Skip to content

Commit 6701d84

Browse files
mCodexCopilot
andcommitted
feat: add AccessControlCard and DiagnosticsCard components; remove unused components
- Introduced AccessControlCard for managing access modes with visual feedback. - Added DiagnosticsCard to display security availability and key version information. - Removed ActionButton, ActionsPanel, Card, Header, KeyRotationPanel, ModeSelector, SecretForm, SecretsList, and formatError utility as they were no longer needed. - Updated constants to streamline access modes and added biometric prompt configuration. - Enhanced StorageCard to manage secure storage with improved UI and functionality. - Introduced Section and StatusLine components for better layout and error handling. - Updated TypeScript configuration to include hooks and errors paths for improved module resolution. Co-authored-by: Copilot <copilot@github.com>
1 parent 4f353ce commit 6701d84

22 files changed

Lines changed: 1290 additions & 2342 deletions

example/ios/Podfile.lock

Lines changed: 649 additions & 1404 deletions
Large diffs are not rendered by default.

example/ios/SensitiveInfoExample.xcodeproj/project.pbxproj

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,18 +368,23 @@
368368
);
369369
MTL_ENABLE_DEBUG_INFO = YES;
370370
ONLY_ACTIVE_ARCH = YES;
371-
OTHER_CFLAGS = "$(inherited)";
371+
OTHER_CFLAGS = (
372+
"$(inherited)",
373+
"-DRCT_REMOVE_LEGACY_ARCH=1",
374+
);
372375
OTHER_CPLUSPLUSFLAGS = (
373376
"$(OTHER_CFLAGS)",
374377
"-DFOLLY_NO_CONFIG",
375378
"-DFOLLY_MOBILE=1",
376379
"-DFOLLY_USE_LIBCPP=1",
377380
"-DFOLLY_CFG_NO_COROUTINES=1",
378381
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
382+
"-DRCT_REMOVE_LEGACY_ARCH=1",
379383
);
380384
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
381385
SDKROOT = iphoneos;
382386
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
387+
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
383388
USE_HERMES = true;
384389
};
385390
name = Debug;
@@ -437,17 +442,22 @@
437442
"\"$(inherited)\"",
438443
);
439444
MTL_ENABLE_DEBUG_INFO = NO;
440-
OTHER_CFLAGS = "$(inherited)";
445+
OTHER_CFLAGS = (
446+
"$(inherited)",
447+
"-DRCT_REMOVE_LEGACY_ARCH=1",
448+
);
441449
OTHER_CPLUSPLUSFLAGS = (
442450
"$(OTHER_CFLAGS)",
443451
"-DFOLLY_NO_CONFIG",
444452
"-DFOLLY_MOBILE=1",
445453
"-DFOLLY_USE_LIBCPP=1",
446454
"-DFOLLY_CFG_NO_COROUTINES=1",
447455
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
456+
"-DRCT_REMOVE_LEGACY_ARCH=1",
448457
);
449458
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
450459
SDKROOT = iphoneos;
460+
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
451461
USE_HERMES = true;
452462
VALIDATE_PRODUCT = YES;
453463
};

example/metro.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ const root = path.resolve(__dirname, '..')
1111
*/
1212
const config = {
1313
watchFolders: [root],
14+
resolver: {
15+
// Honor the `exports` map in package.json so subpath imports like
16+
// `react-native-sensitive-info/hooks` and `/errors` resolve correctly.
17+
unstable_enablePackageExports: true,
18+
},
1419
}
1520

1621
module.exports = mergeConfig(getDefaultConfig(__dirname), config)

example/src/App.tsx

Lines changed: 33 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -1,245 +1,59 @@
1-
import type React from 'react'
2-
import { useCallback, useMemo, useState } from 'react'
3-
import { ScrollView, StyleSheet } from 'react-native'
1+
import { useMemo, useState } from 'react'
2+
import { ScrollView, StyleSheet, Text } from 'react-native'
43
import { SafeAreaView } from 'react-native-safe-area-context'
5-
import { getItem } from 'react-native-sensitive-info'
6-
import {
7-
useSecureStorage,
8-
useSecurityAvailability,
9-
} from 'react-native-sensitive-info/hooks'
10-
import ActionsPanel from './components/ActionsPanel'
11-
import Header from './components/Header'
12-
import KeyRotationPanel from './components/KeyRotationPanel'
13-
import ModeSelector from './components/ModeSelector'
14-
import SecretForm from './components/SecretForm'
15-
import SecretsList from './components/SecretsList'
4+
import AccessControlCard from './components/AccessControlCard'
5+
import DiagnosticsCard from './components/DiagnosticsCard'
6+
import KeyRotationCard from './components/KeyRotationCard'
7+
import StorageCard from './components/StorageCard'
168
import {
179
ACCESS_MODES,
18-
DEFAULT_KEY,
19-
DEFAULT_SECRET,
10+
BIOMETRIC_PROMPT,
2011
DEFAULT_SERVICE,
21-
INITIAL_STATUS,
2212
type ModeKey,
2313
} from './constants'
24-
import { formatError } from './utils/formatError'
2514

26-
const App: React.FC = () => {
27-
const [service, setService] = useState(DEFAULT_SERVICE)
28-
const [keyName, setKeyName] = useState(DEFAULT_KEY)
29-
const [secret, setSecret] = useState(DEFAULT_SECRET)
15+
const App = () => {
3016
const [mode, setMode] = useState<ModeKey>('open')
31-
const [status, setStatus] = useState(INITIAL_STATUS)
32-
const [pending, setPending] = useState(false)
33-
34-
const trimmedService = useMemo(() => {
35-
const next = service.trim()
36-
return next.length > 0 ? next : DEFAULT_SERVICE
37-
}, [service])
38-
39-
const selectedMode = useMemo(() => {
40-
const fallback = ACCESS_MODES[0]
41-
return ACCESS_MODES.find((candidate) => candidate.key === mode) ?? fallback
42-
}, [mode])
43-
44-
const authenticationPrompt = useMemo(() => {
45-
if (selectedMode.key !== 'biometric') {
46-
return undefined
47-
}
4817

18+
const { options, prompt } = useMemo(() => {
19+
const entry =
20+
ACCESS_MODES.find((candidate) => candidate.key === mode) ??
21+
ACCESS_MODES[0]
22+
const isBiometric = entry.key === 'biometric'
4923
return {
50-
title: 'Unlock your secret',
51-
subtitle: 'Biometric authentication is required to continue',
52-
description: 'This demo stores data behind your biometric enrollment.',
53-
cancel: 'Cancel',
54-
} as const
55-
}, [selectedMode.key])
56-
57-
const secureOptions = useMemo(
58-
() => ({
59-
service: trimmedService,
60-
accessControl: selectedMode.accessControl,
61-
authenticationPrompt,
62-
includeValues: true,
63-
}),
64-
[authenticationPrompt, selectedMode.accessControl, trimmedService]
65-
)
66-
67-
const {
68-
items,
69-
isLoading,
70-
error,
71-
saveSecret,
72-
removeSecret,
73-
clearAll,
74-
refreshItems,
75-
} = useSecureStorage(secureOptions)
76-
77-
const { data: availability } = useSecurityAvailability()
78-
79-
const handleSave = useCallback(async () => {
80-
const normalizedKey = keyName.trim()
81-
if (normalizedKey.length === 0) {
82-
setStatus('Please provide a key before saving.')
83-
return
84-
}
85-
86-
setPending(true)
87-
try {
88-
const result = await saveSecret(normalizedKey, secret)
89-
if (result.success) {
90-
setStatus('Secret saved securely.')
91-
} else {
92-
setStatus(result.error?.message ?? 'Unable to save the secret.')
93-
}
94-
} catch (err) {
95-
setStatus(formatError(err))
96-
} finally {
97-
setPending(false)
24+
options: {
25+
service: DEFAULT_SERVICE,
26+
accessControl: entry.accessControl,
27+
...(isBiometric ? { authenticationPrompt: BIOMETRIC_PROMPT } : {}),
28+
},
29+
prompt: isBiometric ? BIOMETRIC_PROMPT : undefined,
9830
}
99-
}, [keyName, saveSecret, secret])
100-
101-
const handleReveal = useCallback(async () => {
102-
const normalizedKey = keyName.trim()
103-
if (normalizedKey.length === 0) {
104-
setStatus('Provide the key you would like to reveal.')
105-
return
106-
}
107-
108-
setPending(true)
109-
try {
110-
const item = await getItem(normalizedKey, {
111-
service: trimmedService,
112-
accessControl: selectedMode.accessControl,
113-
authenticationPrompt,
114-
includeValue: true,
115-
})
116-
117-
if (item?.value) {
118-
setStatus(`Secret for "${normalizedKey}" → ${item.value}`)
119-
} else {
120-
setStatus('That key has no stored value yet.')
121-
}
122-
} catch (err) {
123-
setStatus(formatError(err))
124-
} finally {
125-
setPending(false)
126-
}
127-
}, [
128-
authenticationPrompt,
129-
keyName,
130-
selectedMode.accessControl,
131-
trimmedService,
132-
])
133-
134-
const handleRemove = useCallback(async () => {
135-
const normalizedKey = keyName.trim()
136-
if (normalizedKey.length === 0) {
137-
setStatus('Provide the key you would like to forget.')
138-
return
139-
}
140-
141-
setPending(true)
142-
try {
143-
const result = await removeSecret(normalizedKey)
144-
setStatus(
145-
result.success ? 'Secret deleted.' : 'Secret could not be deleted.'
146-
)
147-
} catch (err) {
148-
setStatus(formatError(err))
149-
} finally {
150-
setPending(false)
151-
}
152-
}, [keyName, removeSecret])
153-
154-
const handleClear = useCallback(async () => {
155-
setPending(true)
156-
try {
157-
const result = await clearAll()
158-
setStatus(
159-
result.success
160-
? 'All secrets cleared for this service.'
161-
: 'Nothing to clear.'
162-
)
163-
} catch (err) {
164-
setStatus(formatError(err))
165-
} finally {
166-
setPending(false)
167-
}
168-
}, [clearAll])
169-
170-
const handleRefresh = useCallback(async () => {
171-
setPending(true)
172-
try {
173-
await refreshItems()
174-
setStatus('Inventory refreshed.')
175-
} catch (err) {
176-
setStatus(formatError(err))
177-
} finally {
178-
setPending(false)
179-
}
180-
}, [refreshItems])
31+
}, [mode])
18132

18233
return (
18334
<SafeAreaView style={styles.safeArea}>
18435
<ScrollView
36+
contentContainerStyle={styles.scroll}
18537
keyboardShouldPersistTaps="handled"
186-
contentContainerStyle={styles.scrollContent}
18738
>
188-
<Header
189-
title="Sensitive Info Playground"
190-
subtitle="Store a small secret, lock it with biometrics if you like, and review the inventory below."
191-
/>
192-
193-
<SecretForm
194-
service={service}
195-
onServiceChange={setService}
196-
keyName={keyName}
197-
onKeyNameChange={setKeyName}
198-
secret={secret}
199-
onSecretChange={setSecret}
200-
servicePlaceholder={DEFAULT_SERVICE}
201-
keyPlaceholder={DEFAULT_KEY}
202-
secretPlaceholder={DEFAULT_SECRET}
203-
/>
204-
205-
<ModeSelector
206-
modes={ACCESS_MODES}
207-
selectedKey={selectedMode.key}
208-
onSelect={setMode}
209-
availability={availability ?? null}
210-
/>
211-
212-
<ActionsPanel
213-
onSave={handleSave}
214-
onReveal={handleReveal}
215-
onRemove={handleRemove}
216-
onClear={handleClear}
217-
onRefresh={handleRefresh}
218-
pending={pending}
219-
status={status}
220-
errorMessage={error?.message}
221-
/>
222-
223-
<SecretsList
224-
items={items}
225-
isLoading={isLoading}
226-
service={trimmedService}
227-
/>
228-
229-
<KeyRotationPanel service={trimmedService} />
39+
<Text style={styles.heading}>Sensitive Info Playground</Text>
40+
<AccessControlCard mode={mode} onChange={setMode} />
41+
<StorageCard options={options} authenticationPrompt={prompt} />
42+
<KeyRotationCard options={options} />
43+
<DiagnosticsCard options={options} />
23044
</ScrollView>
23145
</SafeAreaView>
23246
)
23347
}
23448

23549
const styles = StyleSheet.create({
236-
safeArea: {
237-
flex: 1,
238-
backgroundColor: '#f6f8fb',
239-
},
240-
scrollContent: {
241-
padding: 20,
242-
paddingBottom: 32,
50+
safeArea: { flex: 1, backgroundColor: '#f1f5f9' },
51+
scroll: { padding: 16, paddingBottom: 32 },
52+
heading: {
53+
fontSize: 22,
54+
fontWeight: '700',
55+
color: '#0f172a',
56+
marginBottom: 16,
24357
},
24458
})
24559

0 commit comments

Comments
 (0)