import { CodedError } from 'expo-modules-core'; import * as React from 'react'; import { StyleSheet, Button, View, SafeAreaView, Text, Modal, ActivityIndicator, } from 'react-native'; import FirebaseRecaptcha from './FirebaseRecaptcha'; import { FirebaseAuthApplicationVerifier } from './FirebaseRecaptcha.types'; interface Props extends Omit< React.ComponentProps, 'onVerify' | 'invisible' | 'verify' | 'onVerify' | 'onLoad' | 'onError' | 'onFullChallenge' > { title?: string; cancelLabel?: string; attemptInvisibleVerification?: boolean; } interface State { visible: boolean; visibleLoaded: boolean; invisibleLoaded: boolean; invisibleVerify: boolean; invisibleKey: number; resolve?: (token: string) => void; reject?: (error: Error) => void; } export default class FirebaseRecaptchaVerifierModal extends React.Component implements FirebaseAuthApplicationVerifier { static defaultProps = { title: 'reCAPTCHA', cancelLabel: 'Cancel', }; state: State = { visible: false, visibleLoaded: false, invisibleLoaded: false, invisibleVerify: false, invisibleKey: 1, resolve: undefined, reject: undefined, }; static getDerivedStateFromProps(props: Props, state: State) { if (!props.attemptInvisibleVerification && state.invisibleLoaded) { return { invisibleLoaded: false, invisibleVerify: false, }; } return null; } get type(): string { return 'recaptcha'; } async verify(): Promise { return new Promise((resolve, reject) => { if (this.props.attemptInvisibleVerification) { this.setState({ invisibleVerify: true, resolve, reject, }); } else { this.setState({ visible: true, visibleLoaded: false, resolve, reject, }); } }); } // see: https://github.com/expo/expo/issues/14780 _reset(...args: any): void {} private onVisibleLoad = () => { this.setState({ visibleLoaded: true, }); }; private onInvisibleLoad = () => { this.setState({ invisibleLoaded: true, }); }; private onFullChallenge = async () => { this.setState({ invisibleVerify: false, visible: true, }); }; private onError = () => { const { reject } = this.state; if (reject) { reject(new CodedError('ERR_FIREBASE_RECAPTCHA_ERROR', 'Failed to load reCAPTCHA')); } this.setState({ visible: false, invisibleVerify: false, }); }; private onVerify = (token: string) => { const { resolve } = this.state; if (resolve) { resolve(token); } this.setState((state) => ({ visible: false, invisibleVerify: false, invisibleLoaded: false, invisibleKey: state.invisibleKey + 1, })); }; cancel = () => { const { reject } = this.state; if (reject) { reject(new CodedError('ERR_FIREBASE_RECAPTCHA_CANCEL', 'Cancelled by user')); } this.setState({ visible: false, }); }; onDismiss = () => { // onDismiss should be called when the user dismisses the // modal using a swipe gesture. Due to a bug in RN this // unfortunately doesn't work :/ //https://github.com/facebook/react-native/issues/26892 if (this.state.visible) { this.cancel(); } }; render() { const { title, cancelLabel, attemptInvisibleVerification, ...otherProps } = this.props; const { visible, visibleLoaded, invisibleLoaded, invisibleVerify, invisibleKey } = this.state; return ( {attemptInvisibleVerification && ( )} {title}