Skip to content

Commit

Permalink
feat(authentication): support signInWithPhoneNumber on Web (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
robingenz authored Sep 27, 2023
1 parent 12e0052 commit 16a802c
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-monkeys-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@capacitor-firebase/authentication': minor
---

feat(web): support `signInWithPhoneNumber` and `linkWithPhoneNumber`
32 changes: 14 additions & 18 deletions packages/authentication/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,6 @@ confirmVerificationCode(options: ConfirmVerificationCodeOptions) => Promise<Sign

Finishes the phone number verification process.

Only available for Android and iOS.

| Param | Type |
| ------------- | ----------------------------------------------------------------------------------------- |
| **`options`** | <code><a href="#confirmverificationcodeoptions">ConfirmVerificationCodeOptions</a></code> |
Expand Down Expand Up @@ -804,9 +802,9 @@ Use the `phoneVerificationCompleted` listener to be notified when the verificati
Use the `phoneVerificationFailed` listener to be notified when the verification is failed.
Use the `phoneCodeSent` listener to get the verification id.

| Param | Type |
| ------------- | --------------------------------------------------------------------------------- |
| **`options`** | <code><a href="#linkwithphonenumberoptions">LinkWithPhoneNumberOptions</a></code> |
| Param | Type |
| ------------- | ------------------------------------------------------------------------------------- |
| **`options`** | <code><a href="#signinwithphonenumberoptions">SignInWithPhoneNumberOptions</a></code> |

**Since:** 1.1.0

Expand Down Expand Up @@ -1662,12 +1660,13 @@ Remove all listeners for this plugin.
| **`emailLink`** | <code>string</code> | The link sent to the user's email address. | 1.1.0 |


#### LinkWithPhoneNumberOptions
#### SignInWithPhoneNumberOptions

| Prop | Type | Description | Default | Since |
| ----------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
| **`phoneNumber`** | <code>string</code> | The phone number to be verified in E.164 format. | | 1.1.0 |
| **`resendCode`** | <code>boolean</code> | Resend the verification code to the specified phone number. `linkWithPhoneNumber` must be called once before using this option. Only available for Android. | <code>false</code> | 5.0.0 |
| Prop | Type | Description | Default | Since |
| ----------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
| **`phoneNumber`** | <code>string</code> | The phone number to be verified in E.164 format. | | 0.1.0 |
| **`recaptchaVerifier`** | <code>unknown</code> | The reCAPTCHA verifier. Must be an instance of `firebase.auth.RecaptchaVerifier`. Only available for Web. | | 1.3.0 |
| **`resendCode`** | <code>boolean</code> | Resend the verification code to the specified phone number. `signInWithPhoneNumber` must be called once before using this option. Only available for Android. | <code>false</code> | 1.3.0 |


#### SendPasswordResetEmailOptions
Expand Down Expand Up @@ -1759,14 +1758,6 @@ An interface covering the possible persistence mechanism types.
| **`skipNativeAuth`** | <code>boolean</code> | Whether the plugin should skip the native authentication or not. Only needed if you want to use the Firebase JavaScript SDK. This value overwrites the configrations value of the `skipNativeAuth` option. If no value is set, the configuration value is used. **Note that the plugin may behave differently across the platforms.** `skipNativeAuth` cannot be used in combination with `signInWithCustomToken`, `createUserWithEmailAndPassword` or `signInWithEmailAndPassword`. Only available for Android and iOS. | 1.1.0 |


#### SignInWithPhoneNumberOptions

| Prop | Type | Description | Default | Since |
| ----------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
| **`phoneNumber`** | <code>string</code> | The phone number to be verified in E.164 format. | | 0.1.0 |
| **`resendCode`** | <code>boolean</code> | Resend the verification code to the specified phone number. `signInWithPhoneNumber` must be called once before using this option. Only available for Android. | <code>false</code> | 1.3.0 |


#### UnlinkResult

| Prop | Type | Description | Since |
Expand Down Expand Up @@ -1859,6 +1850,11 @@ An interface covering the possible persistence mechanism types.
<code><a href="#signinresult">SignInResult</a></code>


#### LinkWithPhoneNumberOptions

<code><a href="#signinwithphonenumberoptions">SignInWithPhoneNumberOptions</a></code>


#### AuthStateChangeListener

Callback to receive the user's sign-in state change notifications.
Expand Down
2 changes: 1 addition & 1 deletion packages/authentication/docs/setup-phone.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@

## Web

🚧 Currently not supported.
1. See [Enable Phone Number sign-in for your Firebase project](https://firebase.google.com/docs/auth/web/phone-auth#enable-phone-number-sign-in-for-your-firebase-project) and follow the instructions to configure sign-in using a Phone Number correctly.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class FirebaseAuthenticationHelper {
}

public static func createSignInResult(credential: AuthCredential?, user: User?, idToken: String?, nonce: String?, accessToken: String?, additionalUserInfo: AdditionalUserInfo?, displayName: String?, authorizationCode: String?) -> JSObject {
return createSignInResult(credential: credential, user: user, idToken: idToken, nonce: nonce, accessToken: accessToken,
return createSignInResult(credential: credential, user: user, idToken: idToken, nonce: nonce, accessToken: accessToken,
serverAuthCode: nil, additionalUserInfo: additionalUserInfo, displayName: nil, authorizationCode: nil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ class GoogleAuthProviderHandler: NSObject {
let serverAuthCode = result?.serverAuthCode
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
if isLink == true {
self.pluginImplementation.handleSuccessfulLink(credential: credential, idToken: idToken, nonce: nil, accessToken: accessToken,
self.pluginImplementation.handleSuccessfulLink(credential: credential, idToken: idToken, nonce: nil, accessToken: accessToken,
serverAuthCode: serverAuthCode)
} else {
self.pluginImplementation.handleSuccessfulSignIn(credential: credential, idToken: idToken, nonce: nil, accessToken: accessToken,
self.pluginImplementation.handleSuccessfulSignIn(credential: credential, idToken: idToken, nonce: nil, accessToken: accessToken,
displayName: nil, authorizationCode: nil, serverAuthCode: serverAuthCode)
}
}
Expand Down
31 changes: 10 additions & 21 deletions packages/authentication/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ export interface FirebaseAuthenticationPlugin {
/**
* Finishes the phone number verification process.
*
* Only available for Android and iOS.
*
* @since 5.0.0
*/
confirmVerificationCode(
Expand Down Expand Up @@ -762,25 +760,7 @@ export interface LinkWithEmailLinkOptions {
/**
* @since 1.1.0
*/
export interface LinkWithPhoneNumberOptions {
/**
* The phone number to be verified in E.164 format.
*
* @example "+16505550101"
* @since 1.1.0
*/
phoneNumber: string;
/**
* Resend the verification code to the specified phone number.
* `linkWithPhoneNumber` must be called once before using this option.
*
* Only available for Android.
*
* @since 5.0.0
* @default false
*/
resendCode?: boolean;
}
export type LinkWithPhoneNumberOptions = SignInWithPhoneNumberOptions;

/**
* @since 1.1.0
Expand Down Expand Up @@ -878,6 +858,15 @@ export interface SignInWithPhoneNumberOptions extends SignInOptions {
* @since 0.1.0
*/
phoneNumber: string;
/**
* The reCAPTCHA verifier.
* Must be an instance of `firebase.auth.RecaptchaVerifier`.
*
* Only available for Web.
*
* @since 1.3.0
*/
recaptchaVerifier?: unknown;
/**
* Resend the verification code to the specified phone number.
* `signInWithPhoneNumber` must be called once before using this option.
Expand Down
128 changes: 121 additions & 7 deletions packages/authentication/src/web.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { WebPlugin } from '@capacitor/core';
import type {
ConfirmationResult,
AuthCredential as FirebaseAuthCredential,
AuthProvider as FirebaseAuthProvider,
CustomParameters as FirebaseCustomParameters,
Expand All @@ -15,6 +16,7 @@ import {
GoogleAuthProvider,
OAuthCredential,
OAuthProvider,
RecaptchaVerifier,
TwitterAuthProvider,
applyActionCode,
browserLocalPersistence,
Expand All @@ -30,6 +32,7 @@ import {
indexedDBLocalPersistence,
isSignInWithEmailLink,
linkWithCredential,
linkWithPhoneNumber,
linkWithPopup,
linkWithRedirect,
reload,
Expand All @@ -41,6 +44,7 @@ import {
signInWithCustomToken,
signInWithEmailAndPassword,
signInWithEmailLink,
signInWithPhoneNumber,
signInWithPopup,
signInWithRedirect,
unlink,
Expand Down Expand Up @@ -69,6 +73,8 @@ import type {
LinkWithEmailLinkOptions,
LinkWithOAuthOptions,
LinkWithPhoneNumberOptions,
PhoneCodeSentEvent,
PhoneVerificationFailedEvent,
SendPasswordResetEmailOptions,
SendSignInLinkToEmailOptions,
SetLanguageCodeOptions,
Expand Down Expand Up @@ -96,7 +102,19 @@ export class FirebaseAuthenticationWeb
extends WebPlugin
implements FirebaseAuthenticationPlugin
{
public static readonly AUTH_STATE_CHANGE_EVENT = 'authStateChange';
public static readonly PHONE_CODE_SENT_EVENT = 'phoneCodeSent';
public static readonly PHONE_VERIFICATION_FAILED_EVENT =
'phoneVerificationFailed';
public static readonly ERROR_NO_USER_SIGNED_IN = 'No user is signed in.';
public static readonly ERROR_PHONE_NUMBER_MISSING =
'phoneNumber must be provided.';
public static readonly ERROR_RECAPTCHA_VERIFIER_MISSING =
'recaptchaVerifier must be provided and must be an instance of RecaptchaVerifier.';
public static readonly ERROR_CONFIRMATION_RESULT_MISSING =
'No confirmation result with this verification id was found.';

private lastConfirmationResult: Map<string, ConfirmationResult> = new Map();

constructor() {
super();
Expand Down Expand Up @@ -129,9 +147,17 @@ export class FirebaseAuthenticationWeb
}

public async confirmVerificationCode(
_options: ConfirmVerificationCodeOptions,
options: ConfirmVerificationCodeOptions,
): Promise<SignInResult> {
throw new Error('Not implemented on web.');
const { verificationCode, verificationId } = options;
const confirmationResult = this.lastConfirmationResult.get(verificationId);
if (!confirmationResult) {
throw new Error(
FirebaseAuthenticationWeb.ERROR_CONFIRMATION_RESULT_MISSING,
);
}
const userCredential = await confirmationResult.confirm(verificationCode);
return this.createSignInResult(userCredential, null);
}

public async deleteUser(): Promise<void> {
Expand Down Expand Up @@ -290,9 +316,48 @@ export class FirebaseAuthenticationWeb
}

public async linkWithPhoneNumber(
_options: LinkWithPhoneNumberOptions,
options: LinkWithPhoneNumberOptions,
): Promise<void> {
throw new Error('Not implemented on web.');
const auth = getAuth();
const currentUser = auth.currentUser;
if (!currentUser) {
throw new Error(FirebaseAuthenticationWeb.ERROR_NO_USER_SIGNED_IN);
}
if (!options.phoneNumber) {
throw new Error(FirebaseAuthenticationWeb.ERROR_PHONE_NUMBER_MISSING);
}
if (
!options.recaptchaVerifier ||
!(options.recaptchaVerifier instanceof RecaptchaVerifier)
) {
throw new Error(
FirebaseAuthenticationWeb.ERROR_RECAPTCHA_VERIFIER_MISSING,
);
}
try {
const confirmationResult = await linkWithPhoneNumber(
currentUser,
options.phoneNumber,
options.recaptchaVerifier,
);
const { verificationId } = confirmationResult;
this.lastConfirmationResult.set(verificationId, confirmationResult);
const event: PhoneCodeSentEvent = {
verificationId,
};
this.notifyListeners(
FirebaseAuthenticationWeb.PHONE_CODE_SENT_EVENT,
event,
);
} catch (error) {
const event: PhoneVerificationFailedEvent = {
message: this.getErrorMessage(error),
};
this.notifyListeners(
FirebaseAuthenticationWeb.PHONE_VERIFICATION_FAILED_EVENT,
event,
);
}
}

public async linkWithPlayGames(): Promise<LinkResult> {
Expand Down Expand Up @@ -497,9 +562,44 @@ export class FirebaseAuthenticationWeb
}

public async signInWithPhoneNumber(
_options: SignInWithPhoneNumberOptions,
options: SignInWithPhoneNumberOptions,
): Promise<void> {
throw new Error('Not implemented on web.');
if (!options.phoneNumber) {
throw new Error(FirebaseAuthenticationWeb.ERROR_PHONE_NUMBER_MISSING);
}
if (
!options.recaptchaVerifier ||
!(options.recaptchaVerifier instanceof RecaptchaVerifier)
) {
throw new Error(
FirebaseAuthenticationWeb.ERROR_RECAPTCHA_VERIFIER_MISSING,
);
}
const auth = getAuth();
try {
const confirmationResult = await signInWithPhoneNumber(
auth,
options.phoneNumber,
options.recaptchaVerifier,
);
const { verificationId } = confirmationResult;
this.lastConfirmationResult.set(verificationId, confirmationResult);
const event: PhoneCodeSentEvent = {
verificationId,
};
this.notifyListeners(
FirebaseAuthenticationWeb.PHONE_CODE_SENT_EVENT,
event,
);
} catch (error) {
const event: PhoneVerificationFailedEvent = {
message: this.getErrorMessage(error),
};
this.notifyListeners(
FirebaseAuthenticationWeb.PHONE_VERIFICATION_FAILED_EVENT,
event,
);
}
}

public async signInWithPlayGames(): Promise<SignInResult> {
Expand Down Expand Up @@ -598,7 +698,10 @@ export class FirebaseAuthenticationWeb
const change: AuthStateChange = {
user: userResult,
};
this.notifyListeners('authStateChange', change);
this.notifyListeners(
FirebaseAuthenticationWeb.AUTH_STATE_CHANGE_EVENT,
change,
);
}

private applySignInOptions(
Expand Down Expand Up @@ -760,4 +863,15 @@ export class FirebaseAuthenticationWeb
}
return result;
}

private getErrorMessage(error: unknown): string {
if (
error instanceof Object &&
'message' in error &&
typeof error['message'] === 'string'
) {
return error['message'];
}
return JSON.stringify(error);
}
}

0 comments on commit 16a802c

Please sign in to comment.