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
519export 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