1import * as LocalAuthentication from 'expo-local-authentication';
2import React from 'react';
3import { Text, View } from 'react-native';
4
5import Button from '../components/Button';
6import MonoText from '../components/MonoText';
7
8interface State {
9  waiting: boolean;
10  supportedAuthenticationTypes?: string[];
11  hasHardware?: boolean;
12  isEnrolled?: boolean;
13}
14
15// See: https://github.com/expo/expo/pull/10229#discussion_r490961694
16// eslint-disable-next-line @typescript-eslint/ban-types
17export default class LocalAuthenticationScreen extends React.Component<{}, State> {
18  static navigationOptions = {
19    title: 'LocalAuthentication',
20  };
21
22  readonly state: State = {
23    waiting: false,
24  };
25
26  componentDidMount() {
27    this.checkDevicePossibilities();
28  }
29
30  async checkDevicePossibilities() {
31    const [hasHardware, isEnrolled, supportedAuthenticationTypes] = await Promise.all([
32      LocalAuthentication.hasHardwareAsync(),
33      LocalAuthentication.isEnrolledAsync(),
34      this.getAuthenticationTypes(),
35    ]);
36    this.setState({ hasHardware, isEnrolled, supportedAuthenticationTypes });
37  }
38
39  async getAuthenticationTypes() {
40    return (await LocalAuthentication.supportedAuthenticationTypesAsync()).map(
41      (type) => LocalAuthentication.AuthenticationType[type]
42    );
43  }
44
45  authenticateWithFallback = () => {
46    this.authenticate(true);
47  };
48
49  authenticateWithoutFallback = () => {
50    this.authenticate(false);
51  };
52
53  async authenticate(withFallback: boolean = true) {
54    this.setState({ waiting: true });
55    try {
56      const result = await LocalAuthentication.authenticateAsync({
57        promptMessage: 'Authenticate',
58        cancelLabel: 'Cancel label',
59        disableDeviceFallback: !withFallback,
60      });
61      if (result.success) {
62        alert('Authenticated!');
63      } else {
64        alert('Failed to authenticate, reason: ' + result.error);
65      }
66    } finally {
67      this.setState({ waiting: false });
68    }
69  }
70
71  render() {
72    const { waiting, ...capabilities } = this.state;
73
74    return (
75      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
76        <View style={{ paddingBottom: 30 }}>
77          <Text>Device capabilities:</Text>
78          <MonoText textStyle={{ fontSize: 14 }}>{JSON.stringify(capabilities, null, 2)}</MonoText>
79        </View>
80        <View style={{ height: 200 }}>
81          {waiting ? (
82            <Text>Waiting for authentication...</Text>
83          ) : (
84            <View>
85              <Button
86                style={{ margin: 5 }}
87                onPress={this.authenticateWithFallback}
88                title="Authenticate with device fallback"
89              />
90              <Button
91                style={{ margin: 5 }}
92                onPress={this.authenticateWithoutFallback}
93                title="Authenticate without device fallback"
94              />
95            </View>
96          )}
97        </View>
98      </View>
99    );
100  }
101}
102