Skip to content

Commit 77af6b2

Browse files
feat(auth): linkDomain support for ActionCodeSettings (#8335)
* feat: linkDomain support for actionCodeSettings * chore: update types to keep firebase-js-sdk types --------- Co-authored-by: russellwheatley <[email protected]>
1 parent 4d166ca commit 77af6b2

File tree

9 files changed

+149
-16
lines changed

9 files changed

+149
-16
lines changed

packages/auth/__tests__/auth.test.ts

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
2-
1+
import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';
2+
import { FirebaseAuthTypes } from '../lib/index';
3+
// @ts-ignore
4+
import User from '../lib/User';
35
import auth, {
46
firebase,
57
getAuth,
@@ -58,6 +60,8 @@ import auth, {
5860
getCustomAuthDomain,
5961
} from '../lib';
6062

63+
// @ts-ignore test
64+
import FirebaseModule from '../../app/lib/internal/FirebaseModule';
6165
// @ts-ignore - We don't mind missing types here
6266
import { NativeFirebaseError } from '../../app/lib/internal';
6367

@@ -403,5 +407,83 @@ describe('Auth', function () {
403407
it('`getCustomAuthDomain` function is properly exposed to end user', function () {
404408
expect(getCustomAuthDomain).toBeDefined();
405409
});
410+
411+
describe('ActionCodeSettings', function () {
412+
beforeAll(function () {
413+
// @ts-ignore test
414+
jest.spyOn(FirebaseModule.prototype, 'native', 'get').mockImplementation(() => {
415+
return new Proxy(
416+
{},
417+
{
418+
get: () => jest.fn().mockResolvedValue({} as never),
419+
},
420+
);
421+
});
422+
});
423+
424+
it('should allow linkDomain as `ActionCodeSettings.linkDomain`', function () {
425+
const auth = firebase.app().auth();
426+
const actionCodeSettings: FirebaseAuthTypes.ActionCodeSettings = {
427+
url: 'https://example.com',
428+
handleCodeInApp: true,
429+
linkDomain: 'example.com',
430+
};
431+
const email = '[email protected]';
432+
auth.sendSignInLinkToEmail(email, actionCodeSettings);
433+
auth.sendPasswordResetEmail(email, actionCodeSettings);
434+
sendPasswordResetEmail(auth, email, actionCodeSettings);
435+
sendSignInLinkToEmail(auth, email, actionCodeSettings);
436+
437+
const user: FirebaseAuthTypes.User = new User(auth, {});
438+
439+
user.sendEmailVerification(actionCodeSettings);
440+
user.verifyBeforeUpdateEmail(email, actionCodeSettings);
441+
sendEmailVerification(user, actionCodeSettings);
442+
verifyBeforeUpdateEmail(user, email, actionCodeSettings);
443+
});
444+
445+
it('should warn using `ActionCodeSettings.dynamicLinkDomain`', function () {
446+
const auth = firebase.app().auth();
447+
const actionCodeSettings: FirebaseAuthTypes.ActionCodeSettings = {
448+
url: 'https://example.com',
449+
handleCodeInApp: true,
450+
linkDomain: 'example.com',
451+
dynamicLinkDomain: 'example.com',
452+
};
453+
const email = '[email protected]';
454+
let warnings = 0;
455+
const consoleWarnSpy = jest.spyOn(console, 'warn');
456+
consoleWarnSpy.mockReset();
457+
consoleWarnSpy.mockImplementation(warnMessage => {
458+
if (
459+
warnMessage.includes(
460+
'Instead, use ActionCodeSettings.linkDomain to set up a custom domain',
461+
)
462+
) {
463+
warnings++;
464+
}
465+
});
466+
auth.sendSignInLinkToEmail(email, actionCodeSettings);
467+
expect(warnings).toBe(1);
468+
auth.sendPasswordResetEmail(email, actionCodeSettings);
469+
expect(warnings).toBe(2);
470+
sendPasswordResetEmail(auth, email, actionCodeSettings);
471+
expect(warnings).toBe(3);
472+
sendSignInLinkToEmail(auth, email, actionCodeSettings);
473+
expect(warnings).toBe(4);
474+
const user: FirebaseAuthTypes.User = new User(auth, {});
475+
476+
user.sendEmailVerification(actionCodeSettings);
477+
expect(warnings).toBe(5);
478+
user.verifyBeforeUpdateEmail(email, actionCodeSettings);
479+
expect(warnings).toBe(6);
480+
sendEmailVerification(user, actionCodeSettings);
481+
expect(warnings).toBe(7);
482+
verifyBeforeUpdateEmail(user, email, actionCodeSettings);
483+
expect(warnings).toBe(8);
484+
consoleWarnSpy.mockReset();
485+
consoleWarnSpy.mockRestore();
486+
});
487+
});
406488
});
407489
});

packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2576,6 +2576,11 @@ private ActionCodeSettings buildActionCodeSettings(ReadableMap actionCodeSetting
25762576
builder = builder.setDynamicLinkDomain(actionCodeSettings.getString("dynamicLinkDomain"));
25772577
}
25782578

2579+
if (actionCodeSettings.hasKey("linkDomain")) {
2580+
builder =
2581+
builder.setLinkDomain(Objects.requireNonNull(actionCodeSettings.getString("linkDomain")));
2582+
}
2583+
25792584
if (actionCodeSettings.hasKey("android")) {
25802585
ReadableMap android = actionCodeSettings.getMap("android");
25812586
boolean installApp =

packages/auth/ios/RNFBAuth/RNFBAuthModule.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
static NSString *const constAppLanguage = @"APP_LANGUAGE";
4545
static NSString *const constAppUser = @"APP_USER";
4646
static NSString *const keyHandleCodeInApp = @"handleCodeInApp";
47+
static NSString *const keyLinkDomain = @"linkDomain";
4748
static NSString *const keyDynamicLinkDomain = @"dynamicLinkDomain";
4849
static NSString *const keyAdditionalUserInfo = @"additionalUserInfo";
4950
static NSString *const AUTH_STATE_CHANGED_EVENT = @"auth_state_changed";
@@ -1765,6 +1766,11 @@ - (FIRActionCodeSettings *)buildActionCodeSettings:(NSDictionary *)actionCodeSet
17651766
NSString *url = actionCodeSettings[keyUrl];
17661767
[settings setURL:[NSURL URLWithString:url]];
17671768

1769+
if (actionCodeSettings[keyLinkDomain]) {
1770+
NSString *linkDomain = actionCodeSettings[keyLinkDomain];
1771+
[settings setLinkDomain:linkDomain];
1772+
}
1773+
17681774
if (actionCodeSettings[keyHandleCodeInApp]) {
17691775
BOOL handleCodeInApp = [actionCodeSettings[keyHandleCodeInApp] boolValue];
17701776
[settings setHandleCodeInApp:handleCodeInApp];

packages/auth/lib/User.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { isObject, isString, isUndefined, isBoolean } from '@react-native-firebase/app/lib/common';
19+
import { warnDynamicLink } from './utils';
1920

2021
export default class User {
2122
constructor(auth, user) {
@@ -131,6 +132,7 @@ export default class User {
131132
}
132133

133134
sendEmailVerification(actionCodeSettings) {
135+
warnDynamicLink(actionCodeSettings);
134136
if (isObject(actionCodeSettings)) {
135137
if (!isString(actionCodeSettings.url)) {
136138
throw new Error(
@@ -147,6 +149,12 @@ export default class User {
147149
);
148150
}
149151

152+
if (!isUndefined(actionCodeSettings.linkDomain) && !isString(actionCodeSettings.linkDomain)) {
153+
throw new Error(
154+
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.linkDomain' expected a string value.",
155+
);
156+
}
157+
150158
if (
151159
!isUndefined(actionCodeSettings.handleCodeInApp) &&
152160
!isBoolean(actionCodeSettings.handleCodeInApp)
@@ -241,6 +249,7 @@ export default class User {
241249
}
242250

243251
verifyBeforeUpdateEmail(newEmail, actionCodeSettings) {
252+
warnDynamicLink(actionCodeSettings);
244253
if (!isString(newEmail)) {
245254
throw new Error(
246255
"firebase.auth.User.verifyBeforeUpdateEmail(*) 'newEmail' expected a string value.",
@@ -263,6 +272,12 @@ export default class User {
263272
);
264273
}
265274

275+
if (!isUndefined(actionCodeSettings.linkDomain) && !isString(actionCodeSettings.linkDomain)) {
276+
throw new Error(
277+
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.linkDomain' expected a string value.",
278+
);
279+
}
280+
266281
if (
267282
!isUndefined(actionCodeSettings.handleCodeInApp) &&
268283
!isBoolean(actionCodeSettings.handleCodeInApp)

packages/auth/lib/index.d.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -881,13 +881,19 @@ export namespace FirebaseAuthTypes {
881881

882882
/**
883883
* Sets the dynamic link domain (or subdomain) to use for the current link if it is to be opened using Firebase Dynamic Links. As multiple dynamic link domains can be configured per project, this field provides the ability to explicitly choose one. If none is provided, the first domain is used by default.
884+
* Deprecated - use {@link ActionCodeSettings.linkDomain} instead.
884885
*/
885886
dynamicLinkDomain?: string;
886887

887888
/**
888889
* This URL represents the state/Continue URL in the form of a universal link. This URL can should be constructed as a universal link that would either directly open the app where the action code would be handled or continue to the app after the action code is handled by Firebase.
889890
*/
890891
url: string;
892+
/**
893+
* Firebase Dynamic Links is deprecated and will be shut down as early as August * 2025.
894+
* Instead, use ActionCodeSettings.linkDomain to set a a custom domain. Learn more at: https://firebase.google.com/support/dynamic-links-faq
895+
*/
896+
linkDomain?: string;
891897
}
892898

893899
/**
@@ -1342,8 +1348,7 @@ export namespace FirebaseAuthTypes {
13421348
reauthenticateWithCredential(credential: AuthCredential): Promise<UserCredential>;
13431349

13441350
/**
1345-
* Re-authenticate a user with a federated authentication provider (Microsoft, Yahoo)
1346-
*
1351+
* Re-authenticate a user with a federated authentication provider (Microsoft, Yahoo). For native platforms, this will open a browser window.
13471352
* #### Example
13481353
*
13491354
* ```js
@@ -1359,9 +1364,17 @@ export namespace FirebaseAuthTypes {
13591364
* @error auth/invalid-verification-code Thrown if the credential is a auth.PhoneAuthProvider.credential and the verification code of the credential is not valid.
13601365
* @error auth/invalid-verification-id Thrown if the credential is a auth.PhoneAuthProvider.credential and the verification ID of the credential is not valid.
13611366
* @param provider A created {@link auth.AuthProvider}.
1367+
* @returns A promise that resolves with no value.
13621368
*/
1363-
reauthenticateWithProvider(provider: AuthProvider): Promise<UserCredential>;
1364-
1369+
reauthenticateWithRedirect(provider: AuthProvider): Promise<void>;
1370+
/**
1371+
* Re-authenticate a user with a federated authentication provider (Microsoft, Yahoo). For native platforms, this will open a browser window.
1372+
* pop-up equivalent on native platforms.
1373+
*
1374+
* @param provider - The auth provider.
1375+
* @returns A promise that resolves with the user credentials.
1376+
*/
1377+
reauthenticateWithPopup(provider: AuthProvider): Promise<UserCredential>;
13651378
/**
13661379
* Refreshes the current user.
13671380
*

packages/auth/lib/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import PhoneAuthProvider from './providers/PhoneAuthProvider';
4646
import TwitterAuthProvider from './providers/TwitterAuthProvider';
4747
import version from './version';
4848
import fallBackModule from './web/RNFBAuthModule';
49+
import { warnDynamicLink } from './utils';
4950

5051
export {
5152
AppleAuthProvider,
@@ -350,10 +351,12 @@ class FirebaseAuthModule extends FirebaseModule {
350351
}
351352

352353
sendPasswordResetEmail(email, actionCodeSettings = null) {
354+
warnDynamicLink(actionCodeSettings);
353355
return this.native.sendPasswordResetEmail(email, actionCodeSettings);
354356
}
355357

356358
sendSignInLinkToEmail(email, actionCodeSettings = {}) {
359+
warnDynamicLink(actionCodeSettings);
357360
return this.native.sendSignInLinkToEmail(email, actionCodeSettings);
358361
}
359362

packages/auth/lib/modular/index.d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -530,11 +530,11 @@ export function reauthenticateWithPhoneNumber(
530530
): Promise<FirebaseAuthTypes.ConfirmationResult>;
531531

532532
/**
533-
* Reauthenticates the current user with the specified OAuthProvider using a pop-up based OAuth flow.
533+
* Re-authenticate a user with a federated authentication provider (Microsoft, Yahoo). For native platforms, this will open a browser window.
534534
*
535535
* @param user - The user to re-authenticate.
536536
* @param provider - The auth provider.
537-
* @param resolver - Optional. The popup redirect resolver.
537+
* @param resolver - Optional. The popup redirect resolver. Web only.
538538
* @returns A promise that resolves with the user credentials.
539539
*/
540540
export function reauthenticateWithPopup(
@@ -544,12 +544,12 @@ export function reauthenticateWithPopup(
544544
): Promise<FirebaseAuthTypes.UserCredential>;
545545

546546
/**
547-
* Reauthenticates the current user with the specified OAuthProvider using a full-page redirect flow.
547+
* Re-authenticate a user with a federated authentication provider (Microsoft, Yahoo). For native platforms, this will open a browser window.
548548
*
549549
* @param user - The user to re-authenticate.
550550
* @param provider - The auth provider.
551-
* @param resolver - Optional. The popup redirect resolver.
552-
* @returns A promise that resolves when the redirect is complete.
551+
* @param resolver - Optional. The popup redirect resolver. Web only.
552+
* @returns A promise that resolves with no value.
553553
*/
554554
export function reauthenticateWithRedirect(
555555
user: FirebaseAuthTypes.User,

packages/auth/lib/modular/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -474,22 +474,22 @@ export async function reauthenticateWithPhoneNumber(user, phoneNumber, appVerifi
474474
}
475475

476476
/**
477-
* Reauthenticates the current user with the specified OAuthProvider using a pop-up based OAuth flow.
477+
* Re-authenticate a user with a federated authentication provider (Microsoft, Yahoo). For native platforms, this will open a browser window.
478478
* @param {User} user - The user to re-authenticate.
479479
* @param {AuthProvider} provider - The auth provider.
480-
* @param {PopupRedirectResolver} [resolver] - Optional. The popup redirect resolver.
480+
* @param {PopupRedirectResolver} [resolver] - Optional. The popup redirect resolver. Web only.
481481
* @returns {Promise<UserCredential>}
482482
*/
483483
export async function reauthenticateWithPopup(user, provider, resolver) {
484484
return user.reauthenticateWithPopup(provider, resolver);
485485
}
486486

487487
/**
488-
* Reauthenticates the current user with the specified OAuthProvider using a full-page redirect flow.
488+
* Re-authenticate a user with a federated authentication provider (Microsoft, Yahoo). For native platforms, this will open a browser window.
489489
* @param {User} user - The user to re-authenticate.
490490
* @param {AuthProvider} provider - The auth provider.
491-
* @param {PopupRedirectResolver} [resolver] - Optional. The popup redirect resolver.
492-
* @returns {Promise<void>}
491+
* @param {PopupRedirectResolver} [resolver] - Optional. The popup redirect resolver. Web only.
492+
* @returns {Promise<UserCredential>}
493493
*/
494494
export async function reauthenticateWithRedirect(user, provider, resolver) {
495495
return user.reauthenticateWithRedirect(provider, resolver);

packages/auth/lib/utils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function warnDynamicLink(actionCodeSettings) {
2+
if (actionCodeSettings && actionCodeSettings.dynamicLinkDomain) {
3+
// eslint-disable-next-line no-console
4+
console.warn(
5+
'Firebase Dynamic Links is deprecated and will be shut down as early as August * 2025. \
6+
Instead, use ActionCodeSettings.linkDomain to set up a custom domain. Learn more at: https://firebase.google.com/support/dynamic-links-faq',
7+
);
8+
}
9+
}

0 commit comments

Comments
 (0)