EddyVerbruggen / nativescript-plugin-firebase

:fire: NativeScript plugin for Firebase
https://firebase.google.com
MIT License
1.01k stars 445 forks source link

Android automatically enter phone auth code and logs in but prompt still displayed #1783

Open dlcole opened 3 years ago

dlcole commented 3 years ago

At least some versions of Android will auto-fill the sms code for phone authentication. This results in the user being successfully logged in without actually having to enter the code (yay!) but also leave the confirmation prompt open, so it looks like they still have to manually enter the code.

Is there any way to dismiss the prompt when the user is successfully logged in?

Guervyl commented 3 years ago

I confirm, this is a real issue. When I first had this issue I was wondering why it prints the user information before I enter the code! But then I understand it's an issue from the plugin.

PackagedCat commented 3 years ago

@dlcole, @Guervyl In my time after learning source code I made hack for this. Try this

import { firebase } from "@nativescript/firebase";
import { firebase as firebaseCommon } from "@nativescript/firebase/firebase-common";

private userResponse = null;

//On page initialized event
public loaded() {
  // Overriding default implementation of verification, where showing modal dialog
  // (auto-fill code from sms on android will still work)
  firebaseCommon["requestPhoneAuthVerificationCode"] = ((userResponse) => {
    // Saving callback method
    this.userResponse = userResponse;
  }).bind(this);
}

// On start login button tap event from view
public onLoginButtonTap() {
  firebase.login( ... );
}

// On confirm button tap event from view
public onConfirmCodeButtonTap() {
  // Using callback method for verificate code from text input
  // (can take "undefined" value if you want to cancel login)
  this.userResponse(this.verificationCode); // this.verificationCode is entered text from view
}
dlcole commented 3 years ago

@PackagedCat Your code is very helpful and seems to work, but I'm trying to understand what you're doing. Mine is a JavaScript project, so I've converted the code thusly:

const firebaseCommon = require("nativescript-plugin-firebase/firebase-common");

exports.onLoaded = function (args) {

  // Overriding default implementation of verification, where showing modal dialog
  // (auto-fill code from sms on android will still work)
  // See https://github.com/EddyVerbruggen/nativescript-plugin-firebase/issues/1783
  if (global.isAndroid) {
    firebaseCommon["requestPhoneAuthVerificationCode"] = ((userResponse) => {
      // Saving callback method
      this.userResponse = userResponse;
    }).bind(this);
  }

  var onConfirmCodeButtonClick = function () {
    if (global.isAndroid) {
      // Using callback method for verificate code from text input
      // (can take "undefined" value if you want to cancel login)
      console.log("onConfirmCodeButtonClick...")
      this.userResponse(this.verificationCode);
    }
  }
} 

It looks like you're bypassing the prompt, and this seems to work on both my test devices. onConfirmCodeButtonClink() is never invoked, though. Should it be?

PackagedCat commented 3 years ago

@dlcole onConfirmCodeButtonClick is callback for Button on view just for example. The main thing is to call the callback this.userResponse with verification code from SMS somewhere in your code. I did some changes in code above.

dlcole commented 3 years ago

@PackagedCat I'm still not understanding... is onConfirmCodeButtonClick() bound to the OK button on the prompt dialog displayed by the plugin? That is, my code doesn't handle the value from the SMS prompt, I get control back from the plugin after the SMS code has been entered and authenticated.

PackagedCat commented 3 years ago

@dlcole No, onConfirmCodeButtonClick bounds to your custom button placed somewhere in view. May will be more helpful with full code of example of how I implemented code verification in my application (I use Vue)

View:

  <Page
      actionBarHidden="true"
      @navigatedTo="onNavigatedTo"
      @navigatingFrom="onNavigatingFrom">
      <StackLayout>
          <Label 
              textWrap="true"
              text="Input verification code from SMS" />
          <TextField
              keyboardType="number"
              digits="0123456789"
              v-model="verificationCode"/>

          <Button
              text="Submit"
              @tap="onConfirmCodeButtonTap" />
      </StackLayout>
  </Page>

Code:

import NativeScriptVue from "nativescript-vue";
import { Component , Prop } from "vue-property-decorator";
import { firebase } from "@nativescript/firebase";
import { firebase as firebaseCommon } from "@nativescript/firebase/firebase-common";

@Component
export default class SignInVerifyCodeView extends NativeScriptVue {
    @Prop() phoneNumber;

    private userResponse = null;
    private firebaseUser = null;

    public onNavigatedTo() {
      // Overriding default implementation of verification to prevent show modal dialog
      // (auto-fill code from sms on android will still work)
      firebaseCommon["requestPhoneAuthVerificationCode"] = ((userResponse) => {
          // Saving callback method
          this.userResponse = userResponse;
      }).bind(this);

     this.startLogin();
    }

    private onNavigatingFrom() {
        // Canceling login if user pressed back button
        // (I dont know how detect back button press but it works :) )
        if (this.userResponse != null) {
            this.userResponse(undefined);
        }
    }

    private async startLogin() {
      try {
          firebaseUser = await firebase.login({
              type: firebase.LoginType.PHONE,
              phoneOptions: {
                  phoneNumber: this.phoneNumber, // this.phoneNumber passed from previous page
                  android: {
                      timeout: 30
                  }
              }
          });

        // If login success, set userResponse = null to prevent calling method
        // in onNavigatingFrom event
        this.userResponse = null;

        // Do something else
      } catch (error) {
          // May be has better way to handle error
          if (typeof error === "string") {
              // Throws when user entered invalid verification code
              if (error.indexOf("credential is invalid") !== -1) {
                  // Cyclical login only for invalid verification code error
                  this.startLogin();
                  return;
              }

              // Throws when passed 'undefined' value in userResponse callback
              if (error.indexOf("cancelled") !== -1) {
                  // 
              }
          }

          // May show error to user
      }
    }

    public onConfirmCodeButtonTap() {
        // Using callback method for verificate code from text input
        // (can take "undefined" value if you want to cancel login)
        this.userResponse(this.verificationCode);
    }
}
dlcole commented 3 years ago

@PackagedCat It looks like you're providing your own verification code prompt, where I'm using the prompt from the plugin. Thus, I expect the onConfirmCodeButtonTap() is likewise handled by the plugin.

Thank-you for your help with this. Log-in on Android is now very much simplified!