Skip to content

Commit b047027

Browse files
committed
Enhance SecurityDemo with capability info and store tracking
Added display of device security capabilities (biometric, strongbox, recommended level) and an explanation of storage selection logic. The UI now also shows which store is expected to be used for each operation, improving transparency for users testing secure storage behavior.
1 parent fe0dc2f commit b047027

1 file changed

Lines changed: 96 additions & 3 deletions

File tree

example/src/components/SecurityDemo.tsx

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,53 @@
1-
import React, { useState } from 'react';
2-
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native';
3-
import { setItem, getItem, removeItem } from 'react-native-sensitive-info';
1+
import React, { useEffect, useMemo, useState } from 'react';
2+
import {
3+
View,
4+
Text,
5+
StyleSheet,
6+
TouchableOpacity,
7+
Alert,
8+
Platform,
9+
} from 'react-native';
10+
import {
11+
setItem,
12+
getItem,
13+
removeItem,
14+
getSecurityCapabilities,
15+
} from 'react-native-sensitive-info';
16+
17+
type StoreKind = 'standard' | 'biometric' | 'strongbox' | '—';
418

519
export const SecurityDemo: React.FC = () => {
620
const [status, setStatus] = useState<string>('Idle');
721
const [value, setValue] = useState<string | null>(null);
822
const [isLoading, setIsLoading] = useState<boolean>(false);
23+
const [usedStore, setUsedStore] = useState<StoreKind>('—');
24+
const [caps, setCaps] = useState({
25+
biometric: false,
26+
strongbox: false,
27+
recommendedLevel: 'standard' as 'standard' | 'biometric' | 'strongbox',
28+
});
29+
30+
useEffect(() => {
31+
(async () => {
32+
try {
33+
const c = await getSecurityCapabilities();
34+
setCaps(c);
35+
} catch {
36+
// keep defaults
37+
}
38+
})();
39+
}, []);
40+
41+
// Auto store uses standard by default
42+
const expectedAutoStore: StoreKind = 'standard';
43+
const expectedBiometricStore = useMemo<Exclude<StoreKind, '—'>>(() => {
44+
if (caps.biometric) return 'biometric';
45+
46+
// On Android, our implementation falls back to StrongBox if available
47+
if (Platform.OS === 'android' && caps.strongbox) return 'strongbox';
48+
// On iOS/macOS, biometric falls back to standard
49+
return 'standard';
50+
}, [caps.biometric, caps.strongbox]);
951

1052
const withUi = async (label: string, fn: () => Promise<void>) => {
1153
try {
@@ -34,6 +76,7 @@ export const SecurityDemo: React.FC = () => {
3476
disabled={isLoading}
3577
onPress={() =>
3678
withUi('Storing (auto)', async () => {
79+
setUsedStore(expectedAutoStore);
3780
await setItem('demo:key', 'hello-world');
3881
setStatus('Stored');
3982
})
@@ -47,6 +90,7 @@ export const SecurityDemo: React.FC = () => {
4790
disabled={isLoading}
4891
onPress={() =>
4992
withUi('Reading (auto)', async () => {
93+
setUsedStore(expectedAutoStore);
5094
const v = await getItem('demo:key');
5195
setValue(v);
5296
setStatus(`Read: ${v ?? 'null'}`);
@@ -63,6 +107,7 @@ export const SecurityDemo: React.FC = () => {
63107
disabled={isLoading}
64108
onPress={() =>
65109
withUi('Store (biometric)', async () => {
110+
setUsedStore(expectedBiometricStore);
66111
await setItem('demo:bio', 'biometric-secret', {
67112
securityLevel: 'biometric',
68113
biometricOptions: {
@@ -84,6 +129,7 @@ export const SecurityDemo: React.FC = () => {
84129
disabled={isLoading}
85130
onPress={() =>
86131
withUi('Read (biometric)', async () => {
132+
setUsedStore(expectedBiometricStore);
87133
const v = await getItem('demo:bio', {
88134
securityLevel: 'biometric',
89135
biometricOptions: {
@@ -123,6 +169,37 @@ export const SecurityDemo: React.FC = () => {
123169
<Text style={styles.statusLabel}>Status</Text>
124170
<Text style={styles.statusText}>{status}</Text>
125171
<Text style={styles.statusSub}>Value: {value ?? '—'}</Text>
172+
<Text style={styles.statusSub}>Expected store used: {usedStore}</Text>
173+
</View>
174+
175+
<View style={styles.infoBox}>
176+
<Text style={styles.infoTitle}>Device capabilities</Text>
177+
<Text style={styles.infoText}>
178+
• Biometric: {caps.biometric ? 'Yes' : 'No'} | StrongBox/Secure
179+
Enclave: {caps.strongbox ? 'Yes' : 'No'}
180+
</Text>
181+
<Text style={styles.infoText}>
182+
• Recommended level: {caps.recommendedLevel}
183+
</Text>
184+
185+
<View style={styles.spacer} />
186+
<Text style={styles.infoTitle}>How storage is chosen</Text>
187+
<Text style={styles.infoText}>
188+
• If you don't pass a securityLevel, data is stored with the standard
189+
keychain/keystore.
190+
</Text>
191+
<Text style={styles.infoText}>
192+
• When requesting biometric/strongbox, the library attempts that
193+
level.
194+
</Text>
195+
<Text style={styles.infoText}>
196+
• If unavailable, it falls back to what's available.
197+
</Text>
198+
<Text style={styles.infoText}>
199+
• Actual fallback behavior may differ between iOS and Android. The
200+
"Expected store used" value above is based on detected capabilities
201+
and may differ from the exact platform behavior.
202+
</Text>
126203
</View>
127204
</View>
128205
);
@@ -158,4 +235,20 @@ const styles = StyleSheet.create({
158235
statusLabel: { fontSize: 12, color: '#6B7280', marginBottom: 4 },
159236
statusText: { fontSize: 14, color: '#111' },
160237
statusSub: { fontSize: 12, color: '#6B7280', marginTop: 2 },
238+
infoBox: {
239+
marginTop: 16,
240+
padding: 12,
241+
borderRadius: 12,
242+
backgroundColor: '#F3F4F6',
243+
borderWidth: 1,
244+
borderColor: '#e5e7eb',
245+
},
246+
infoTitle: {
247+
fontSize: 14,
248+
fontWeight: '700',
249+
color: '#111',
250+
marginBottom: 6,
251+
},
252+
infoText: { fontSize: 12, color: '#111', lineHeight: 18 },
253+
spacer: { height: 8 },
161254
});

0 commit comments

Comments
 (0)