Closed cqwcent closed 6 months ago
I have the same issue on Android with @react-native-firebase==10.8.0
when I try to link anonymous user to a social account that already exists. Last version to correctly raise auth/credential-already-in-use
is 10.1.1
(though I'm not 100% sure).
@devtud have you tried reverting the firebase-android-sdk version back to the one that was in use with the last version of react-native-firebase that worked for you? The gradle build logs will tell you which firebase-android-sdk is being used and rnfirebase.io has instructions on how to override for testing https://rnfirebase.io/#android
@mikehardy I have overwritten bom 26.5.0 with 26.0.0 and it seems the auth/credential-already-in-use
error is raised correctly. Thanks.
Interesting. Then it is one of these two releases:
https://firebase.google.com/support/release-notes/android#auth_v20-0-1 (firebase-android-sdk 26.1.0) https://firebase.google.com/support/release-notes/android#auth_v20-0-2 (firebase-android-sdk 26.3.0)
@devtud can you confirm which one breaks? If we know for sure 26.0.0 works and 26.1.0 breaks or that 26.2.0 still works but 26.3.0 breaks then we can really limit our search area for the regression down
I really appreciate the testing, thank you
@mikehardy , 26.1.0 breaks.
Unfortunately if I understand correctly firebase-android-sdk has not open sourced the auth module, but they do still track their issues on github (for auth as well) https://github.com/firebase/firebase-android-sdk/issues/new?assignees=&labels=&template=bug.md
They typically want a reproduction but they offer quickstarts to make it relatively painless https://github.com/firebase/quickstart-android/tree/master/auth - I suppose the hard part will be integrating the facebook example but they have a pretty good guide for native as well https://developers.facebook.com/docs/facebook-login/android
While recognizing that's a pain, if the only thing changed here (in react-native-firebase) was using underlying android SDK 26.0.0 vs 26.1.0, it's got to be an upstream issue and it must be pursued there
Hi @mikehardy I am facing this same issue on linkWithCredentials while trying to link the PhoneNo. on Android Devices. It works fine on IOS devices. I did debug and found on the Android side on the error is caught logged on the console however its not returned and instead the user linked with phone no. is returned.
the section of code giving the issue.
@GautamKrishnan I don't understand how that moves the issue forward in the context of my previous comment. Can you explain? Or are we still waiting for a native reproduction and information from firebase-android-sdk support?
I'm having the same issue.
@modmido how did it go when you did a native reproduction and submitted it to firebase-android-sdk issue tracker?
Same here. Created an issue. If anyone one would be able to create a reproducable demo - he would be a hero.
Any thought what could be done right now to prevent data loss from anonymous user while linking accounts?
@RohovDmytro Hi, I commented this portion in ReactNativeFirebaseAuthModule.java and returned the promiseRejectAuthException(promise, exception);
Solution from @GautamKrishnan seems to work fine for me. Trying to link with an existing account throws an error, linking with a new (email) account works fine.
Patch in case anyone needs it (yes, unclean indentations ^^):
diff --git a/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
index c649123..37e00af 100644
--- a/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
+++ b/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
@@ -1311,6 +1311,8 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
} else {
Exception exception = task.getException();
Log.e(TAG, "link:onComplete:failure", exception);
+ promiseRejectAuthException(promise, exception);
+ /*
if (exception instanceof FirebaseAuthUserCollisionException) {
FirebaseAuthUserCollisionException authUserCollisionException = (FirebaseAuthUserCollisionException) exception;
AuthCredential updatedCredential = authUserCollisionException.getUpdatedCredential();
@@ -1329,6 +1331,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
} else {
promiseRejectAuthException(promise, exception);
}
+ */
}});
} else {
promiseNoUser(promise, true);
We've never done this before but I want to help - if there was some way to constrain this workaround in the code to only the affected firebase-android-sdks that are affected 26.1.0 up through but maybe not including(?) 27.0.0 maybe we could include the version check then the workaround? I'm not sure if firebase-android-sdk's auth module makes it's version available dynamically or not to make something like that work
Hello 👋, to help manage issues we automatically close stale issues. This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?
This issue will be closed in 15 days if no further activity occurs. Thank you for your contributions.
I am having the same issue, we have anonymouse users + phone auth and I am trying to linkWithCredential
and if it returns "auth/credential-already-in-use" error I am doing signInWithCredential
instead. Works like a charm on iOS but fails on Android. After going through code for both platforms I've noticed is that behaviour of linkWithCredential
on iOS and Android is completely different. On iOS, as expected, it throws "auth/credential-already-in-use" error, returns updated auth credentials hash key in error.userInfo.authCredential
and also saves updated non-serializable credentials to temp dictionary which is later re-used if signInWithCredential
is called using same hash key returned in error.userInfo.authCredential
On the other hand Android version attempts to do all of this automatically, which @GautamKrishnan tried to avoid by just replacing it with promiseRejectAuthException(promise, exception);
In my case this was only half working, because now I was getting a "auth/credential-already-in-use" error as expected, but still no updated credentials that I can use to attempt a signInWithCredential
to match iOS behaviour. @mikehardy I am not exactly sure why it was done differently on iOS and Android, but it seems like it is an issue with react-native-firebase and not with firebase-android-sdk. Here is my Patch that fixes it for me (basically just copied what iOS code does):
diff --git a/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
index d30755e..f8aeccd 100644
--- a/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
+++ b/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
@@ -84,6 +84,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
private String mLastPhoneNumber;
private PhoneAuthProvider.ForceResendingToken mForceResendingToken;
private PhoneAuthCredential mCredential;
+ private HashMap<String, AuthCredential> credentials = new HashMap<>();
ReactNativeFirebaseAuthModule(ReactApplicationContext reactContext) {
@@ -1312,21 +1313,24 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
} else {
Exception exception = task.getException();
Log.e(TAG, "link:onComplete:failure", exception);
+ //promiseRejectAuthException(promise, exception);
+
if (exception instanceof FirebaseAuthUserCollisionException) {
FirebaseAuthUserCollisionException authUserCollisionException = (FirebaseAuthUserCollisionException) exception;
AuthCredential updatedCredential = authUserCollisionException.getUpdatedCredential();
- try {
- firebaseAuth.signInWithCredential(updatedCredential).addOnCompleteListener(getExecutor(), result -> {
- if (result.isSuccessful()) {
- promiseWithAuthResult(result.getResult(), promise);
- } else {
- promiseRejectAuthException(promise, exception);
- }
- });
- } catch (Exception e) {
- // we the attempt to log in after the collision failed, reject back to JS
- promiseRejectAuthException(promise, exception);
- }
+ promiseRejectAuthException(promise, exception, updatedCredential);
+ // try {
+ // firebaseAuth.signInWithCredential(updatedCredential).addOnCompleteListener(getExecutor(), result -> {
+ // if (result.isSuccessful()) {
+ // promiseWithAuthResult(result.getResult(), promise);
+ // } else {
+ // promiseRejectAuthException(promise, exception);
+ // }
+ // });
+ // } catch (Exception e) {
+ // // we the attempt to log in after the collision failed, reject back to JS
+ // promiseRejectAuthException(promise, exception);
+ // }
} else {
promiseRejectAuthException(promise, exception);
}
@@ -1412,31 +1416,35 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
String authToken,
String authSecret
) {
- switch (provider) {
- case "facebook.com":
- return FacebookAuthProvider.getCredential(authToken);
- case "google.com":
- return GoogleAuthProvider.getCredential(authToken, authSecret);
- case "twitter.com":
- return TwitterAuthProvider.getCredential(authToken, authSecret);
- case "github.com":
- return GithubAuthProvider.getCredential(authToken);
- case "apple.com":
- return OAuthProvider.newCredentialBuilder(provider).setIdTokenWithRawNonce(authToken, authSecret).build();
- case "oauth":
- return OAuthProvider.getCredential(provider, authToken, authSecret);
- case "phone":
- return getPhoneAuthCredential(authToken, authSecret);
- case "password":
- // authToken = email
- // authSecret = password
- return EmailAuthProvider.getCredential(authToken, authSecret);
- case "emailLink":
- // authToken = email
- // authSecret = link
- return EmailAuthProvider.getCredentialWithLink(authToken, authSecret);
- default:
- return null;
+ if (credentials.containsKey(authToken) && credentials.get(authToken) != null) {
+ return credentials.get(authToken);
+ } else {
+ switch (provider) {
+ case "facebook.com":
+ return FacebookAuthProvider.getCredential(authToken);
+ case "google.com":
+ return GoogleAuthProvider.getCredential(authToken, authSecret);
+ case "twitter.com":
+ return TwitterAuthProvider.getCredential(authToken, authSecret);
+ case "github.com":
+ return GithubAuthProvider.getCredential(authToken);
+ case "apple.com":
+ return OAuthProvider.newCredentialBuilder(provider).setIdTokenWithRawNonce(authToken, authSecret).build();
+ case "oauth":
+ return OAuthProvider.getCredential(provider, authToken, authSecret);
+ case "phone":
+ return getPhoneAuthCredential(authToken, authSecret);
+ case "password":
+ // authToken = email
+ // authSecret = password
+ return EmailAuthProvider.getCredential(authToken, authSecret);
+ case "emailLink":
+ // authToken = email
+ // authSecret = link
+ return EmailAuthProvider.getCredentialWithLink(authToken, authSecret);
+ default:
+ return null;
+ }
}
}
@@ -1792,6 +1800,30 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
rejectPromiseWithCodeAndMessage(promise, error.getString("code"), error.getString("message"));
}
+ public void promiseRejectAuthException(
+ Promise promise,
+ Exception exception,
+ AuthCredential authCredential
+ ) {
+ WritableMap error = getJSError(exception);
+
+ String authHashCode = String.valueOf(authCredential.hashCode());
+
+ WritableMap authCredentialsMap = Arguments.createMap();
+ authCredentialsMap.putString("providerId", authCredential.getProvider());
+ authCredentialsMap.putString("token", authHashCode);
+ authCredentialsMap.putString("secret", null);
+
+ // Temporarily store the non-serializable credential for later
+ credentials.put(authHashCode, authCredential);
+
+ WritableMap userInfoMap = Arguments.createMap();
+ userInfoMap.putString("code", error.getString("code"));
+ userInfoMap.putString("message", error.getString("message"));
+ userInfoMap.putMap("authCredential", authCredentialsMap);
+ promise.reject(error.getString("code"), error.getString("message"), userInfoMap);
+ }
+
/**
* getJSError
*
I can do a PR if needed, but I think this might break things for people who are using it just for Android.
I think this was fixed with #5694 - the original PR that added the attempt in android to upgrade anonymous users was later disavowed as no longer working by the author so removing it seems non-breaking (it was already broken?) and now the platforms should be equivalent. v12.7.5 release here has it
@mikehardy FYI the revert still doesn't push the updated credentials to the JS side. In order to properly fix this, you'll have to add the patch based on the work of @Shaninnik):
1) Import types:
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
2) Add a private hashmap:
private HashMap<String, AuthCredential> credentials = new HashMap<>();
3) Add new credential storage for getter
private AuthCredential getCredentialForProvider(
String provider, String authToken, String authSecret) {
if (credentials.containsKey(authToken) && credentials.get(authToken) != null) {
return credentials.get(authToken);
} else {
switch (provider) {
case "facebook.com":
return FacebookAuthProvider.getCredential(authToken);
case "google.com":
return GoogleAuthProvider.getCredential(authToken, authSecret);
case "twitter.com":
return TwitterAuthProvider.getCredential(authToken, authSecret);
case "github.com":
return GithubAuthProvider.getCredential(authToken);
case "apple.com":
return OAuthProvider.newCredentialBuilder(provider)
.setIdTokenWithRawNonce(authToken, authSecret)
.build();
case "oauth":
return OAuthProvider.getCredential(provider, authToken, authSecret);
case "phone":
return getPhoneAuthCredential(authToken, authSecret);
case "password":
// authToken = email
// authSecret = password
return EmailAuthProvider.getCredential(authToken, authSecret);
case "emailLink":
// authToken = email
// authSecret = link
return EmailAuthProvider.getCredentialWithLink(authToken, authSecret);
default:
return null;
}
}
}
4) Handle link exceptions with new reject handler:
/**
* promiseRejectLinkAuthException
*
* @param promise
* @param exception
* @param authCredential
*/
private void promiseRejectLinkAuthException(Promise promise, Exception exception, AuthCredential authCredential) {
WritableMap error = getJSError(exception);
String authHashCode = String.valueOf(authCredential.hashCode());
WritableMap authCredentialsMap = Arguments.createMap();
authCredentialsMap.putString("providerId", authCredential.getProvider());
authCredentialsMap.putString("token", authHashCode);
authCredentialsMap.putString("secret", null);
// Temporarily store the non-serializable credential for later
credentials.put(authHashCode, authCredential);
WritableMap userInfoMap = Arguments.createMap();
userInfoMap.putString("code", error.getString("code"));
userInfoMap.putString("message", error.getString("message"));
userInfoMap.putMap("authCredential", authCredentialsMap);
promise.reject(error.getString("code"), error.getString("message"), userInfoMap);
}
@ReactMethod
private void linkWithCredential(
String appName, String provider, String authToken, String authSecret, final Promise promise) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
AuthCredential credential = getCredentialForProvider(provider, authToken, authSecret);
if (credential == null) {
rejectPromiseWithCodeAndMessage(
promise,
"invalid-credential",
"The supplied auth credential is malformed, has expired or is not currently supported.");
} else {
FirebaseUser user = firebaseAuth.getCurrentUser();
Log.d(TAG, "link");
if (user != null) {
user.linkWithCredential(credential)
.addOnCompleteListener(
getExecutor(),
task -> {
if (task.isSuccessful()) {
Log.d(TAG, "link:onComplete:success");
promiseWithAuthResult(task.getResult(), promise);
} else {
Exception exception = task.getException();
if (exception instanceof FirebaseAuthUserCollisionException) {
FirebaseAuthUserCollisionException authUserCollisionException = (FirebaseAuthUserCollisionException) exception;
AuthCredential updatedCredential = authUserCollisionException.getUpdatedCredential();
Log.e(TAG, "link:onComplete:collisionFailure", exception);
promiseRejectLinkAuthException(promise, exception, updatedCredential);
} else {
Log.e(TAG, "link:onComplete:failure", exception);
promiseRejectAuthException(promise, exception);
}
}
});
} else {
promiseNoUser(promise, true);
}
}
}
From the JS-side, you can now do the following:
try {
// try linking credential
await auth().currentUser!.linkWithCredential(credential);
await auth().currentUser!.reload();
// Handle success
} catch (err) {
const error = err as FirebaseAuthTypes.NativeFirebaseAuthError;
// If authCredentials are available, we can log in on the
// account that we attempted to link with
if (error.userInfo.authCredential) {
await auth().signInWithCredential(error.userInfo.authCredential);
await auth().currentUser!.reload();
// Handle success, but now logged into the linked account
}
// Handle error
I got a got diff, but its mired with style changes, so hope you're able to follow.
Is there a way for you to use patch-package or similar? the text of the new is nowhere near as powerful as a diff I can examine + apply :-), one goes in the "oh no, needs triage" pile, one goes in the "review and likely merge" pile, and review+merge pile gets processed satisfyingly quickly, triage pile is slooow
@mikehardy as said, my patch-package will get really messy due to stying changes. This approach now mirrors iOS perfectly. The same JS code can be used on both platforms to handle account colissions.
Here it is:
--- a/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
+++ b/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
@@ -42,6 +42,7 @@ import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
+import com.google.firebase.auth.FirebaseAuthUserCollisionException;
import com.google.firebase.auth.FirebaseAuthProvider;
import com.google.firebase.auth.FirebaseAuthSettings;
import com.google.firebase.auth.FirebaseUser;
@@ -70,11 +71,12 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
-@SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "JavaDoc"})
+@SuppressWarnings({ "ThrowableResultOfMethodCallIgnored", "JavaDoc" })
class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
private static final String TAG = "Auth";
private static HashMap<String, FirebaseAuth.AuthStateListener> mAuthListeners = new HashMap<>();
private static HashMap<String, FirebaseAuth.IdTokenListener> mIdTokenListeners = new HashMap<>();
+ private HashMap<String, AuthCredential> credentials = new HashMap<>();
private String mVerificationId;
private String mLastPhoneNumber;
private PhoneAuthProvider.ForceResendingToken mForceResendingToken;
@@ -102,8 +104,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
String appName = (String) pair.getKey();
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
- FirebaseAuth.AuthStateListener mAuthListener =
- (FirebaseAuth.AuthStateListener) pair.getValue();
+ FirebaseAuth.AuthStateListener mAuthListener = (FirebaseAuth.AuthStateListener) pair.getValue();
firebaseAuth.removeAuthStateListener(mAuthListener);
authListenerIterator.remove();
}
@@ -130,24 +131,21 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
FirebaseAuth.AuthStateListener mAuthListener = mAuthListeners.get(appName);
if (mAuthListener == null) {
- FirebaseAuth.AuthStateListener newAuthListener =
- firebaseAuth1 -> {
- FirebaseUser user = firebaseAuth1.getCurrentUser();
- WritableMap eventBody = Arguments.createMap();
- ReactNativeFirebaseEventEmitter emitter =
- ReactNativeFirebaseEventEmitter.getSharedInstance();
- if (user != null) {
- eventBody.putString("appName", appName); // for js side distribution
- eventBody.putMap("user", firebaseUserToMap(user));
- } else {
- eventBody.putString("appName", appName); // for js side distribution
- }
- Log.d(TAG, "addAuthStateListener:eventBody " + eventBody.toString());
-
- ReactNativeFirebaseEvent event =
- new ReactNativeFirebaseEvent("auth_state_changed", eventBody, appName);
- emitter.sendEvent(event);
- };
+ FirebaseAuth.AuthStateListener newAuthListener = firebaseAuth1 -> {
+ FirebaseUser user = firebaseAuth1.getCurrentUser();
+ WritableMap eventBody = Arguments.createMap();
+ ReactNativeFirebaseEventEmitter emitter = ReactNativeFirebaseEventEmitter.getSharedInstance();
+ if (user != null) {
+ eventBody.putString("appName", appName); // for js side distribution
+ eventBody.putMap("user", firebaseUserToMap(user));
+ } else {
+ eventBody.putString("appName", appName); // for js side distribution
+ }
+ Log.d(TAG, "addAuthStateListener:eventBody " + eventBody.toString());
+
+ ReactNativeFirebaseEvent event = new ReactNativeFirebaseEvent("auth_state_changed", eventBody, appName);
+ emitter.sendEvent(event);
+ };
firebaseAuth.addAuthStateListener(newAuthListener);
mAuthListeners.put(appName, newAuthListener);
@@ -179,25 +177,22 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
if (!mIdTokenListeners.containsKey(appName)) {
- FirebaseAuth.IdTokenListener newIdTokenListener =
- firebaseAuth1 -> {
- FirebaseUser user = firebaseAuth1.getCurrentUser();
- ReactNativeFirebaseEventEmitter emitter =
- ReactNativeFirebaseEventEmitter.getSharedInstance();
- WritableMap eventBody = Arguments.createMap();
- if (user != null) {
- eventBody.putBoolean("authenticated", true);
- eventBody.putString("appName", appName);
- eventBody.putMap("user", firebaseUserToMap(user));
- } else {
- eventBody.putString("appName", appName);
- eventBody.putBoolean("authenticated", false);
- }
-
- ReactNativeFirebaseEvent event =
- new ReactNativeFirebaseEvent("auth_id_token_changed", eventBody, appName);
- emitter.sendEvent(event);
- };
+ FirebaseAuth.IdTokenListener newIdTokenListener = firebaseAuth1 -> {
+ FirebaseUser user = firebaseAuth1.getCurrentUser();
+ ReactNativeFirebaseEventEmitter emitter = ReactNativeFirebaseEventEmitter.getSharedInstance();
+ WritableMap eventBody = Arguments.createMap();
+ if (user != null) {
+ eventBody.putBoolean("authenticated", true);
+ eventBody.putString("appName", appName);
+ eventBody.putMap("user", firebaseUserToMap(user));
+ } else {
+ eventBody.putString("appName", appName);
+ eventBody.putBoolean("authenticated", false);
+ }
+
+ ReactNativeFirebaseEvent event = new ReactNativeFirebaseEvent("auth_id_token_changed", eventBody, appName);
+ emitter.sendEvent(event);
+ };
firebaseAuth.addIdTokenListener(newIdTokenListener);
mIdTokenListeners.put(appName, newIdTokenListener);
@@ -221,10 +216,13 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
}
/**
- * The phone number and SMS code here must have been configured in the Firebase Console
+ * The phone number and SMS code here must have been configured in the Firebase
+ * Console
* (Authentication > Sign In Method > Phone).
*
- * <p>Calling this method a second time will overwrite the previously passed parameters. Only one
+ * <p>
+ * Calling this method a second time will overwrite the previously passed
+ * parameters. Only one
* number can be configured at a given time.
*
* @param appName
@@ -245,13 +243,14 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
/**
* Disable app verification for the running of tests
+ *
* @param appName
* @param disabled
* @param promise
*/
@ReactMethod
public void setAppVerificationDisabledForTesting(
- String appName, boolean disabled, Promise promise) {
+ String appName, boolean disabled, Promise promise) {
Log.d(TAG, "setAppVerificationDisabledForTesting");
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
@@ -413,17 +412,16 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
- OnCompleteListener<Void> listener =
- task -> {
- if (task.isSuccessful()) {
- Log.d(TAG, "sendPasswordResetEmail:onComplete:success");
- promiseNoUser(promise, false);
- } else {
- Exception exception = task.getException();
- Log.e(TAG, "sendPasswordResetEmail:onComplete:failure", exception);
- promiseRejectAuthException(promise, exception);
- }
- };
+ OnCompleteListener<Void> listener = task -> {
+ if (task.isSuccessful()) {
+ Log.d(TAG, "sendPasswordResetEmail:onComplete:success");
+ promiseNoUser(promise, false);
+ } else {
+ Exception exception = task.getException();
+ Log.e(TAG, "sendPasswordResetEmail:onComplete:failure", exception);
+ promiseRejectAuthException(promise, exception);
+ }
+ };
if (actionCodeSettings == null) {
firebaseAuth.sendPasswordResetEmail(email).addOnCompleteListener(getExecutor(), listener);
@@ -448,17 +446,16 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
- OnCompleteListener<Void> listener =
- task -> {
- if (task.isSuccessful()) {
- Log.d(TAG, "sendSignInLinkToEmail:onComplete:success");
- promiseNoUser(promise, false);
- } else {
- Exception exception = task.getException();
- Log.e(TAG, "sendSignInLinkToEmail:onComplete:failure", exception);
- promiseRejectAuthException(promise, exception);
- }
- };
+ OnCompleteListener<Void> listener = task -> {
+ if (task.isSuccessful()) {
+ Log.d(TAG, "sendSignInLinkToEmail:onComplete:success");
+ promiseNoUser(promise, false);
+ } else {
+ Exception exception = task.getException();
+ Log.e(TAG, "sendSignInLinkToEmail:onComplete:failure", exception);
+ promiseRejectAuthException(promise, exception);
+ }
+ };
ActionCodeSettings settings = buildActionCodeSettings(actionCodeSettings);
firebaseAuth
@@ -466,9 +463,11 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
.addOnCompleteListener(getExecutor(), listener);
}
- /* ----------------------
- * .currentUser methods
- * ---------------------- */
+ /*
+ * ----------------------
+ * .currentUser methods
+ * ----------------------
+ */
/**
* delete
@@ -553,17 +552,16 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
promiseNoUser(promise, false);
Log.e(TAG, "sendEmailVerification:failure:noCurrentUser");
} else {
- OnCompleteListener<Void> listener =
- task -> {
- if (task.isSuccessful()) {
- Log.d(TAG, "sendEmailVerification:onComplete:success");
- promiseWithUser(firebaseAuth.getCurrentUser(), promise);
- } else {
- Exception exception = task.getException();
- Log.e(TAG, "sendEmailVerification:onComplete:failure", exception);
- promiseRejectAuthException(promise, exception);
- }
- };
+ OnCompleteListener<Void> listener = task -> {
+ if (task.isSuccessful()) {
+ Log.d(TAG, "sendEmailVerification:onComplete:success");
+ promiseWithUser(firebaseAuth.getCurrentUser(), promise);
+ } else {
+ Exception exception = task.getException();
+ Log.e(TAG, "sendEmailVerification:onComplete:failure", exception);
+ promiseRejectAuthException(promise, exception);
+ }
+ };
if (actionCodeSettings == null) {
user.sendEmailVerification().addOnCompleteListener(getExecutor(), listener);
@@ -592,17 +590,16 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
promiseNoUser(promise, false);
Log.e(TAG, "verifyBeforeUpdateEmail:failure:noCurrentUser");
} else {
- OnCompleteListener<Void> listener =
- task -> {
- if (task.isSuccessful()) {
- Log.d(TAG, "verifyBeforeUpdateEmail:onComplete:success");
- promiseWithUser(firebaseAuth.getCurrentUser(), promise);
- } else {
- Exception exception = task.getException();
- Log.e(TAG, "verifyBeforeUpdateEmail:onComplete:failure", exception);
- promiseRejectAuthException(promise, exception);
- }
- };
+ OnCompleteListener<Void> listener = task -> {
+ if (task.isSuccessful()) {
+ Log.d(TAG, "verifyBeforeUpdateEmail:onComplete:success");
+ promiseWithUser(firebaseAuth.getCurrentUser(), promise);
+ } else {
+ Exception exception = task.getException();
+ Log.e(TAG, "verifyBeforeUpdateEmail:onComplete:failure", exception);
+ promiseRejectAuthException(promise, exception);
+ }
+ };
if (actionCodeSettings == null) {
user.verifyBeforeUpdateEmail(email).addOnCompleteListener(getExecutor(), listener);
@@ -835,85 +832,85 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
// Reset the verification Id
mVerificationId = null;
- PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks =
- 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(
- getExecutor(),
- 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();
-
- Parcel parcel = Parcel.obtain();
- phoneAuthCredential.writeToParcel(parcel, 0);
- parcel.setDataPosition(16); // verificationId
- String verificationId = parcel.readString();
- mVerificationId = verificationId;
- parcel.recycle();
-
- verificationMap.putString("verificationId", 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);
- // 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
- if (!promiseResolved) {
- 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;
- mForceResendingToken = forceResendingToken;
- 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
- }
- };
+ PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks = 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(
+ getExecutor(),
+ 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();
+
+ Parcel parcel = Parcel.obtain();
+ phoneAuthCredential.writeToParcel(parcel, 0);
+ parcel.setDataPosition(16); // verificationId
+ String verificationId = parcel.readString();
+ mVerificationId = verificationId;
+ parcel.recycle();
+
+ verificationMap.putString("verificationId", 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);
+ // 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
+ if (!promiseResolved) {
+ 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;
+ mForceResendingToken = forceResendingToken;
+ 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
+ }
+ };
if (activity != null) {
if (forceResend && mForceResendingToken != null) {
@@ -990,78 +987,78 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
// Reset the credential
mCredential = null;
- PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks =
- new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
-
- @Override
- public void onVerificationCompleted(final PhoneAuthCredential phoneAuthCredential) {
- // Cache the credential to protect against null verificationId
- mCredential = phoneAuthCredential;
-
- Log.d(TAG, "verifyPhoneNumber:verification:onVerificationCompleted");
- WritableMap state = Arguments.createMap();
-
- Parcel parcel = Parcel.obtain();
- phoneAuthCredential.writeToParcel(parcel, 0);
-
- // verificationId
- parcel.setDataPosition(16);
- String verificationId = parcel.readString();
-
- // sms Code
- parcel.setDataPosition(parcel.dataPosition() + 8);
- String code = parcel.readString();
-
- state.putString("code", code);
- state.putString("verificationId", verificationId);
- parcel.recycle();
- sendPhoneStateEvent(appName, requestKey, "onVerificationComplete", state);
- }
-
- @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, "verifyPhoneNumber:verification:onVerificationFailed");
- WritableMap state = Arguments.createMap();
- state.putMap("error", getJSError(e));
- sendPhoneStateEvent(appName, requestKey, "onVerificationFailed", state);
- }
-
- @Override
- public void onCodeSent(
- String verificationId, PhoneAuthProvider.ForceResendingToken forceResendingToken) {
- Log.d(TAG, "verifyPhoneNumber:verification:onCodeSent");
- mForceResendingToken = forceResendingToken;
- WritableMap state = Arguments.createMap();
- state.putString("verificationId", verificationId);
-
- // todo forceResendingToken - it's actually just an empty class ... no actual token >.>
- // Parcel parcel = Parcel.obtain();
- // forceResendingToken.writeToParcel(parcel, 0);
- //
- // // verificationId
- // parcel.setDataPosition(0);
- // int int1 = parcel.readInt();
- // String token = parcel.readString();
- //
- // state.putString("refreshToken", token);
- // parcel.recycle();
-
- state.putString("verificationId", verificationId);
- sendPhoneStateEvent(appName, requestKey, "onCodeSent", state);
- }
-
- @Override
- public void onCodeAutoRetrievalTimeOut(String verificationId) {
- super.onCodeAutoRetrievalTimeOut(verificationId);
- Log.d(TAG, "verifyPhoneNumber:verification:onCodeAutoRetrievalTimeOut");
- WritableMap state = Arguments.createMap();
- state.putString("verificationId", verificationId);
- sendPhoneStateEvent(appName, requestKey, "onCodeAutoRetrievalTimeout", state);
- }
- };
+ PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
+
+ @Override
+ public void onVerificationCompleted(final PhoneAuthCredential phoneAuthCredential) {
+ // Cache the credential to protect against null verificationId
+ mCredential = phoneAuthCredential;
+
+ Log.d(TAG, "verifyPhoneNumber:verification:onVerificationCompleted");
+ WritableMap state = Arguments.createMap();
+
+ Parcel parcel = Parcel.obtain();
+ phoneAuthCredential.writeToParcel(parcel, 0);
+
+ // verificationId
+ parcel.setDataPosition(16);
+ String verificationId = parcel.readString();
+
+ // sms Code
+ parcel.setDataPosition(parcel.dataPosition() + 8);
+ String code = parcel.readString();
+
+ state.putString("code", code);
+ state.putString("verificationId", verificationId);
+ parcel.recycle();
+ sendPhoneStateEvent(appName, requestKey, "onVerificationComplete", state);
+ }
+
+ @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, "verifyPhoneNumber:verification:onVerificationFailed");
+ WritableMap state = Arguments.createMap();
+ state.putMap("error", getJSError(e));
+ sendPhoneStateEvent(appName, requestKey, "onVerificationFailed", state);
+ }
+
+ @Override
+ public void onCodeSent(
+ String verificationId, PhoneAuthProvider.ForceResendingToken forceResendingToken) {
+ Log.d(TAG, "verifyPhoneNumber:verification:onCodeSent");
+ mForceResendingToken = forceResendingToken;
+ WritableMap state = Arguments.createMap();
+ state.putString("verificationId", verificationId);
+
+ // todo forceResendingToken - it's actually just an empty class ... no actual
+ // token >.>
+ // Parcel parcel = Parcel.obtain();
+ // forceResendingToken.writeToParcel(parcel, 0);
+ //
+ // // verificationId
+ // parcel.setDataPosition(0);
+ // int int1 = parcel.readInt();
+ // String token = parcel.readString();
+ //
+ // state.putString("refreshToken", token);
+ // parcel.recycle();
+
+ state.putString("verificationId", verificationId);
+ sendPhoneStateEvent(appName, requestKey, "onCodeSent", state);
+ }
+
+ @Override
+ public void onCodeAutoRetrievalTimeOut(String verificationId) {
+ super.onCodeAutoRetrievalTimeOut(verificationId);
+ Log.d(TAG, "verifyPhoneNumber:verification:onCodeAutoRetrievalTimeOut");
+ WritableMap state = Arguments.createMap();
+ state.putString("verificationId", verificationId);
+ sendPhoneStateEvent(appName, requestKey, "onCodeAutoRetrievalTimeout", state);
+ }
+ };
if (activity != null) {
if (forceResend && mForceResendingToken != null) {
@@ -1229,8 +1226,16 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
promiseWithAuthResult(task.getResult(), promise);
} else {
Exception exception = task.getException();
- Log.e(TAG, "link:onComplete:failure", exception);
- promiseRejectAuthException(promise, exception);
+
+ if (exception instanceof FirebaseAuthUserCollisionException) {
+ FirebaseAuthUserCollisionException authUserCollisionException = (FirebaseAuthUserCollisionException) exception;
+ AuthCredential updatedCredential = authUserCollisionException.getUpdatedCredential();
+ Log.e(TAG, "link:onComplete:collisionFailure", exception);
+ promiseRejectLinkAuthException(promise, exception, updatedCredential);
+ } else {
+ Log.e(TAG, "link:onComplete:failure", exception);
+ promiseRejectAuthException(promise, exception);
+ }
}
});
} else {
@@ -1305,40 +1310,46 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
/** Returns an instance of AuthCredential for the specified provider */
private AuthCredential getCredentialForProvider(
String provider, String authToken, String authSecret) {
- switch (provider) {
- case "facebook.com":
- return FacebookAuthProvider.getCredential(authToken);
- case "google.com":
- return GoogleAuthProvider.getCredential(authToken, authSecret);
- case "twitter.com":
- return TwitterAuthProvider.getCredential(authToken, authSecret);
- case "github.com":
- return GithubAuthProvider.getCredential(authToken);
- case "apple.com":
- return OAuthProvider.newCredentialBuilder(provider)
- .setIdTokenWithRawNonce(authToken, authSecret)
- .build();
- case "oauth":
- return OAuthProvider.getCredential(provider, authToken, authSecret);
- case "phone":
- return getPhoneAuthCredential(authToken, authSecret);
- case "password":
- // authToken = email
- // authSecret = password
- return EmailAuthProvider.getCredential(authToken, authSecret);
- case "emailLink":
- // authToken = email
- // authSecret = link
- return EmailAuthProvider.getCredentialWithLink(authToken, authSecret);
- default:
- return null;
+ if (credentials.containsKey(authToken) && credentials.get(authToken) != null) {
+ return credentials.get(authToken);
+ } else {
+ switch (provider) {
+ case "facebook.com":
+ return FacebookAuthProvider.getCredential(authToken);
+ case "google.com":
+ return GoogleAuthProvider.getCredential(authToken, authSecret);
+ case "twitter.com":
+ return TwitterAuthProvider.getCredential(authToken, authSecret);
+ case "github.com":
+ return GithubAuthProvider.getCredential(authToken);
+ case "apple.com":
+ return OAuthProvider.newCredentialBuilder(provider)
+ .setIdTokenWithRawNonce(authToken, authSecret)
+ .build();
+ case "oauth":
+ return OAuthProvider.getCredential(provider, authToken, authSecret);
+ case "phone":
+ return getPhoneAuthCredential(authToken, authSecret);
+ case "password":
+ // authToken = email
+ // authSecret = password
+ return EmailAuthProvider.getCredential(authToken, authSecret);
+ case "emailLink":
+ // authToken = email
+ // authSecret = link
+ return EmailAuthProvider.getCredentialWithLink(authToken, authSecret);
+ default:
+ return null;
+ }
}
}
/** Returns an instance of PhoneAuthCredential, potentially cached */
private PhoneAuthCredential getPhoneAuthCredential(String authToken, String authSecret) {
- // If the phone number is auto-verified quickly, then the verificationId can be null
- // We cached the credential as part of the verifyPhoneNumber request to be re-used here
+ // If the phone number is auto-verified quickly, then the verificationId can be
+ // null
+ // We cached the credential as part of the verifyPhoneNumber request to be
+ // re-used here
// if possible
if (authToken == null && mCredential != null) {
PhoneAuthCredential credential = mCredential;
@@ -1472,8 +1483,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
task -> {
if (task.isSuccessful()) {
Log.d(TAG, "fetchProvidersForEmail:onComplete:success");
- List<String> providers =
- Objects.requireNonNull(task.getResult()).getSignInMethods();
+ List<String> providers = Objects.requireNonNull(task.getResult()).getSignInMethods();
WritableArray array = Arguments.createArray();
if (providers != null) {
@@ -1566,9 +1576,11 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
firebaseAuth.useEmulator(host, port);
}
- /* ------------------
+ /*
+ * ------------------
* INTERNAL HELPERS
- * ---------------- */
+ * ----------------
+ */
/**
* Resolves or rejects an auth method promise without a user (user was missing)
@@ -1652,6 +1664,33 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
rejectPromiseWithCodeAndMessage(promise, error.getString("code"), error.getString("message"));
}
+ /**
+ * promiseRejectLinkAuthException
+ *
+ * @param promise
+ * @param exception
+ * @param authCredential
+ */
+ private void promiseRejectLinkAuthException(Promise promise, Exception exception, AuthCredential authCredential) {
+ WritableMap error = getJSError(exception);
+ String authHashCode = String.valueOf(authCredential.hashCode());
+
+ WritableMap authCredentialsMap = Arguments.createMap();
+ authCredentialsMap.putString("providerId", authCredential.getProvider());
+ authCredentialsMap.putString("token", authHashCode);
+ authCredentialsMap.putString("secret", null);
+
+ // Temporarily store the non-serializable credential for later
+ credentials.put(authHashCode, authCredential);
+
+ WritableMap userInfoMap = Arguments.createMap();
+ userInfoMap.putString("code", error.getString("code"));
+ userInfoMap.putString("message", error.getString("message"));
+ userInfoMap.putMap("authCredential", authCredentialsMap);
+
+ promise.reject(error.getString("code"), error.getString("message"), userInfoMap);
+ }
+
/**
* getJSError
*
@@ -1689,18 +1728,15 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
message = "The password is invalid or the user does not have a password.";
break;
case "USER_MISMATCH":
- message =
- "The supplied credentials do not correspond to the previously signed in user.";
+ message = "The supplied credentials do not correspond to the previously signed in user.";
break;
case "REQUIRES_RECENT_LOGIN":
- message =
- "This operation is sensitive and requires recent authentication. Log in again"
- + " before retrying this request.";
+ message = "This operation is sensitive and requires recent authentication. Log in again"
+ + " before retrying this request.";
break;
case "ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL":
- message =
- "An account already exists with the same email address but different sign-in"
- + " credentials. Sign in using a provider associated with this email address.";
+ message = "An account already exists with the same email address but different sign-in"
+ + " credentials. Sign in using a provider associated with this email address.";
break;
case "EMAIL_ALREADY_IN_USE":
message = "The email address is already in use by another account.";
@@ -1715,9 +1751,8 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
message = "The user\'s credential is no longer valid. The user must sign in again.";
break;
case "USER_NOT_FOUND":
- message =
- "There is no user record corresponding to this identifier. The user may have been"
- + " deleted.";
+ message = "There is no user record corresponding to this identifier. The user may have been"
+ + " deleted.";
break;
case "INVALID_USER_TOKEN":
message = "The user\'s credential is no longer valid. The user must sign in again.";
@@ -1756,7 +1791,8 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
}
/**
- * Converts a List of UserInfo instances into the correct format to match the web sdk
+ * Converts a List of UserInfo instances into the correct format to match the
+ * web sdk
*
* @param providerData List<UserInfo> user.getProviderData()
* @return WritableArray array
@@ -1782,8 +1818,10 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
}
final String phoneNumber = userInfo.getPhoneNumber();
- // The Android SDK is missing the phone number property for the phone provider when the
- // user first signs up using their phone number. Use the phone number from the user
+ // The Android SDK is missing the phone number property for the phone provider
+ // when the
+ // user first signs up using their phone number. Use the phone number from the
+ // user
// object instead
if (PhoneAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())
&& (userInfo.getPhoneNumber() == null || "".equals(userInfo.getPhoneNumber()))) {
@@ -1794,7 +1832,8 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
userInfoMap.putNull("phoneNumber");
}
- // The Android SDK is missing the email property for the email provider, so we use UID
+ // The Android SDK is missing the email property for the email provider, so we
+ // use UID
// instead
if (EmailAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())
&& (userInfo.getEmail() == null || "".equals(userInfo.getEmail()))) {
@@ -1896,15 +1935,12 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
if (actionCodeSettings.hasKey("android")) {
ReadableMap android = actionCodeSettings.getMap("android");
- boolean installApp =
- Objects.requireNonNull(android).hasKey("installApp") && android.getBoolean("installApp");
- String minimumVersion =
- android.hasKey("minimumVersion") ? android.getString("minimumVersion") : null;
+ boolean installApp = Objects.requireNonNull(android).hasKey("installApp") && android.getBoolean("installApp");
+ String minimumVersion = android.hasKey("minimumVersion") ? android.getString("minimumVersion") : null;
String packageName = android.getString("packageName");
- builder =
- builder.setAndroidPackageName(
- Objects.requireNonNull(packageName), installApp, minimumVersion);
+ builder = builder.setAndroidPackageName(
+ Objects.requireNonNull(packageName), installApp, minimumVersion);
}
if (actionCodeSettings.hasKey("iOS")) {
@@ -1929,8 +1965,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
eventBody.putString("requestKey", requestKey);
eventBody.putString("type", type);
eventBody.putMap("state", state);
- ReactNativeFirebaseEvent event =
- new ReactNativeFirebaseEvent("phone_auth_state_changed", eventBody, appName);
+ ReactNativeFirebaseEvent event = new ReactNativeFirebaseEvent("phone_auth_state_changed", eventBody, appName);
emitter.sendEvent(event);
}
@@ -1954,7 +1989,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(instance);
FirebaseUser user = firebaseAuth.getCurrentUser();
- //noinspection ConstantConditions
+ // noinspection ConstantConditions
appLanguage.put(appName, firebaseAuth.getLanguageCode());
if (user != null) {
Quite alright - I can still apply it then use tools to ignore spacing etc :-), thanks for posting this
@mikehardy There is also my patch that I've posted earlier here, been running it ever since in production with 0 issues. The one that @kristfal posted appears to be more complete, unfortunately I can't review right now
Hello 👋, to help manage issues we automatically close stale issues. This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?
This issue will be closed in 15 days if no further activity occurs. Thank you for your contributions.
Hello 👋, to help manage issues we automatically close stale issues.
This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?
This issue will be closed in 15 days if no further activity occurs.
Thank you for your contributions.
@mikehardy I have just checked the source code for the latest version of auth and I think it is still an issue. (cant check because I cant use use_frameworks
) Yes it was somewhat made better by removing an attempt to upgrade anonymous users but it still will not forward updated credentials authCredentials
in promise reject to JS side on Android which is done in iOS:
rejectPromiseWithUserInfo:reject
userInfo:(NSMutableDictionary *)@{
@"code" : [jsError valueForKey:@"code"],
@"message" : [jsError valueForKey:@"message"],
@"nativeErrorMessage" : [jsError valueForKey:@"nativeErrorMessage"],
@"authCredential" : [jsError valueForKey:@"authCredential"],
@"resolver" : [jsError valueForKey:@"resolver"]
}];
and
@"code" : code,
@"message" : message,
@"nativeErrorMessage" : nativeErrorMessage,
@"authCredential" : authCredentialDict != nil ? (id)authCredentialDict : [NSNull null],
@"resolver" : resolverDict != nil ? (id)resolverDict : [NSNull null]
};
Hello 👋, to help manage issues we automatically close stale issues.
This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?
This issue will be closed in 15 days if no further activity occurs.
Thank you for your contributions.
is this resolved? I'm still not getting the credentials on the error object for Android?
This issue is still not resolved. You still can't upgrade anonymous users using phone auth on Android. Still same issue: iOS is passing authCredential
back after calling linkWithCredential
while Android does not. @faljabi you can use patch-package for latest react native firebase version 18.1.0:
diff --git a/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
index b6406f1..cc2f6e1 100644
--- a/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
+++ b/node_modules/@react-native-firebase/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java
@@ -43,6 +43,7 @@ import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
+import com.google.firebase.auth.FirebaseAuthUserCollisionException;
import com.google.firebase.auth.FirebaseAuthMultiFactorException;
import com.google.firebase.auth.FirebaseAuthProvider;
import com.google.firebase.auth.FirebaseAuthSettings;
@@ -103,6 +104,10 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
private final HashMap<String, MultiFactorResolver> mCachedResolvers = new HashMap<>();
private final HashMap<String, MultiFactorSession> mMultiFactorSessions = new HashMap<>();
+ // patching anonumous phone auth linkWithCredentials
+ // https://github.com/invertase/react-native-firebase/issues/4911
+ private HashMap<String, AuthCredential> credentials = new HashMap<>();
+
ReactNativeFirebaseAuthModule(ReactApplicationContext reactContext) {
super(reactContext, TAG);
}
@@ -1501,8 +1506,15 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
promiseWithAuthResult(task.getResult(), promise);
} else {
Exception exception = task.getException();
- Log.e(TAG, "link:onComplete:failure", exception);
- promiseRejectAuthException(promise, exception);
+ if (exception instanceof FirebaseAuthUserCollisionException) {
+ FirebaseAuthUserCollisionException authUserCollisionException = (FirebaseAuthUserCollisionException) exception;
+ AuthCredential updatedCredential = authUserCollisionException.getUpdatedCredential();
+ Log.e(TAG, "link:onComplete:collisionFailure", exception);
+ promiseRejectLinkAuthException(promise, exception, updatedCredential);
+ } else {
+ Log.e(TAG, "link:onComplete:failure", exception);
+ promiseRejectAuthException(promise, exception);
+ }
}
});
} else {
@@ -1580,34 +1592,37 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
if (provider.startsWith("oidc.")) {
return OAuthProvider.newCredentialBuilder(provider).setIdToken(authToken).build();
}
-
- switch (provider) {
- case "facebook.com":
- return FacebookAuthProvider.getCredential(authToken);
- case "google.com":
- return GoogleAuthProvider.getCredential(authToken, authSecret);
- case "twitter.com":
- return TwitterAuthProvider.getCredential(authToken, authSecret);
- case "github.com":
- return GithubAuthProvider.getCredential(authToken);
- case "apple.com":
- return OAuthProvider.newCredentialBuilder(provider)
- .setIdTokenWithRawNonce(authToken, authSecret)
- .build();
- case "oauth":
- return OAuthProvider.getCredential(provider, authToken, authSecret);
- case "phone":
- return getPhoneAuthCredential(authToken, authSecret);
- case "password":
- // authToken = email
- // authSecret = password
- return EmailAuthProvider.getCredential(authToken, authSecret);
- case "emailLink":
- // authToken = email
- // authSecret = link
- return EmailAuthProvider.getCredentialWithLink(authToken, authSecret);
- default:
- return null;
+ if (credentials.containsKey(authToken) && credentials.get(authToken) != null) {
+ return credentials.get(authToken);
+ } else {
+ switch (provider) {
+ case "facebook.com":
+ return FacebookAuthProvider.getCredential(authToken);
+ case "google.com":
+ return GoogleAuthProvider.getCredential(authToken, authSecret);
+ case "twitter.com":
+ return TwitterAuthProvider.getCredential(authToken, authSecret);
+ case "github.com":
+ return GithubAuthProvider.getCredential(authToken);
+ case "apple.com":
+ return OAuthProvider.newCredentialBuilder(provider)
+ .setIdTokenWithRawNonce(authToken, authSecret)
+ .build();
+ case "oauth":
+ return OAuthProvider.getCredential(provider, authToken, authSecret);
+ case "phone":
+ return getPhoneAuthCredential(authToken, authSecret);
+ case "password":
+ // authToken = email
+ // authSecret = password
+ return EmailAuthProvider.getCredential(authToken, authSecret);
+ case "emailLink":
+ // authToken = email
+ // authSecret = link
+ return EmailAuthProvider.getCredentialWithLink(authToken, authSecret);
+ default:
+ return null;
+ }
}
}
@@ -1939,6 +1954,33 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
promise, error.getString("code"), error.getString("message"), resolverAsMap);
}
+ /**
+ * promiseRejectLinkAuthException
+ *
+ * @param promise
+ * @param exception
+ * @param authCredential
+ */
+ private void promiseRejectLinkAuthException(Promise promise, Exception exception, AuthCredential authCredential) {
+ WritableMap error = getJSError(exception);
+ String authHashCode = String.valueOf(authCredential.hashCode());
+
+ WritableMap authCredentialsMap = Arguments.createMap();
+ authCredentialsMap.putString("providerId", authCredential.getProvider());
+ authCredentialsMap.putString("token", authHashCode);
+ authCredentialsMap.putString("secret", null);
+
+ // Temporarily store the non-serializable credential for later
+ credentials.put(authHashCode, authCredential);
+
+ WritableMap userInfoMap = Arguments.createMap();
+ userInfoMap.putString("code", error.getString("code"));
+ userInfoMap.putString("message", error.getString("message"));
+ userInfoMap.putMap("authCredential", authCredentialsMap);
+
+ promise.reject(error.getString("code"), error.getString("message"), userInfoMap);
+ }
+
/**
* getJSError
*
This is still an issue in April 2024. Shouldn't this just be a simple merge of the above patch @mikehardy ?
@joaqo reasonable PRs are merged if you would like to propose one - I haven't looked at this closely in a while but I try to merge PRs as a high priority with any time I have if you propose one
Tested @Shaninnik's patch and it works perfectly. Created this PR so other people can have it in the future as well. Added credit to @Shaninnik in the commit message, thanks for the great patch! @mikehardy I think its ready to merge.
Sorry this closed stale, thanks very much to @joaqo for carrying through the excellent detective + fixing work from @Shaninnik @kristfal and all the others collaborating here. This should now close for real when #7793 merges and releases
@joaqo thank you for your time creating this PR!
Awesome, thank you for the quick merge! 👏
Issue
Application with email/password, and Facebook login.
On Android, create email/password account, name it ABC. And link it with Facebook account. Success. Expected. On Android, create another email/password account, name it XYZ. And try to link it with the same Facebook account. Expecting an error "credential-already-in-use", but instead, "auth" proceed login process and retrieved ABC's information.
Wipe out ABC and XYZ info on firebase console.
On IOS, create email/password account, name it ABC. And link it with Facebook account. Success. Expected. On IOS, create another email/password account, name it XYZ. And try to link it with the same Facebook account. Got "credential-already-in-use", as expected.
GIF, Android, not as expected.
Screen recording, IOS, expected.
https://user-images.githubusercontent.com/5864358/108017897-1c1fba00-6fe4-11eb-989e-1a7060ed3bdd.mov
Code used to link:
Project Files
Javascript
Click To Expand
#### `package.json`: ```json "dependencies": { "@invertase/react-native-apple-authentication": "^2.1.0", "@react-native-community/async-storage": "^1.12.1", "@react-native-community/google-signin": "^5.0.0", "@react-native-community/masked-view": "^0.1.10", "@react-native-firebase/app": "^10.6.4", "@react-native-firebase/auth": "^10.6.4", "@react-navigation/bottom-tabs": "^5.11.7", "@react-navigation/native": "^5.9.2", "@react-navigation/stack": "^5.14.2", "deepmerge": "^4.2.2", "react": "16.13.1", "react-native": "0.63.4", "react-native-animatable": "^1.3.3", "react-native-fbsdk": "^3.0.0", "react-native-gesture-handler": "^1.9.0", "react-native-image-crop-picker": "^0.35.3", "react-native-onboarding-swiper": "^1.1.4", "react-native-paper": "^4.7.1", "react-native-reanimated": "^1.13.2", "react-native-safe-area-context": "^3.1.9", "react-native-screens": "^2.17.1", "react-native-step-indicator": "^1.0.3", "react-native-vector-icons": "^8.0.0", "react-redux": "^7.2.2", "react-router-redux": "^4.0.8", "reanimated-bottom-sheet": "^1.0.0-alpha.22", "redux": "^4.0.5", "redux-logger": "^3.0.6", "redux-observable": "^1.2.0", "redux-thunk": "^2.3.0", "rxjs": "^6.6.3", "validator": "^13.5.2" },``` #### `firebase.json` for react-native-firebase v6: ```json # N/A ```
iOS
Click To Expand
#### `ios/Podfile`: - [ ] I'm not using Pods - [x] I'm using Pods and my Podfile looks like: ```ruby require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' platform :ios, '10.0' target 'myapp_name' do config = use_native_modules! pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons' use_react_native!(:path => config["reactNativePath"]) pod 'GoogleSignIn', '~> 5.0.2' target 'myapp_nameTests' do inherit! :complete # Pods for testing end # Enables Flipper. # # Note that if you have use_frameworks! enabled, Flipper will not work and # you should disable these next few lines. # use_flipper! # post_install do |installer| # flipper_post_install(installer) # end end target 'myapp_name-tvOS' do # Pods for myapp_name-tvOS target 'myapp_name-tvOSTests' do inherit! :search_paths # Pods for testing end end ``` #### `AppDelegate.m`: ```objc #import "AppDelegate.h" #import
#import
#import
#import
#import
#import
#import
#import
#ifdef FB_SONARKIT_ENABLED
#import
#import
#import
#import
#import
#import
static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([FIRApp defaultApp] == nil) {
[FIRApp configure];
}
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"myapp_name"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
[[FBSDKApplicationDelegate sharedInstance] application:application
didFinishLaunchingWithOptions:launchOptions];
return YES;
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(nonnull NSDictionary *)options
{
[[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
options:options] || [RNGoogleSignin application:application openURL:url options:options];
return YES;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
@end
```
Android
Click To Expand
#### Have you converted to AndroidX? - [ ] my application is an AndroidX application? - [ ] I am using `android/gradle.settings` `jetifier=true` for Android compatibility? - [ ] I am using the NPM package `jetifier` for react-native compatibility? #### `android/build.gradle`: ```groovy // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext { buildToolsVersion = "29.0.2" minSdkVersion = 16 compileSdkVersion = 29 targetSdkVersion = 29 googlePlayServicesAuthVersion = "16.0.1" } repositories { google() jcenter() } dependencies { classpath("com.android.tools.build:gradle:4.0.1") classpath("com.google.gms:google-services:4.3.5") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { mavenLocal() mavenCentral() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url("$rootDir/../node_modules/react-native/android") } maven { // Android JSC is installed from npm url("$rootDir/../node_modules/jsc-android/dist") } google() jcenter() maven { url 'https://maven.google.com' } maven { url 'https://www.jitpack.io' } } } ``` #### `android/app/build.gradle`: ```groovy apply plugin: "com.android.application" import com.android.build.OutputFile /** * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * and bundleReleaseJsAndAssets). * These basically call `react-native bundle` with the correct arguments during the Android build * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * * // the entry file for bundle generation. If none specified and * // "index.android.js" exists, it will be used. Otherwise "index.js" is * // default. Can be overridden with ENTRY_FILE environment variable. * entryFile: "index.android.js", * * // https://reactnative.dev/docs/performance#enable-the-ram-format * bundleCommand: "ram-bundle", * * // whether to bundle JS and assets in debug mode * bundleInDebug: false, * * // whether to bundle JS and assets in release mode * bundleInRelease: true, * * // whether to bundle JS and assets in another build variant (if configured). * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants * // The configuration property can be in the following formats * // 'bundleIn${productFlavor}${buildType}' * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, * * // whether to disable dev mode in custom build variants (by default only disabled in release) * // for example: to disable dev mode in the staging build type (if configured) * devDisabledInStaging: true, * // The configuration property can be in the following formats * // 'devDisabledIn${productFlavor}${buildType}' * // 'devDisabledIn${buildType}' * * // the root of your project, i.e. where "package.json" lives * root: "../../", * * // where to put the JS bundle asset in debug mode * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", * * // where to put the JS bundle asset in release mode * jsBundleDirRelease: "$buildDir/intermediates/assets/release", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in debug mode * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in release mode * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", * * // by default the gradle tasks are skipped if none of the JS files or assets change; this means * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. * inputExcludes: ["android/**", "ios/**"], * * // override which node gets called and with what additional arguments * nodeExecutableAndArgs: ["node"], * * // supply additional arguments to the packager * extraPackagerArgs: [] * ] */ project.ext.react = [ enableHermes: false, // clean and rebuild if changing ] apply from: "../../node_modules/react-native/react.gradle" /** * Set this to true to create two separate APKs instead of one: * - An APK that only works on ARM devices * - An APK that only works on x86 devices * The advantage is the size of the APK is reduced by about 4MB. * Upload all the APKs to the Play Store and people will download * the correct one based on the CPU architecture of their device. */ def enableSeparateBuildPerCPUArchitecture = false /** * Run Proguard to shrink the Java bytecode in release builds. */ def enableProguardInReleaseBuilds = false /** * The preferred build flavor of JavaScriptCore. * * For example, to use the international variant, you can use: * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ def jscFlavor = 'org.webkit:android-jsc:+' /** * Whether to enable the Hermes VM. * * This should be set on project.ext.react and mirrored here. If it is not set * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode * and the benefits of using Hermes will therefore be sharply reduced. */ def enableHermes = project.ext.react.get("enableHermes", false); android { compileSdkVersion rootProject.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "com.myapp_name" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" multiDexEnabled true vectorDrawables.useSupportLibrary = true } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } } signingConfigs { debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } buildTypes { debug { signingConfig signingConfigs.debug } release { // Caution! In production, you need to generate your own keystore file. // see https://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode } } } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules // Import the Firebase BoM implementation platform("com.google.firebase:firebase-bom:26.4.0") implementation 'com.facebook.android:facebook-android-sdk:[4,5)' implementation 'com.android.support:multidex:1.0.3' // Add the dependencies for the desired Firebase products // https://firebase.google.com/docs/android/setup#available-libraries implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { exclude group:'com.facebook.fbjni' } debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' exclude group:'com.squareup.okhttp3', module:'okhttp' } debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' } if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") releaseImplementation files(hermesPath + "hermes-release.aar") } else { implementation jscFlavor } } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { from configurations.compile into 'libs' } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) project.ext.vectoricons = [ iconFontNames: [ 'MaterialCommunityIcons.ttf', 'MaterialIcons.ttf' ] ] apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" apply plugin: "com.google.gms.google-services" // <--- this should be the last line ``` #### `android/settings.gradle`: ```groovy rootProject.name = 'myapp_name' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' ``` #### `MainApplication.java`: ```java package com.myapp_name; import android.app.Application; import android.content.Context; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.soloader.SoLoader; import java.lang.reflect.InvocationTargetException; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class> aClass = Class.forName("com.myapp_name.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
```
#### `AndroidManifest.xml`:
```xml
```
Environment
Click To Expand
**`react-native info` output:** Android test was performed on both Mac and Windows. ``` System: OS: macOS 11.2 CPU: (4) x64 Intel(R) Core(TM) i5-5350U CPU @ 1.80GHz Memory: 1.67 GB / 8.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 15.8.0 - /usr/local/bin/node Yarn: 1.22.10 - /usr/local/bin/yarn npm: 7.5.0 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.10.1 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: iOS 14.4, DriverKit 20.2, macOS 11.1, tvOS 14.3, watchOS 7.2 Android SDK: Not Found IDEs: Android Studio: Not Found Xcode: 12.4/12D4e - /usr/bin/xcodebuild Languages: Java: Not Found Python: 2.7.16 - /usr/bin/python npmPackages: @react-native-community/cli: Not Found react: 16.13.1 => 16.13.1 react-native: 0.63.4 => 0.63.4 react-native-macos: Not Found npmGlobalPackages: *react-native*: Not Found ``` ``` info Fetching system and libraries information... System: OS: Windows 10 10.0.19042 CPU: (4) x64 Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz Memory: 708.10 MB / 7.85 GB Binaries: Node: 12.13.1 - C:\Program Files\nodejs\node.EXE Yarn: 1.21.1 - ~\AppData\Roaming\npm\yarn.CMD npm: 6.14.11 - C:\Program Files\nodejs\npm.CMD Watchman: Not Found SDKs: Android SDK: API Levels: 29, 30 Build Tools: 28.0.3, 29.0.2, 30.0.3 System Images: android-30 | Google APIs Intel x86 Atom Android NDK: Not Found Windows SDK: AllowAllTrustedApps: Disabled Versions: 10.0.15063.0 IDEs: Android Studio: Version 4.1.0.0 AI-201.8743.12.41.7042882 Visual Studio: Not Found Languages: Java: Not Found Python: 3.9.0 - /c/cygwin64/bin/python npmPackages: @react-native-community/cli: Not Found react: 16.13.1 => 16.13.1 react-native: 0.63.4 => 0.63.4 react-native-windows: Not Found npmGlobalPackages: *react-native*: Not Found ``` - **Platform that you're experiencing the issue on**: - [ ] iOS - [X] Android - [ ] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **`react-native-firebase` version you're using that has this issue:** - "^10.6.4" (see package.json) - **`Firebase` module(s) you're using that has the issue:** - "^10.6.4" (see package.json) - **Are you using `TypeScript`?** - yes, "^3.8.3"
React Native Firebase
andInvertase
on Twitter for updates on the library.