diff --git a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.java b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.java index 353a4bf4ad..91e4e41e49 100644 --- a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.java +++ b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.java @@ -54,6 +54,15 @@ public static void rejectPromiseWithCodeAndMessage( promise.reject(code, message, userInfoMap); } + public static void rejectPromiseWithMap( + Promise promise, String code, String message, ReadableMap map) { + WritableMap userInfoMap = Arguments.createMap(); + userInfoMap.putString("code", code); + userInfoMap.putString("message", message); + userInfoMap.merge(map); + promise.reject(code, message, userInfoMap); + } + public static void rejectPromiseWithCodeAndMessage(Promise promise, String code, String message) { WritableMap userInfoMap = Arguments.createMap(); userInfoMap.putString("code", code); diff --git a/packages/app/lib/index.d.ts b/packages/app/lib/index.d.ts index 26913309c2..b9553ad9ab 100644 --- a/packages/app/lib/index.d.ts +++ b/packages/app/lib/index.d.ts @@ -42,6 +42,11 @@ export namespace ReactNativeFirebase { */ readonly message: string; + /** + * The email address of the user's account used in the operation that triggered the error, if applicable + */ + readonly email?: string; + /** * The firebase module namespace that this error originated from, e.g. 'analytics' */ diff --git a/packages/app/lib/internal/NativeFirebaseError.js b/packages/app/lib/internal/NativeFirebaseError.js index b458e51739..da465f56a2 100644 --- a/packages/app/lib/internal/NativeFirebaseError.js +++ b/packages/app/lib/internal/NativeFirebaseError.js @@ -39,6 +39,15 @@ export default class NativeFirebaseError extends Error { value: `[${this.code}] ${userInfo.message || nativeError.message}`, }); + if (typeof userInfo === 'object' && userInfo !== null) { + if ('email' in userInfo) { + Object.defineProperty(this, 'email', { + enumerable: true, + value: userInfo.email, + }); + } + } + Object.defineProperty(this, 'jsStack', { enumerable: false, value: jsStack, diff --git a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java index b6406f19a4..257e72b379 100644 --- a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java +++ b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java @@ -30,6 +30,9 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseException; import com.google.firebase.FirebaseNetworkException; @@ -46,6 +49,7 @@ import com.google.firebase.auth.FirebaseAuthMultiFactorException; import com.google.firebase.auth.FirebaseAuthProvider; import com.google.firebase.auth.FirebaseAuthSettings; +import com.google.firebase.auth.FirebaseAuthUserCollisionException; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.FirebaseUserMetadata; import com.google.firebase.auth.GetTokenResult; @@ -55,6 +59,7 @@ import com.google.firebase.auth.MultiFactorInfo; import com.google.firebase.auth.MultiFactorResolver; import com.google.firebase.auth.MultiFactorSession; +import com.google.firebase.auth.OAuthCredential; import com.google.firebase.auth.OAuthProvider; import com.google.firebase.auth.PhoneAuthCredential; import com.google.firebase.auth.PhoneAuthOptions; @@ -203,7 +208,6 @@ public void addIdTokenListener(final String appName) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); - if (!mIdTokenListeners.containsKey(appName)) { FirebaseAuth.IdTokenListener newIdTokenListener = firebaseAuth1 -> { @@ -862,6 +866,93 @@ private void signInWithCredential( } } + @ReactMethod + public void signInWithProvider( + String appName, String providerId, @Nullable String email, Promise promise) { + OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId); + + if (email != null) { + provider.addCustomParameter("login_hint", email); + } + provider.addCustomParameter("prompt", "select_account"); + + Activity activity = getCurrentActivity(); + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + + OnSuccessListener onSuccess = + new OnSuccessListener() { + @Override + public void onSuccess(AuthResult authResult) { + Log.d(TAG, "signInWithProvider:onComplete:success"); + promiseWithAuthResult(authResult, promise); + } + }; + + OnFailureListener onFailure = + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "signInWithProvider:onComplete:failure", e); + promiseRejectAuthException(promise, e); + } + }; + + Task pendingResultTask = firebaseAuth.getPendingAuthResult(); + if (pendingResultTask != null) { + pendingResultTask.addOnSuccessListener(onSuccess).addOnFailureListener(onFailure); + } else { + firebaseAuth + .startActivityForSignInWithProvider(activity, provider.build()) + .addOnSuccessListener(onSuccess) + .addOnFailureListener(onFailure); + } + } + + @ReactMethod + public void linkWithProvider( + String appName, String providerId, @Nullable String email, Promise promise) { + OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId); + + if (email != null) { + provider.addCustomParameter("login_hint", email); + } + provider.addCustomParameter("prompt", "select_account"); + + Activity activity = getCurrentActivity(); + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + FirebaseUser firebaseUser = firebaseAuth.getCurrentUser(); + + OnSuccessListener onSuccess = + new OnSuccessListener() { + @Override + public void onSuccess(AuthResult authResult) { + Log.d(TAG, "linkWithProvider:onComplete:success"); + promiseWithAuthResult(authResult, promise); + } + }; + + OnFailureListener onFailure = + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "linkInWithProvider:onComplete:failure", e); + promiseRejectAuthException(promise, e); + } + }; + + Task pendingResultTask = firebaseAuth.getPendingAuthResult(); + if (pendingResultTask != null) { + pendingResultTask.addOnSuccessListener(onSuccess).addOnFailureListener(onFailure); + } else { + firebaseUser + .startActivityForLinkWithProvider(activity, provider.build()) + .addOnSuccessListener(onSuccess) + .addOnFailureListener(onFailure); + } + } + /** * signInWithPhoneNumber * @@ -1889,6 +1980,30 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) { WritableMap authResultMap = Arguments.createMap(); WritableMap userMap = firebaseUserToMap(authResult.getUser()); + if (authResult.getCredential() != null) { + if (authResult.getCredential() instanceof OAuthCredential) { + OAuthCredential creds = (OAuthCredential) authResult.getCredential(); + WritableMap credentialMap = Arguments.createMap(); + + credentialMap.putString("providerId", creds.getProvider()); + credentialMap.putString("signInMethod", creds.getSignInMethod()); + + if (creds.getIdToken() != null) { + credentialMap.putString("idToken", creds.getIdToken()); + } + + if (creds.getAccessToken() != null) { + credentialMap.putString("accessToken", creds.getAccessToken()); + } + + if (creds.getSecret() != null) { + credentialMap.putString("secret", creds.getSecret()); + } + + authResultMap.putMap("credential", credentialMap); + } + } + if (authResult.getAdditionalUserInfo() != null) { WritableMap additionalUserInfoMap = Arguments.createMap(); @@ -1929,14 +2044,22 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) { */ private void promiseRejectAuthException(Promise promise, Exception exception) { WritableMap error = getJSError(exception); + final String sessionId = error.getString("sessionId"); final MultiFactorResolver multiFactorResolver = mCachedResolvers.get(sessionId); WritableMap resolverAsMap = Arguments.createMap(); + + WritableMap map = Arguments.createMap(); if (multiFactorResolver != null) { resolverAsMap = resolverToMap(sessionId, multiFactorResolver); + map.putMap("resolver", resolverAsMap); + } + + if (error.getString("email") != null) { + map.putString("email", error.getString("email")); } - rejectPromiseWithCodeAndMessage( - promise, error.getString("code"), error.getString("message"), resolverAsMap); + + rejectPromiseWithMap(promise, error.getString("code"), error.getString("message"), map); } /** @@ -1950,6 +2073,7 @@ private WritableMap getJSError(Exception exception) { String message = exception.getMessage(); String invalidEmail = "The email address is badly formatted."; + System.out.print(exception); try { FirebaseAuthException authException = (FirebaseAuthException) exception; code = authException.getErrorCode(); @@ -2023,6 +2147,10 @@ private WritableMap getJSError(Exception exception) { } } + if (exception instanceof FirebaseAuthUserCollisionException) { + error.putString("email", ((FirebaseAuthUserCollisionException) exception).getEmail()); + } + if (exception instanceof FirebaseAuthMultiFactorException) { final FirebaseAuthMultiFactorException multiFactorException = (FirebaseAuthMultiFactorException) exception; diff --git a/packages/auth/e2e/auth.e2e.js b/packages/auth/e2e/auth.e2e.js index 5c89134e8a..602b62d32c 100644 --- a/packages/auth/e2e/auth.e2e.js +++ b/packages/auth/e2e/auth.e2e.js @@ -912,12 +912,11 @@ describe('auth()', function () { }); describe('signInWithPopup', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().signInWithPopup(); - }).should.throw( - 'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.', - ); + it('should trigger the oauth flow', async function () { + await (async () => { + const provider = new firebase.auth.OAuthProvider('oidc.react.com'); + await firebase.auth().signInWithPopup(provider); + }).should.not.throw(); }); }); @@ -1025,12 +1024,11 @@ describe('auth()', function () { }); describe('signInWithRedirect()', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().signInWithRedirect(); - }).should.throw( - 'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.', - ); + it('should trigger the oauth flow', async function () { + await (async () => { + const provider = new firebase.auth.OAuthProvider('oidc.react.com'); + await firebase.auth().signInWithRedirect(provider); + }).should.not.throw(); }); }); diff --git a/packages/auth/e2e/provider.e2e.js b/packages/auth/e2e/provider.e2e.js index 1475eb5675..36fea1ea29 100644 --- a/packages/auth/e2e/provider.e2e.js +++ b/packages/auth/e2e/provider.e2e.js @@ -149,9 +149,7 @@ describe('auth() -> Providers', function () { describe('OAuthProvider', function () { describe('constructor', function () { it('should throw an unsupported error', function () { - (() => new firebase.auth.OAuthProvider()).should.throw( - '`new OAuthProvider()` is not supported on the native Firebase SDKs.', - ); + (() => new firebase.auth.OAuthProvider('oidc.react.com')).should.not.throw(); }); }); diff --git a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m index 8f410ec6d9..c8d812b116 100644 --- a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m +++ b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m @@ -45,6 +45,10 @@ static NSString *const keyHandleCodeInApp = @"handleCodeInApp"; static NSString *const keyDynamicLinkDomain = @"dynamicLinkDomain"; static NSString *const keyAdditionalUserInfo = @"additionalUserInfo"; +static NSString *const keyCredential = @"credential"; +static NSString *const keyIdToken = @"idToken"; +static NSString *const keyAccessToken = @"accessToken"; +static NSString *const keySecret = @"secret"; static NSString *const AUTH_STATE_CHANGED_EVENT = @"auth_state_changed"; static NSString *const AUTH_ID_TOKEN_CHANGED_EVENT = @"auth_id_token_changed"; static NSString *const PHONE_AUTH_STATE_CHANGED_EVENT = @"phone_auth_state_changed"; @@ -56,6 +60,7 @@ static __strong NSMutableDictionary *credentials; static __strong NSMutableDictionary *cachedResolver; static __strong NSMutableDictionary *cachedSessions; +static __strong NSMutableDictionary *oAuthProviders; @implementation RNFBAuthModule #pragma mark - @@ -77,6 +82,7 @@ - (id)init { credentials = [[NSMutableDictionary alloc] init]; cachedResolver = [[NSMutableDictionary alloc] init]; cachedSessions = [[NSMutableDictionary alloc] init]; + oAuthProviders = [[NSMutableDictionary alloc] init]; }); return self; } @@ -104,6 +110,7 @@ - (void)invalidate { [credentials removeAllObjects]; [cachedResolver removeAllObjects]; [cachedSessions removeAllObjects]; + [oAuthProviders removeAllObjects]; } #pragma mark - @@ -571,6 +578,88 @@ - (void)invalidate { }]; } +RCT_EXPORT_METHOD(signInWithProvider + : (FIRApp *)firebaseApp + : (NSString *)providerID + : (NSString *_Nullable)email + : (RCTPromiseResolveBlock)resolve + : (RCTPromiseRejectBlock)reject) { + FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:providerID]; + [oAuthProviders setValue:provider forKey:providerID]; + + if (email) { + [provider setCustomParameters:@{@"login_hint" : email, @"prompt" : @"select_account"}]; + } else { + [provider setCustomParameters:@{@"prompt" : @"select_account"}]; + } + + [provider getCredentialWithUIDelegate:nil + completion:^(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } + + if (credential) { + [[FIRAuth auth] + signInWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + if (error) { + [self promiseRejectAuthException:reject + error:error]; + } else { + [self promiseWithAuthResult:resolve + rejecter:reject + authResult:authResult + credential:credential]; + } + }]; + } + }]; +} + +RCT_EXPORT_METHOD(linkWithProvider + : (FIRApp *)firebaseApp + : (NSString *)providerID + : (NSString *_Nullable)email + : (RCTPromiseResolveBlock)resolve + : (RCTPromiseRejectBlock)reject) { + FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:providerID]; + [oAuthProviders setValue:provider forKey:providerID]; + + if (email) { + [provider setCustomParameters:@{@"login_hint" : email, @"prompt" : @"select_account"}]; + } else { + [provider setCustomParameters:@{@"prompt" : @"select_account"}]; + } + + [provider getCredentialWithUIDelegate:nil + completion:^(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } + + if (credential) { + [[FIRAuth auth].currentUser + linkWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + if (error) { + [self promiseRejectAuthException:reject + error:error]; + } else { + [self promiseWithAuthResult:resolve + rejecter:reject + authResult:authResult + credential:credential]; + } + }]; + } + }]; +} + RCT_EXPORT_METHOD(confirmPasswordReset : (FIRApp *)firebaseApp : (NSString *)code @@ -1194,7 +1283,8 @@ - (void)promiseRejectAuthException:(RCTPromiseRejectBlock)reject error:(NSError @"message" : [jsError valueForKey:@"message"], @"nativeErrorMessage" : [jsError valueForKey:@"nativeErrorMessage"], @"authCredential" : [jsError valueForKey:@"authCredential"], - @"resolver" : [jsError valueForKey:@"resolver"] + @"resolver" : [jsError valueForKey:@"resolver"], + @"email" : [jsError valueForKey:@"email"] }]; } @@ -1293,12 +1383,15 @@ - (NSDictionary *)getJSError:(NSError *)error { cachedResolver[sessionKey] = resolver; } + NSString *email = [error userInfo][FIRAuthErrorUserInfoEmailKey]; + return @{ @"code" : code, @"message" : message, @"nativeErrorMessage" : nativeErrorMessage, @"authCredential" : authCredentialDict != nil ? (id)authCredentialDict : [NSNull null], - @"resolver" : resolverDict != nil ? (id)resolverDict : [NSNull null] + @"resolver" : resolverDict != nil ? (id)resolverDict : [NSNull null], + @"email" : email != nil ? email : [NSNull null] }; } @@ -1316,6 +1409,13 @@ - (void)promiseWithUser:(RCTPromiseResolveBlock)resolve - (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject authResult:(FIRAuthDataResult *)authResult { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult credential:nil]; +} + +- (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject + authResult:(FIRAuthDataResult *)authResult + credential:(FIRAuthCredential *_Nullable)credential { if (authResult && authResult.user) { NSMutableDictionary *authResultDict = [NSMutableDictionary dictionary]; @@ -1347,6 +1447,35 @@ - (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve [authResultDict setValue:[NSNull null] forKey:keyAdditionalUserInfo]; } + if (credential && ([credential isKindOfClass:[FIROAuthCredential class]])) { + NSMutableDictionary *credentialDict = [NSMutableDictionary dictionary]; + FIROAuthCredential *oAuthCredential = (FIROAuthCredential *)credential; + + [credentialDict setValue:oAuthCredential.provider forKey:keyProviderId]; + + if (oAuthCredential.IDToken) { + [credentialDict setValue:oAuthCredential.IDToken forKey:keyIdToken]; + } else { + [credentialDict setValue:[NSNull null] forKey:keyIdToken]; + } + + if (oAuthCredential.accessToken) { + [credentialDict setValue:oAuthCredential.accessToken forKey:keyAccessToken]; + } else { + [credentialDict setValue:[NSNull null] forKey:keyAccessToken]; + } + + if (oAuthCredential.accessToken) { + [credentialDict setValue:oAuthCredential.secret forKey:keySecret]; + } else { + [credentialDict setValue:[NSNull null] forKey:keySecret]; + } + + [authResultDict setValue:credentialDict forKey:keyCredential]; + } else { + [authResultDict setValue:[NSNull null] forKey:keyCredential]; + } + [authResultDict setValue:[self firebaseUserToDict:authResult.user] forKey:keyUser]; resolve(authResultDict); } else { diff --git a/packages/auth/lib/User.js b/packages/auth/lib/User.js index 8ac80ae49b..98a92a0118 100644 --- a/packages/auth/lib/User.js +++ b/packages/auth/lib/User.js @@ -310,16 +310,14 @@ export default class User { ); } - linkWithPopup() { - throw new Error( - 'firebase.auth.User.linkWithPopup() is unsupported by the native Firebase SDKs.', - ); + linkWithPopup(provider) { + return this._auth.native + .linkWithProvider(provider.providerId, provider.customParameters?.login_hint) + .then(userCredential => this._auth._setUserCredential(userCredential)); } - linkWithRedirect() { - throw new Error( - 'firebase.auth.User.linkWithRedirect() is unsupported by the native Firebase SDKs.', - ); + async linkWithRedirect(provider) { + return this.linkWithPopup(provider); } reauthenticateWithPhoneNumber() { diff --git a/packages/auth/lib/index.d.ts b/packages/auth/lib/index.d.ts index 6d5ff139a1..51eae8f7a7 100644 --- a/packages/auth/lib/index.d.ts +++ b/packages/auth/lib/index.d.ts @@ -109,6 +109,11 @@ export namespace FirebaseAuthTypes { credential: (token: string | null, secret?: string) => AuthCredential; } + export type OAuthProvider = AuthProvider & { + new (providerId: string): OAuthProvider; + setCustomParameters: (customOAuthParameters: object) => void; + }; + /** * Interface that represents an Open ID Connect auth provider. Implemented by other providers. */ @@ -315,7 +320,7 @@ export namespace FirebaseAuthTypes { * firebase.auth.OAuthProvider; * ``` */ - OAuthProvider: AuthProvider; + OAuthProvider: OAuthProvider; /** * Custom Open ID connect auth provider implementation. * @@ -387,6 +392,12 @@ export namespace FirebaseAuthTypes { * Any additional user information assigned to the user. */ additionalUserInfo?: AdditionalUserInfo; + + /** + * The AuthCredential returned from the identity provider. + */ + credential: AuthCredential | null; + /** * Returns the {@link auth.User} interface of this credential. */ @@ -1212,6 +1223,46 @@ export namespace FirebaseAuthTypes { */ linkWithCredential(credential: AuthCredential): Promise; + /** + * Link the user with a 3rd party provider. + * + * #### Example + * + * ```js + * const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com') + * const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider); + * ``` + * + * @error auth/provider-already-linked Thrown if the provider has already been linked to the user. This error is thrown even if this is not the same provider's account that is currently linked to the user. + * @error auth/invalid-credential Thrown if the provider's credential is not valid. This can happen if it has already expired when calling link, or if it used invalid token(s). See the Firebase documentation for your provider, and make sure you pass in the correct parameters to the credential method. + * @error auth/credential-already-in-use Thrown if the account corresponding to the credential already exists among your users, or is already linked to a Firebase User. + * @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users. + * @error auth/operation-not-allowed Thrown if you have not enabled the provider in the Firebase Console. Go to the Firebase Console for your project, in the Auth section and the Sign in Method tab and configure the provider. + * @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError} + * @param provider A created {@link auth.AuthProvider}. + */ + linkWithPopup(provider: AuthProvider): Promise; + + /** + * Link the user with a 3rd party provider. + * + * #### Example + * + * ```js + * const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com') + * const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider); + * ``` + * + * @error auth/provider-already-linked Thrown if the provider has already been linked to the user. This error is thrown even if this is not the same provider's account that is currently linked to the user. + * @error auth/invalid-credential Thrown if the provider's credential is not valid. This can happen if it has already expired when calling link, or if it used invalid token(s). See the Firebase documentation for your provider, and make sure you pass in the correct parameters to the credential method. + * @error auth/credential-already-in-use Thrown if the account corresponding to the credential already exists among your users, or is already linked to a Firebase User. + * @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users. + * @error auth/operation-not-allowed Thrown if you have not enabled the provider in the Firebase Console. Go to the Firebase Console for your project, in the Auth section and the Sign in Method tab and configure the provider. + * @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError} + * @param provider A created {@link auth.AuthProvider}. + */ + linkWithRedirect(provider: Provider): Promise; + /** * Re-authenticate a user with a third-party authentication provider. * @@ -1722,6 +1773,9 @@ export namespace FirebaseAuthTypes { */ signInWithCredential(credential: AuthCredential): Promise; + signInWithPopup(provider: AuthProvider): Promise; + signInWithRedirect(provider: AuthProvider): Promise; + /** * Sends a password reset email to the given email address. * Unlike the web SDK, the email will contain a password reset link rather than a code. diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index fe6af99339..bf4b825aae 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -360,16 +360,14 @@ class FirebaseAuthModule extends FirebaseModule { throw new Error('firebase.auth().setPersistence() is unsupported by the native Firebase SDKs.'); } - signInWithPopup() { - throw new Error( - 'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.', - ); + signInWithPopup(provider) { + return this.native + .signInWithProvider(provider.providerId, provider.customParameters?.login_hint) + .then(userCredential => this._setUserCredential(userCredential)); } - signInWithRedirect() { - throw new Error( - 'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.', - ); + signInWithRedirect(provider) { + return this.signInWithPopup(provider); } // firebase issue - https://github.com/invertase/react-native-firebase/pull/655#issuecomment-349904680 diff --git a/packages/auth/lib/providers/OAuthProvider.js b/packages/auth/lib/providers/OAuthProvider.js index 7ef56c624c..837fbf338a 100644 --- a/packages/auth/lib/providers/OAuthProvider.js +++ b/packages/auth/lib/providers/OAuthProvider.js @@ -18,8 +18,14 @@ const providerId = 'oauth'; export default class OAuthProvider { - constructor() { - throw new Error('`new OAuthProvider()` is not supported on the native Firebase SDKs.'); + constructor(providerId) { + this.providerId = providerId; + } + + customParameters = {}; + + setCustomParameters(customParameters) { + this.customParameters = customParameters; } static get PROVIDER_ID() {