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