[android][auth] phoneAuth via EE implementation

This commit is contained in:
Salakar 2017-08-19 05:22:07 +01:00
parent a27abfdf92
commit fd474d5adb
6 changed files with 179 additions and 28 deletions

View File

@ -568,11 +568,12 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
/**
* signInWithPhoneNumber
*
* @param appName
* @param phoneNumber
* @param promise
* @param phoneAuthRequestKey
*/
@ReactMethod
public void signInWithPhoneNumber(String appName, final String phoneNumber, final Promise promise) {
public void signInWithPhoneNumber(final String appName, final String phoneNumber, final String phoneAuthRequestKey) {
Log.d(TAG, "signInWithPhoneNumber");
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
final FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
@ -580,48 +581,64 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
// Reset the verification Id
mVerificationId = null;
PhoneAuthProvider.getInstance(firebaseAuth).verifyPhoneNumber(phoneNumber, 60, TimeUnit.SECONDS,
getCurrentActivity(), new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
PhoneAuthProvider.getInstance(firebaseAuth).verifyPhoneNumber(phoneNumber, 120, TimeUnit.SECONDS,
mReactContext.getCurrentActivity(), new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@Override
public void onVerificationCompleted(PhoneAuthCredential phoneAuthCredential) {
public void onVerificationCompleted(final PhoneAuthCredential phoneAuthCredential) {
// User has been automatically verified, log them in
firebaseAuth.signInWithCredential(phoneAuthCredential)
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult authResult) {
// onAuthStateChanged will pick up the user change
Log.d(TAG, "signInWithPhoneNumber:autoVerified:success");
WritableMap event = Arguments.createMap();
WritableMap user = firebaseUserToMap(authResult.getUser());
event.putMap("user", user);
event.putString("type", "user");
event.putString("appName", appName);
event.putString("appName", phoneAuthCredential.getSmsCode());
event.putString("phoneAuthRequestKey", phoneAuthRequestKey);
Utils.sendEvent(mReactContext, "phone_auth_event", event);
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception exception) {
Log.e(TAG, "signInWithPhoneNumber:autoVerified:failure", exception);
// TODO: Will this ever error? How do we get it back to the JS side?
// promiseRejectAuthException(promise, exception);
WritableMap event = Arguments.createMap();
WritableMap error = getJSError(exception);
event.putMap("error", error);
event.putString("type", "error");
event.putString("appName", appName);
event.putString("phoneAuthRequestKey", phoneAuthRequestKey);
Utils.sendEvent(mReactContext, "phone_auth_event", event);
}
});
}
@Override
public void onVerificationFailed(FirebaseException e) {
// TODO: Will this ever get sent after we've received an onCodeSent?
// If so, then this will cause an exception as the promise has already been used
promiseRejectAuthException(promise, e);
WritableMap event = Arguments.createMap();
WritableMap error = getJSError(e);
event.putMap("error", error);
event.putString("type", "error");
event.putString("appName", appName);
event.putString("phoneAuthRequestKey", phoneAuthRequestKey);
Utils.sendEvent(mReactContext, "phone_auth_event", event);
}
@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("appName", appName);
verificationMap.putString("type", "confirm");
verificationMap.putString("verificationId", verificationId);
promise.resolve(verificationMap);
verificationMap.putString("phoneAuthRequestKey", phoneAuthRequestKey);
Utils.sendEvent(mReactContext, "phone_auth_event", verificationMap);
}
@Override
public void onCodeAutoRetrievalTimeOut (String verificationId) {
public void onCodeAutoRetrievalTimeOut(String verificationId) {
super.onCodeAutoRetrievalTimeOut(verificationId);
// Purposefully not doing anything with this at the moment
}
@ -629,11 +646,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
}
@ReactMethod
public void _confirmVerificationCode(String appName, final String verificationCode, final Promise promise) {
public void _confirmVerificationCode(final String appName, final String phoneAuthRequestKey, String verificationId, String verificationCode, final Promise promise) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
PhoneAuthCredential credential = PhoneAuthProvider.getCredential(mVerificationId, verificationCode);
PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, verificationCode);
firebaseAuth.signInWithCredential(credential)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@ -641,10 +658,24 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Log.d(TAG, "signInWithCredential:onComplete:success");
WritableMap event = Arguments.createMap();
WritableMap user = firebaseUserToMap(task.getResult().getUser());
event.putMap("user", user);
event.putString("type", "user");
event.putString("appName", appName);
event.putString("phoneAuthRequestKey", phoneAuthRequestKey);
Utils.sendEvent(mReactContext, "phone_auth_event", event);
promiseWithUser(task.getResult().getUser(), promise);
} else {
Exception exception = task.getException();
Log.e(TAG, "signInWithCredential:onComplete:failure", exception);
WritableMap event = Arguments.createMap();
WritableMap error = getJSError(exception);
event.putMap("error", error);
event.putString("type", "error");
event.putString("appName", appName);
event.putString("phoneAuthRequestKey", phoneAuthRequestKey);
Utils.sendEvent(mReactContext, "phone_auth_event", event);
promiseRejectAuthException(promise, exception);
}
}
@ -1017,6 +1048,17 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
* @param exception
*/
private void promiseRejectAuthException(Promise promise, Exception exception) {
WritableMap error = getJSError(exception);
promise.reject(error.getString("code"), error.getString("message"), exception);
}
/**
* getJSError
*
* @param exception
*/
private WritableMap getJSError(Exception exception) {
WritableMap error = Arguments.createMap();
String code = "UNKNOWN";
String message = exception.getMessage();
String invalidEmail = "The email address is badly formatted.";
@ -1024,6 +1066,7 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
try {
FirebaseAuthException authException = (FirebaseAuthException) exception;
code = authException.getErrorCode();
error.putString("nativeErrorCode", code);
message = authException.getMessage();
} catch (Exception e) {
Matcher matcher = Pattern.compile("\\[(.*):.*\\]").matcher(message);
@ -1088,7 +1131,10 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
}
code = "auth/" + code.toLowerCase().replace("error_", "").replace('_', '-');
promise.reject(code, message, exception);
error.putString("code", code);
error.putString("message", message);
error.putString("nativeErrorMessage", exception.getMessage());
return error;
}

View File

@ -544,7 +544,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @return
*/
private static String getCodeWithService(String service, String code) {
return service.toUpperCase() + "/" + code.toUpperCase();
return service.toLowerCase() + "/" + code.toLowerCase();
}
/**

View File

@ -56,6 +56,13 @@ export default {
ERROR_INIT_OBJECT: 'Firebase.initializeApp(options <-- requires a valid configuration object.',
ERROR_INIT_STRING_NAME: 'Firebase.initializeApp(options, name <-- requires a valid string value.',
/**
* @return {string}
*/
ERROR_MISSING_CB(method) {
return `Missing required callback for method ${method}().`;
},
/**
* @return {string}
* @param namespace

View File

@ -4,19 +4,31 @@
export default class ConfirmationResult {
_auth: Object;
_verificationId: string;
_phoneAuthRequestKey: string;
/**
*
* @param auth
* @param verificationId The phone number authentication operation's verification ID.
* @param phoneAuthRequestKey
*/
constructor(auth: Object, verificationId: string) {
constructor(auth: Object, verificationId: string, phoneAuthRequestKey: string) {
this._auth = auth;
this._verificationId = verificationId;
this._phoneAuthRequestKey = phoneAuthRequestKey;
}
/**
*
* @param verificationCode
* @return {*}
*/
confirm(verificationCode: string): Promise<Object> {
// verificationId is stored server side in case the app is shut when opening the SMS app
return this._auth._native.confirmVerificationCode(verificationCode);
return this._auth._native._confirmVerificationCode(
this._phoneAuthRequestKey,
this._verificationId,
verificationCode,
);
}
get verificationId(): String | null {

View File

@ -1,6 +1,8 @@
// @flow
import User from './user';
import INTERNALS from './../../internals';
import ModuleBase from './../../utils/ModuleBase';
import { nativeToJSError, generatePushID, isFunction } from './../../utils';
import ConfirmationResult from './ConfirmationResult';
// providers
@ -31,9 +33,48 @@ export default class Auth extends ModuleBase {
this._onAuthStateChanged.bind(this),
);
this.addListener(
// sub to phone auth native event - this can
// be either a code sent confirmation result
// event, a successful signIn event or an
// error event
this._getAppEventName('phone_auth_event'),
this._onPhoneAuthEvent.bind(this),
);
this._native.addAuthStateListener();
}
/**
* Handles an incoming phone auth event and emits to the internal once listeners.
* @param event
* @private
*/
_onPhoneAuthEvent(event) {
const { type, phoneAuthRequestKey } = event;
if (type === 'confirm') {
return this.emit(
`phone:auth:${phoneAuthRequestKey}:confirm`,
new ConfirmationResult(this, event.verificationId, phoneAuthRequestKey),
);
}
if (type === 'user') {
return this.emit(
`phone:auth:${phoneAuthRequestKey}:user`,
this._onAuthStateChanged({ authenticated: true, user: event.user }, false),
);
}
if (type === 'error') {
const { code, message } = event.error;
return this.emit(`phone:auth:${phoneAuthRequestKey}:error`, nativeToJSError(code, message));
}
throw new Error('Internal RNFirebase Error: Invalid phone auth event received.');
}
/**
* Internal auth changed listener
* @param auth
@ -141,18 +182,58 @@ export default class Auth extends ModuleBase {
* @return {Promise} A promise resolved upon completion
*/
signInWithCredential(credential: CredentialType): Promise<Object> {
return this._interceptUserValue(this._native.signInWithCredential(credential.provider, credential.token, credential.secret));
return this._interceptUserValue(
this._native.signInWithCredential(
credential.provider, credential.token, credential.secret,
),
);
}
/**
*
* @param phoneNumber
* @return {Promise.<TResult>}
* @return Object
*/
signInWithPhoneNumber(phoneNumber: string): Promise<Object> {
return this._native.signInWithPhoneNumber(phoneNumber).then((result) => {
return new ConfirmationResult(this, result.verificationId);
});
const phoneAuthRequestKey = generatePushID();
return {
onCodeSent: (cb) => {
if (!isFunction(cb)) {
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_CB('onCodeSent'));
}
this.once(`phone:auth:${phoneAuthRequestKey}:confirm`, cb);
return new Promise((resolve, reject) => {
// start auth flow
this._native.signInWithPhoneNumber(
phoneNumber,
phoneAuthRequestKey,
);
let hadEvent = false;
const successEvent = `phone:auth:${phoneAuthRequestKey}:user`;
const errorEvent = `phone:auth:${phoneAuthRequestKey}:error`;
this.once(successEvent, (user) => {
if (!hadEvent) {
hadEvent = true;
this.removeListener(errorEvent, reject);
resolve(user);
}
});
this.once(errorEvent, (error) => {
if (!hadEvent) {
hadEvent = true;
this.removeListener(successEvent, resolve);
reject(error);
}
});
});
},
};
}
/**

View File

@ -23,6 +23,7 @@ const NATIVE_MODULE_EVENTS = {
],
Auth: [
'onAuthStateChanged',
'phone_auth_event',
],
Database: [
// 'database_on_event',
@ -133,6 +134,10 @@ export default class ModuleBase {
return INTERNALS.SharedEventEmitter.addListener.bind(INTERNALS.SharedEventEmitter);
}
get once() {
return INTERNALS.SharedEventEmitter.once.bind(INTERNALS.SharedEventEmitter);
}
get on() {
return INTERNALS.SharedEventEmitter.addListener.bind(INTERNALS.SharedEventEmitter);
}