Recon
Recon

Reputation: 618

Firebase Phone Auth Recaptcha Verifier not being set to window/loading

I'm trying to load a RecaptchaVerifier into my React class, so I can enable Phone Auth for my web app. However, when I submit my phone number, I get the following error:

"signInWithPhoneNumber failed: Second argument "applicationVerifier" must be an implementation of firebase.auth.ApplicationVerifier." The entire console log for this error

I logged the window.recaptchaVerifier, to find it is silently erroring with the error code:

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter (<anonymous>:2:14) Error in Console

The Recaptcha doesn't even render, and the window.repcaptchaVerfier.render() method spits another error (seems like that part was optional in the docs, so I removed it).

  constructor(props) {
    super(props);

    this.state = {
      sendOTP: false,
      phone_number: "",
      currentUser: null,
      otp: "",
      isButtonDisabled: true,
      error: '',
      cardAnimation: 'cardHidden',
    };
  }

  componentDidMount() {
    this.setupRecaptcha();

    setTimeout(() => {
      this.setState({cardAnimation: ""});
    }, 700);

    // window.recaptchaVerifier.render().then(widgetId => {
    //   window.recaptchaWidgetId = widgetId;
    // });
    // Removed, since it errors on load with this code.

  }

  setupRecaptcha = () => {
    window.recaptchaVerifier = new this.props.firebase.recaptchaVerifier(
      'recaptcha-container',
      {
        size: "normal",
        callback: response => {
          // reCAPTCHA solved, allow signInWithPhoneNumber.
          // ...
          this.setState({ isButtonDisabled: false });
        },
        "expired-callback": response => {
          // Response expired. Ask user to solve reCAPTCHA again.
          // ...
          this.setState({ isButtonDisabled: true, error: "Recaptcha Expired. Please try again." });

        }
      }
    );
    console.log(window.recaptchaVerifier)
    // Here is where i get the silent error about "strict" mode.
  };

  handleLogin = () => {
    let appVerifier = window.recaptchaVerifier;
    this.props.firebase.doSignInWithPhoneNumber(this.state.phone_number, appVerifier).then(confirmationResult => {
      this.setState({sendOTP: true});
      window.confirmationResult = confirmationResult;
    }).catch(err => {
      this.setState({error: err})
    })
  };

  handleOTPCheck = () => {
    window.confirmationResult
      .confirm(this.state.otp)
      .then(function(result) {
        // User signed in successfully.
        console.log(result);
        // ...
      })
      .catch(function(error) {
        // User couldn't sign in (bad verification code?)
        // ...
      });
  };

  handleSubmit(event) {
    event.preventDefault();
  }

  onChange = event => {
    this.setState({ [event.target.name]: event.target.value });
  };

  render() {
    const { phone_number, otp, error } = this.state;

    const isInvalid = phone_number === '';
    const { classes } = this.props;

    return (
      <div
        className={classes.pageHeader}
        style={{
          backgroundImage: "url(" + backgroundImage + ")",
          backgroundSize: "cover",
          backgroundPosition: "top center"
        }}
      >
        <div className={classes.container}>
          <Grid container justify="center">
            <Grid item xs={12} sm={12} md={4}>
              <Card className={classes[this.state.cardAnimation]}>
                <form className={classes.form}>
                  <CardHeader color="primary" className={classes.cardHeader}>
                    <Typography variant={'h4'}>Login</Typography>
                  </CardHeader>
                  <CardBody>
                    <TextField
                      label={"Phone Number"}
                      id="phone_number"
                      name={"phone_number"}
                      fullWidth

                      InputProps={{
                        endAdornment: <InputAdornment position="end"><Phone/></InputAdornment>,
                      }}
                      type={"tel"}
                    />
                    <TextField
                      label="One-Time Password"
                      id="otp"
                      name={"otp"}
                      fullWidth

                      InputProps={{
                        endAdornment: <InputAdornment position="end"><Lock/></InputAdornment>,
                      }}

                      type={'password'}
                      autoComplete={'false'}
                    />
                  </CardBody>
                  <div id={'recaptcha-container'}/>

                  <p className={classes.divider}>Standard messaging rates may apply.</p>

                  <CardFooter className={classes.cardFooter}>
                    <Button color="primary" size="large" onClick={this.handleLogin}>
                      Verify
                    </Button>
                  </CardFooter>

                </form>
              </Card>
            </Grid>
          </Grid>
        </div>
        <Footer whiteFont />
      </div>
    );
  }
}
const SignInForm = compose(
  withRouter,
  withFirebase,
)(SignInFormBase);
/// this is then rendered in a functional react component in this page

I have a higher-order component that gives me firebase context. The function called at this.props.firebase.recaptchaVerifier within my setupRecaptcha function is:

class Firebase() {
    constructor() {
        // initialise firebase etc
        this.authRef = app().auth;
        this.auth.useDeviceLanguage();
    }
    recaptchaVerifier = (container, parameters) =>
        this.authRef.RecaptchaVerifier(container, parameters);

}

I'm well and truly stumped!

EDIT:

I managed to fix it. I moved the 'new' statement into my context provider:

recaptchaVerifier = (container, params) => {
    return new this.authRef.RecaptchaVerifier(container, params);
  };
    setupRecaptcha = () => {
        window.recaptchaVerifier = this.props.firebase.recaptchaVerifier('recaptcha-container', {
            'size': 'normal',
            'callback': response => {
                // reCAPTCHA solved, allow signInWithPhoneNumber.
                this.setState({isButtonDisabled: false});
            },
            'expired-callback': response => {
                // Response expired. Ask user to solve reCAPTCHA again.
                this.setState({isButtonDisabled: true, error: "Recaptcha Expired. Please try again."});
            }
        });
        window.recaptchaVerifier.render().then(widgetId => {
            window.recaptchaWidgetId = widgetId;
        });
    };

Upvotes: 5

Views: 7447

Answers (1)

Recon
Recon

Reputation: 618

I managed to fix it. I moved the 'new' statement into my context provider:

// In context.js
recaptchaVerifier = (container, params) => {
    return new this.authRef.RecaptchaVerifier(container, params);
  };
// in my component
    setupRecaptcha = () => {
        window.recaptchaVerifier = this.props.firebase.recaptchaVerifier('recaptcha-container', {
            'size': 'normal',
            'callback': response => {
                // reCAPTCHA solved, allow signInWithPhoneNumber.
                this.setState({isButtonDisabled: false});
            },
            'expired-callback': response => {
                // Response expired. Ask user to solve reCAPTCHA again.
                this.setState({isButtonDisabled: true, error: "Recaptcha Expired. Please try again."});
            }
        });
        window.recaptchaVerifier.render().then(widgetId => {
            window.recaptchaWidgetId = widgetId;
        });
    };```

Upvotes: 2

Related Questions