diff --git a/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java b/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java index b807574f..c5871f76 100644 --- a/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java +++ b/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java @@ -561,6 +561,108 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } + /** + * signInWithPhoneNumber + * + * @param appName + * @param phoneNumber + */ + @ReactMethod + public void signInWithPhoneNumber(String appName, final String phoneNumber, final Promise promise) { + Log.d(TAG, "signInWithPhoneNumber"); + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + final FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + + // Reset the verification Id + mVerificationId = null; + + PhoneAuthProvider.getInstance(firebaseAuth).verifyPhoneNumber(phoneNumber, 60, TimeUnit.SECONDS, + mReactContext.getCurrentActivity(), new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { + private boolean promiseResolved = false; + + @Override + public void onVerificationCompleted(final PhoneAuthCredential phoneAuthCredential) { + // User has been automatically verified, log them in + firebaseAuth.signInWithCredential(phoneAuthCredential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + // onAuthStateChanged will pick up the user change + Log.d(TAG, "signInWithPhoneNumber:autoVerified:signInWithCredential:onComplete:success"); + // To ensure that there is no hanging promise, we resolve it with a null verificationId + // as calling ConfirmationResult.confirm(code) is invalid in this case anyway + if (!promiseResolved) { + WritableMap verificationMap = Arguments.createMap(); + verificationMap.putNull("verificationId"); + promise.resolve(verificationMap); + } + } else { + // With phone auth, the credential will only every be rejected if the user + // account linked to the phone number has been disabled + Exception exception = task.getException(); + Log.e(TAG, "signInWithPhoneNumber:autoVerified:signInWithCredential:onComplete:failure", exception); + if (promiseResolved) { + // In the scenario where an SMS code has been sent, we have no way to report + // back to the front-end that as the promise has already been used + } else { + promiseRejectAuthException(promise, exception); + } + } + } + }); + } + + @Override + public void onVerificationFailed(FirebaseException e) { + // This callback is invoked in an invalid request for verification is made, + // e.g. phone number format is incorrect, or the SMS quota for the project + // has been exceeded + Log.d(TAG, "signInWithPhoneNumber:verification:failed"); + promiseRejectAuthException(promise, e); + } + + @Override + public void onCodeSent(String verificationId, PhoneAuthProvider.ForceResendingToken forceResendingToken) { + // TODO: This isn't being saved anywhere if the activity gets restarted when going to the SMS app + mVerificationId = verificationId; + WritableMap verificationMap = Arguments.createMap(); + verificationMap.putString("verificationId", verificationId); + promise.resolve(verificationMap); + promiseResolved = true; + } + + @Override + public void onCodeAutoRetrievalTimeOut(String verificationId) { + super.onCodeAutoRetrievalTimeOut(verificationId); + // Purposefully not doing anything with this at the moment + } + }); + } + + @ReactMethod + public void _confirmVerificationCode(String appName, final String verificationCode, final Promise promise) { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + + PhoneAuthCredential credential = PhoneAuthProvider.getCredential(mVerificationId, verificationCode); + + firebaseAuth.signInWithCredential(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "_confirmVerificationCode:signInWithCredential:onComplete:success"); + promiseWithUser(task.getResult().getUser(), promise); + } else { + Exception exception = task.getException(); + Log.e(TAG, "_confirmVerificationCode:signInWithCredential:onComplete:failure", exception); + promiseRejectAuthException(promise, exception); + } + } + }); + } + /** * confirmPasswordReset * diff --git a/lib/modules/auth/ConfirmationResult.js b/lib/modules/auth/ConfirmationResult.js new file mode 100644 index 00000000..6bfda24a --- /dev/null +++ b/lib/modules/auth/ConfirmationResult.js @@ -0,0 +1,32 @@ +/** + * @url https://firebase.google.com/docs/reference/js/firebase.User + */ +export default class ConfirmationResult { + _auth: Object; + _verificationId: string; + + /** + * + * @param auth + * @param verificationId The phone number authentication operation's verification ID. + */ + constructor(auth: Object, verificationId: string) { + this._auth = auth; + this._verificationId = verificationId; + } + + /** + * + * @param verificationCode + * @return {*} + */ + confirm(verificationCode: string): Promise { + return this._auth._interceptUserValue( + this._auth._native._confirmVerificationCode(verificationCode) + ); + } + + get verificationId(): String | null { + return _verificationId; + } +} diff --git a/lib/modules/auth/index.js b/lib/modules/auth/index.js index 395dd00d..46988232 100644 --- a/lib/modules/auth/index.js +++ b/lib/modules/auth/index.js @@ -1,6 +1,7 @@ // @flow import User from './user'; import ModuleBase from './../../utils/ModuleBase'; +import ConfirmationResult from './ConfirmationResult'; // providers import EmailAuthProvider from './providers/Email'; @@ -8,6 +9,7 @@ import GoogleAuthProvider from './providers/Google'; import GithubAuthProvider from './providers/Github'; import TwitterAuthProvider from './providers/Twitter'; import FacebookAuthProvider from './providers/Facebook'; +import PhoneAuthProvider from './providers/Phone'; export default class Auth extends ModuleBase { static _NAMESPACE = 'auth'; @@ -146,6 +148,16 @@ export default class Auth extends ModuleBase { ); } + /** + * Asynchronously signs in using a phone number. + * + */ + signInWithPhoneNumber(phoneNumber: string): Promise { + return this._native.signInWithPhoneNumber(phoneNumber).then((result) => { + return new ConfirmationResult(this, result.verificationId); + }); + } + /** * Send reset password instructions via email * @param {string} email The email to send password reset instructions diff --git a/lib/modules/auth/providers/Phone.js b/lib/modules/auth/providers/Phone.js new file mode 100644 index 00000000..9fa405d6 --- /dev/null +++ b/lib/modules/auth/providers/Phone.js @@ -0,0 +1,10 @@ +export default { + credential(verificationId, code) { + return { + token: verificationId, + secret: code, + provider: 'phone', + providerId: 'phone', + }; + }, +};