1import * as SecureStore from 'expo-secure-store'; 2import * as React from 'react'; 3import { 4 Alert, 5 Platform, 6 ScrollView, 7 Text, 8 TextInput, 9 View, 10 Switch, 11 StyleSheet, 12} from 'react-native'; 13 14import ListButton from '../components/ListButton'; 15import Colors from '../constants/Colors'; 16import { useResolvedValue } from '../utilities/useResolvedValue'; 17 18export default function SecureStoreScreen() { 19 const [isAvailable, error] = useResolvedValue(SecureStore.isAvailableAsync); 20 21 const warning = React.useMemo(() => { 22 if (error) { 23 return `An unknown error occurred while checking the API availability: ${error.message}`; 24 } else if (isAvailable === null) { 25 return 'Checking availability...'; 26 } else if (isAvailable === false) { 27 return 'SecureStore API is not available on this platform.'; 28 } 29 return null; 30 }, [error, isAvailable]); 31 32 if (warning) { 33 return ( 34 <View style={{ justifyContent: 'center', alignItems: 'center', flex: 1 }}> 35 <Text>{warning}</Text> 36 </View> 37 ); 38 } 39 40 return <SecureStoreView />; 41} 42 43function SecureStoreView() { 44 const [key, setKey] = React.useState<string | undefined>(); 45 const [value, setValue] = React.useState<string | undefined>(); 46 const [service, setService] = React.useState<string | undefined>(); 47 const [requireAuth, setRequireAuth] = React.useState<boolean | undefined>(); 48 49 const toggleAuth = async () => { 50 setRequireAuth(!requireAuth); 51 }; 52 53 async function storeValueAsync(value: string, key: string) { 54 try { 55 await SecureStore.setItemAsync(key, value, { 56 keychainService: service, 57 requireAuthentication: requireAuth, 58 authenticationPrompt: 'Authenticate', 59 }); 60 Alert.alert('Success!', 'Value: ' + value + ', stored successfully for key: ' + key, [ 61 { text: 'OK', onPress: () => {} }, 62 ]); 63 } catch (e) { 64 Alert.alert('Error!', e.message, [{ text: 'OK', onPress: () => {} }]); 65 } 66 } 67 68 function storeValue(value: string, key: string) { 69 try { 70 SecureStore.setItem(key, value, { 71 keychainService: service, 72 requireAuthentication: requireAuth, 73 authenticationPrompt: 'Authenticate', 74 }); 75 Alert.alert('Success!', 'Value: ' + value + ', stored successfully for key: ' + key, [ 76 { text: 'OK', onPress: () => {} }, 77 ]); 78 } catch (e) { 79 Alert.alert('Error!', e.message, [{ text: 'OK', onPress: () => {} }]); 80 } 81 } 82 83 async function getValueAsync(key: string) { 84 try { 85 const fetchedValue = await SecureStore.getItemAsync(key, { 86 keychainService: service, 87 requireAuthentication: requireAuth, 88 authenticationPrompt: 'Authenticate', 89 }); 90 Alert.alert('Success!', 'Fetched value: ' + fetchedValue, [ 91 { text: 'OK', onPress: () => {} }, 92 ]); 93 } catch (e) { 94 Alert.alert('Error!', e.message, [{ text: 'OK', onPress: () => {} }]); 95 } 96 } 97 98 function getValue(key: string) { 99 try { 100 const fetchedValue = SecureStore.getItem(key, { 101 keychainService: service, 102 requireAuthentication: requireAuth, 103 authenticationPrompt: 'Authenticate', 104 }); 105 Alert.alert('Success!', 'Fetched value: ' + fetchedValue, [ 106 { text: 'OK', onPress: () => {} }, 107 ]); 108 } catch (e) { 109 Alert.alert('Error!', e.message, [{ text: 'OK', onPress: () => {} }]); 110 } 111 } 112 113 async function deleteValue(key: string) { 114 try { 115 await SecureStore.deleteItemAsync(key, { keychainService: service }); 116 Alert.alert('Success!', 'Value deleted', [{ text: 'OK', onPress: () => {} }]); 117 } catch (e) { 118 Alert.alert('Error!', e.message, [{ text: 'OK', onPress: () => {} }]); 119 } 120 } 121 122 return ( 123 <ScrollView style={styles.container}> 124 <TextInput 125 style={styles.textInput} 126 placeholder="Enter a value to store (ex. pw123!)" 127 placeholderTextColor={Colors.secondaryText} 128 value={value} 129 onChangeText={setValue} 130 /> 131 <TextInput 132 style={styles.textInput} 133 placeholder="Enter a key for the value (ex. password)" 134 placeholderTextColor={Colors.secondaryText} 135 value={key} 136 onChangeText={setKey} 137 /> 138 <TextInput 139 style={styles.textInput} 140 placeholder="Enter a service name (may be blank)" 141 placeholderTextColor={Colors.secondaryText} 142 value={service} 143 onChangeText={setService} 144 /> 145 <View style={styles.authToggleContainer}> 146 <Text>Requires authentication:</Text> 147 <Switch value={requireAuth} onValueChange={toggleAuth} /> 148 </View> 149 {value && key && ( 150 <ListButton onPress={() => storeValueAsync(value, key)} title="Store value with key" /> 151 )} 152 {key && <ListButton onPress={() => getValueAsync(key)} title="Get value with key" />} 153 {value && key && ( 154 <ListButton 155 onPress={() => storeValue(value, key)} 156 title="Store value with key synchronously" 157 /> 158 )} 159 {key && <ListButton onPress={() => getValue(key)} title="Get value with key synchronously" />} 160 {key && <ListButton onPress={() => deleteValue(key)} title="Delete value with key" />} 161 </ScrollView> 162 ); 163} 164 165SecureStoreScreen.navigationOptions = { 166 title: 'SecureStore', 167}; 168 169const styles = StyleSheet.create({ 170 container: { 171 flex: 1, 172 padding: 10, 173 }, 174 textInput: { 175 marginBottom: 10, 176 padding: 10, 177 height: 40, 178 ...Platform.select({ 179 ios: { 180 borderColor: '#ccc', 181 borderWidth: 1, 182 borderRadius: 3, 183 }, 184 }), 185 }, 186 authToggleContainer: { 187 flexDirection: 'row', 188 justifyContent: 'space-between', 189 alignItems: 'center', 190 }, 191}); 192