Skip to content

Commit

Permalink
feat(authentication): support actionCodeSettings in `sendEmailVerif…
Browse files Browse the repository at this point in the history
…ication(...)` and `sendPasswordResetEmail(...)` (#657)

* wip

* wip: android

* wip

* format

* fix android

* docs [skip ci]

* fix android
  • Loading branch information
robingenz authored Jul 1, 2024
1 parent 549585d commit 638adac
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 104 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-games-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@capacitor-firebase/authentication': minor
---

feat: support `actionCodeSettings` in `sendEmailVerification(...)` and `sendPasswordResetEmail(...)`
38 changes: 25 additions & 13 deletions packages/authentication/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ const useEmulator = async () => {
* [`linkWithYahoo(...)`](#linkwithyahoo)
* [`reload()`](#reload)
* [`revokeAccessToken(...)`](#revokeaccesstoken)
* [`sendEmailVerification()`](#sendemailverification)
* [`sendEmailVerification(...)`](#sendemailverification)
* [`sendPasswordResetEmail(...)`](#sendpasswordresetemail)
* [`sendSignInLinkToEmail(...)`](#sendsigninlinktoemail)
* [`setLanguageCode(...)`](#setlanguagecode)
Expand Down Expand Up @@ -1004,14 +1004,18 @@ Revokes the given access token. Currently only supports Apple OAuth access token
--------------------


### sendEmailVerification()
### sendEmailVerification(...)

```typescript
sendEmailVerification() => Promise<void>
sendEmailVerification(options?: SendEmailVerificationOptions | undefined) => Promise<void>
```

Sends a verification email to the currently signed in user.

| Param | Type |
| ------------- | ------------------------------------------------------------------------------------- |
| **`options`** | <code><a href="#sendemailverificationoptions">SendEmailVerificationOptions</a></code> |

**Since:** 0.2.2

--------------------
Expand Down Expand Up @@ -1831,19 +1835,11 @@ Remove all listeners for this plugin.
| **`token`** | <code>string</code> | The access token to revoke. | 6.1.0 |


#### SendPasswordResetEmailOptions

| Prop | Type | Since |
| ----------- | ------------------- | ----- |
| **`email`** | <code>string</code> | 0.2.2 |


#### SendSignInLinkToEmailOptions
#### SendEmailVerificationOptions

| Prop | Type | Description | Since |
| ------------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----- |
| **`email`** | <code>string</code> | The user's email address. | 1.1.0 |
| **`actionCodeSettings`** | <code><a href="#actioncodesettings">ActionCodeSettings</a></code> | Structure that contains the required continue/state URL with optional Android and iOS bundle identifiers. | 1.1.0 |
| **`actionCodeSettings`** | <code><a href="#actioncodesettings">ActionCodeSettings</a></code> | Structure that contains the required continue/state URL with optional Android and iOS bundle identifiers. | 6.1.0 |


#### ActionCodeSettings
Expand All @@ -1860,6 +1856,22 @@ bundle identifiers.
| **`dynamicLinkDomain`** | <code>string</code> | When multiple custom dynamic link domains are defined for a project, specify which one to use when the link is to be opened via a specified mobile app (for example, `example.page.link`). |


#### SendPasswordResetEmailOptions

| Prop | Type | Description | Since |
| ------------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----- |
| **`email`** | <code>string</code> | | 0.2.2 |
| **`actionCodeSettings`** | <code><a href="#actioncodesettings">ActionCodeSettings</a></code> | Structure that contains the required continue/state URL with optional Android and iOS bundle identifiers. | 6.1.0 |


#### SendSignInLinkToEmailOptions

| Prop | Type | Description | Since |
| ------------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----- |
| **`email`** | <code>string</code> | The user's email address. | 1.1.0 |
| **`actionCodeSettings`** | <code><a href="#actioncodesettings">ActionCodeSettings</a></code> | Structure that contains the required continue/state URL with optional Android and iOS bundle identifiers. | 1.1.0 |


#### SetLanguageCodeOptions

| Prop | Type | Description | Since |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.capawesome.capacitorjs.plugins.firebase.authentication;

import static io.capawesome.capacitorjs.plugins.firebase.authentication.FirebaseAuthenticationPlugin.ERROR_NO_USER_SIGNED_IN;
import static io.capawesome.capacitorjs.plugins.firebase.authentication.FirebaseAuthenticationPlugin.TAG;

import android.content.Intent;
Expand Down Expand Up @@ -31,6 +32,8 @@
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.SignInWithPhoneNumberOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.FetchSignInMethodsForEmailOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.RevokeAccessTokenOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.SendEmailVerificationOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.SendPasswordResetEmailOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.results.FetchSignInMethodsForEmailResult;
import io.capawesome.capacitorjs.plugins.firebase.authentication.handlers.AppleAuthProviderHandler;
import io.capawesome.capacitorjs.plugins.firebase.authentication.handlers.FacebookAuthProviderHandler;
Expand Down Expand Up @@ -167,7 +170,7 @@ public FirebaseUser getCurrentUser() {
public void getIdToken(Boolean forceRefresh, @NonNull final NonEmptyResultCallback callback) {
FirebaseUser user = getCurrentUser();
if (user == null) {
callback.error(new Exception(FirebaseAuthenticationPlugin.ERROR_NO_USER_SIGNED_IN));
callback.error(new Exception(ERROR_NO_USER_SIGNED_IN));
return;
}
Task<GetTokenResult> tokenResultTask = user.getIdToken(forceRefresh);
Expand Down Expand Up @@ -210,7 +213,7 @@ public void linkWithEmailAndPassword(final PluginCall call) {
}
FirebaseUser user = getCurrentUser();
if (user == null) {
call.reject(FirebaseAuthenticationPlugin.ERROR_NO_USER_SIGNED_IN);
call.reject(ERROR_NO_USER_SIGNED_IN);
return;
}

Expand Down Expand Up @@ -245,7 +248,7 @@ public void linkWithEmailLink(final PluginCall call) {
}
FirebaseUser user = getCurrentUser();
if (user == null) {
call.reject(FirebaseAuthenticationPlugin.ERROR_NO_USER_SIGNED_IN);
call.reject(ERROR_NO_USER_SIGNED_IN);
return;
}

Expand Down Expand Up @@ -327,24 +330,33 @@ public void revokeAccessToken(@NonNull RevokeAccessTokenOptions options, @NonNul
.addOnFailureListener(exception -> callback.error(exception));
}

public void sendEmailVerification(FirebaseUser user, @NonNull Runnable callback) {
user
.sendEmailVerification()
.addOnCompleteListener(
task -> {
callback.run();
}
);
public void sendEmailVerification(@NonNull SendEmailVerificationOptions options, @NonNull EmptyResultCallback callback) {
ActionCodeSettings actionCodeSettings = options.getActionCodeSettings();

FirebaseUser user = getCurrentUser();
if (user == null) {
Exception exception = new Exception(ERROR_NO_USER_SIGNED_IN);
callback.error(exception);
return;
}

Task<Void> task = null;
if (actionCodeSettings == null) {
task = user.sendEmailVerification();
} else {
task = user.sendEmailVerification(actionCodeSettings);
}
task.addOnSuccessListener(unused -> callback.success()).addOnFailureListener(exception -> callback.error(exception));
}

public void sendPasswordResetEmail(@NonNull String email, @NonNull Runnable callback) {
public void sendPasswordResetEmail(@NonNull SendPasswordResetEmailOptions options, @NonNull EmptyResultCallback callback) {
String email = options.getEmail();
ActionCodeSettings actionCodeSettings = options.getActionCodeSettings();

getFirebaseAuthInstance()
.sendPasswordResetEmail(email)
.addOnCompleteListener(
task -> {
callback.run();
}
);
.sendPasswordResetEmail(email, actionCodeSettings)
.addOnSuccessListener(unused -> callback.success())
.addOnFailureListener(exception -> callback.error(exception));
}

public void sendSignInLinkToEmail(@NonNull String email, @NonNull ActionCodeSettings actionCodeSettings, @NonNull Runnable callback) {
Expand Down Expand Up @@ -670,7 +682,7 @@ public void signInWithCredential(
public void linkWithCredential(@NonNull AuthCredential credential, @NonNull NonEmptyResultCallback callback) {
FirebaseUser user = getFirebaseAuthInstance().getCurrentUser();
if (user == null) {
callback.error(new Exception(FirebaseAuthenticationPlugin.ERROR_NO_USER_SIGNED_IN));
callback.error(new Exception(ERROR_NO_USER_SIGNED_IN));
return;
}
user
Expand Down Expand Up @@ -806,7 +818,7 @@ public void handleSuccessfulLink(
) {
FirebaseUser user = getFirebaseAuthInstance().getCurrentUser();
if (user == null) {
call.reject(FirebaseAuthenticationPlugin.ERROR_NO_USER_SIGNED_IN);
call.reject(ERROR_NO_USER_SIGNED_IN);
return;
}
if (credential == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import androidx.annotation.Nullable;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.google.firebase.auth.ActionCodeSettings;
import com.google.firebase.auth.AdditionalUserInfo;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.FirebaseAuthException;
Expand All @@ -12,6 +13,7 @@
import com.google.firebase.auth.UserInfo;
import java.util.List;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;

public class FirebaseAuthenticationHelper {
Expand All @@ -30,6 +32,35 @@ public static class ProviderId {
public static final String PHONE = "phone";
}

@Nullable
public static ActionCodeSettings createActionCodeSettings(@Nullable JSObject settings) throws JSONException {
if (settings == null) {
return null;
}
ActionCodeSettings.Builder builder = ActionCodeSettings.newBuilder().setUrl(settings.getString("url"));
Boolean handleCodeInApp = settings.getBoolean("handleCodeInApp", null);
if (handleCodeInApp != null) {
builder.setHandleCodeInApp(handleCodeInApp);
}
JSObject iOS = settings.getJSObject("iOS");
if (iOS != null) {
builder.setIOSBundleId(iOS.getString("bundleId"));
}
JSObject android = settings.getJSObject("android");
if (android != null) {
builder.setAndroidPackageName(
android.getString("packageName"),
android.getBoolean("installApp"),
android.getString("minimumVersion")
);
}
String dynamicLinkDomain = settings.getString("dynamicLinkDomain");
if (dynamicLinkDomain != null) {
builder.setDynamicLinkDomain(dynamicLinkDomain);
}
return builder.build();
}

@Nullable
public static String createErrorCode(@Nullable Exception exception) {
if (exception == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,18 @@
import com.getcapacitor.annotation.CapacitorPlugin;
import com.google.firebase.auth.ActionCodeSettings;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.PhoneAuthCredential;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.ConfirmVerificationCodeOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.LinkWithPhoneNumberOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.PhoneVerificationCompletedEvent;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.SignInResult;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.SignInWithPhoneNumberOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.FetchSignInMethodsForEmailOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.RevokeAccessTokenOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.SendEmailVerificationOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options.SendPasswordResetEmailOptions;
import io.capawesome.capacitorjs.plugins.firebase.authentication.handlers.FacebookAuthProviderHandler;
import io.capawesome.capacitorjs.plugins.firebase.authentication.interfaces.EmptyResultCallback;
import io.capawesome.capacitorjs.plugins.firebase.authentication.interfaces.NonEmptyResultCallback;
import io.capawesome.capacitorjs.plugins.firebase.authentication.interfaces.Result;
import io.capawesome.capacitorjs.plugins.firebase.authentication.interfaces.ResultCallback;
import org.json.JSONObject;

@CapacitorPlugin(name = "FirebaseAuthentication", requestCodes = { FacebookAuthProviderHandler.RC_FACEBOOK_AUTH })
Expand Down Expand Up @@ -493,12 +492,24 @@ public void error(Exception exception) {
@PluginMethod
public void sendEmailVerification(PluginCall call) {
try {
FirebaseUser user = implementation.getCurrentUser();
if (user == null) {
call.reject(ERROR_NO_USER_SIGNED_IN);
return;
}
implementation.sendEmailVerification(user, () -> call.resolve());
JSObject actionCodeSettings = call.getObject("actionCodeSettings");

SendEmailVerificationOptions options = new SendEmailVerificationOptions(actionCodeSettings);
EmptyResultCallback callback = new EmptyResultCallback() {
@Override
public void success() {
call.resolve();
}

@Override
public void error(Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
String code = FirebaseAuthenticationHelper.createErrorCode(exception);
call.reject(exception.getMessage(), code);
}
};

implementation.sendEmailVerification(options, callback);
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
String code = FirebaseAuthenticationHelper.createErrorCode(exception);
Expand All @@ -514,7 +525,24 @@ public void sendPasswordResetEmail(PluginCall call) {
call.reject(ERROR_EMAIL_MISSING);
return;
}
implementation.sendPasswordResetEmail(email, () -> call.resolve());
JSObject actionCodeSettings = call.getObject("actionCodeSettings");

SendPasswordResetEmailOptions options = new SendPasswordResetEmailOptions(email, actionCodeSettings);
EmptyResultCallback callback = new EmptyResultCallback() {
@Override
public void success() {
call.resolve();
}

@Override
public void error(Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
String code = FirebaseAuthenticationHelper.createErrorCode(exception);
call.reject(exception.getMessage(), code);
}
};

implementation.sendPasswordResetEmail(options, callback);
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
String code = FirebaseAuthenticationHelper.createErrorCode(exception);
Expand All @@ -536,25 +564,9 @@ public void sendSignInLinkToEmail(PluginCall call) {
return;
}

ActionCodeSettings.Builder actionCodeSettings = ActionCodeSettings.newBuilder().setUrl(settings.getString("url"));

Boolean handleCodeInApp = settings.getBoolean("handleCodeInApp");
if (handleCodeInApp != null) actionCodeSettings.setHandleCodeInApp(handleCodeInApp);

JSObject iOS = settings.getJSObject("iOS");
if (iOS != null) actionCodeSettings.setIOSBundleId(iOS.getString("bundleId"));

JSObject android = settings.getJSObject("android");
if (android != null) actionCodeSettings.setAndroidPackageName(
android.getString("packageName"),
android.getBoolean("installApp"),
android.getString("minimumVersion")
);

String dynamicLinkDomain = settings.getString("dynamicLinkDomain");
if (dynamicLinkDomain != null) actionCodeSettings.setDynamicLinkDomain(dynamicLinkDomain);
ActionCodeSettings actionCodeSettings = FirebaseAuthenticationHelper.createActionCodeSettings(settings);

implementation.sendSignInLinkToEmail(email, actionCodeSettings.build(), () -> call.resolve());
implementation.sendSignInLinkToEmail(email, actionCodeSettings, () -> call.resolve());
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
String code = FirebaseAuthenticationHelper.createErrorCode(exception);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.capawesome.capacitorjs.plugins.firebase.authentication.classes.options;

import androidx.annotation.Nullable;
import com.getcapacitor.JSObject;
import com.google.firebase.auth.ActionCodeSettings;
import io.capawesome.capacitorjs.plugins.firebase.authentication.FirebaseAuthenticationHelper;
import org.json.JSONException;

public class SendEmailVerificationOptions {

@Nullable
private ActionCodeSettings actionCodeSettings;

public SendEmailVerificationOptions(@Nullable JSObject actionCodeSettings) throws JSONException {
this.actionCodeSettings = FirebaseAuthenticationHelper.createActionCodeSettings(actionCodeSettings);
}

@Nullable
public ActionCodeSettings getActionCodeSettings() {
return actionCodeSettings;
}
}
Loading

0 comments on commit 638adac

Please sign in to comment.