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 { useResolvedValue } from '../utilities/useResolvedValue';
16
17export default function SecureStoreScreen() {
18  const [isAvailable, error] = useResolvedValue(SecureStore.isAvailableAsync);
19
20  const warning = React.useMemo(() => {
21    if (error) {
22      return `An unknown error occurred while checking the API availability: ${error.message}`;
23    } else if (isAvailable === null) {
24      return 'Checking availability...';
25    } else if (isAvailable === false) {
26      return 'SecureStore API is not available on this platform.';
27    }
28    return null;
29  }, [error, isAvailable]);
30
31  if (warning) {
32    return (
33      <View style={{ justifyContent: 'center', alignItems: 'center', flex: 1 }}>
34        <Text>{warning}</Text>
35      </View>
36    );
37  }
38
39  return <SecureStoreView />;
40}
41
42function SecureStoreView() {
43  const [key, setKey] = React.useState<string | undefined>();
44  const [value, setValue] = React.useState<string | undefined>();
45  const [service, setService] = React.useState<string | undefined>();
46  const [requireAuth, setRequireAuth] = React.useState<boolean | undefined>();
47
48  const _toggleAuth = async () => {
49    setRequireAuth(!requireAuth);
50  };
51
52  const _setValue = async (value: string, key: string) => {
53    try {
54      console.log('SecureStore: ' + SecureStore);
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  const _getValue = async (key: string) => {
69    try {
70      const fetchedValue = await SecureStore.getItemAsync(key, {
71        keychainService: service,
72        requireAuthentication: requireAuth,
73        authenticationPrompt: 'Authenticate',
74      });
75      Alert.alert('Success!', 'Fetched value: ' + fetchedValue, [
76        { text: 'OK', onPress: () => {} },
77      ]);
78    } catch (e) {
79      Alert.alert('Error!', e.message, [{ text: 'OK', onPress: () => {} }]);
80    }
81  };
82
83  const _deleteValue = async (key: string) => {
84    try {
85      await SecureStore.deleteItemAsync(key, { keychainService: service });
86      Alert.alert('Success!', 'Value deleted', [{ text: 'OK', onPress: () => {} }]);
87    } catch (e) {
88      Alert.alert('Error!', e.message, [{ text: 'OK', onPress: () => {} }]);
89    }
90  };
91
92  return (
93    <ScrollView style={styles.container}>
94      <TextInput
95        style={styles.textInput}
96        placeholder="Enter a value to store (ex. pw123!)"
97        value={value}
98        onChangeText={setValue}
99      />
100      <TextInput
101        style={styles.textInput}
102        placeholder="Enter a key for the value (ex. password)"
103        value={key}
104        onChangeText={setKey}
105      />
106      <TextInput
107        style={styles.textInput}
108        placeholder="Enter a service name (may be blank)"
109        value={service}
110        onChangeText={setService}
111      />
112      <View style={styles.authToggleContainer}>
113        <Text>Requires authentication:</Text>
114        <Switch value={requireAuth} onValueChange={_toggleAuth} />
115      </View>
116      {value && key && (
117        <ListButton onPress={() => _setValue(value, key)} title="Store value with key" />
118      )}
119      {key && <ListButton onPress={() => _getValue(key)} title="Get value with key" />}
120      {key && <ListButton onPress={() => _deleteValue(key)} title="Delete value with key" />}
121    </ScrollView>
122  );
123}
124
125SecureStoreScreen.navigationOptions = {
126  title: 'SecureStore',
127};
128
129const styles = StyleSheet.create({
130  container: {
131    flex: 1,
132    padding: 10,
133  },
134  textInput: {
135    marginBottom: 10,
136    padding: 10,
137    height: 40,
138    ...Platform.select({
139      ios: {
140        borderColor: '#ccc',
141        borderWidth: 1,
142        borderRadius: 3,
143      },
144    }),
145  },
146  authToggleContainer: {
147    flexDirection: 'row',
148    justifyContent: 'space-between',
149    alignItems: 'center',
150  },
151});
152