diff --git a/packages-exp/auth-compat-exp/src/platform.ts b/packages-exp/auth-compat-exp/src/platform.ts index 5d5d879a463..33c778f0509 100644 --- a/packages-exp/auth-compat-exp/src/platform.ts +++ b/packages-exp/auth-compat-exp/src/platform.ts @@ -162,8 +162,13 @@ export function _getClientPlatform(): impl.ClientPlatform { return impl.ClientPlatform.BROWSER; } +/** Quick check that indicates the platform *may* be Cordova */ +export function _isLikelyCordova(): boolean { + return _isAndroidOrIosCordovaScheme() && typeof document !== 'undefined'; +} + export async function _isCordova(): Promise { - if (!_isAndroidOrIosCordovaScheme() || typeof document === 'undefined') { + if (!_isLikelyCordova()) { return false; } diff --git a/packages-exp/auth-compat-exp/src/popup_redirect.test.ts b/packages-exp/auth-compat-exp/src/popup_redirect.test.ts index 8de3d9642ef..c6f9915d7b3 100644 --- a/packages-exp/auth-compat-exp/src/popup_redirect.test.ts +++ b/packages-exp/auth-compat-exp/src/popup_redirect.test.ts @@ -126,11 +126,45 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => { ); }); }); + + context('_shouldInitProactively', () => { + it('returns true if platform may be cordova', () => { + sinon.stub(platform, '_isLikelyCordova').returns(true); + expect(compatResolver._shouldInitProactively).to.be.true; + }); + + it('returns true if cordova is false but browser value is true', () => { + sinon + .stub( + exp._getInstance( + exp.browserPopupRedirectResolver + ), + '_shouldInitProactively' + ) + .value(true); + sinon.stub(platform, '_isLikelyCordova').returns(false); + expect(compatResolver._shouldInitProactively).to.be.true; + }); + + it('returns false if not cordova and not browser early init', () => { + sinon + .stub( + exp._getInstance( + exp.browserPopupRedirectResolver + ), + '_shouldInitProactively' + ) + .value(false); + sinon.stub(platform, '_isLikelyCordova').returns(false); + expect(compatResolver._shouldInitProactively).to.be.false; + }); + }); }); class FakeResolver implements exp.PopupRedirectResolverInternal { _completeRedirectFn = async (): Promise => null; _redirectPersistence = exp.inMemoryPersistence; + _shouldInitProactively = true; _initialize(): Promise { throw new Error('Method not implemented.'); diff --git a/packages-exp/auth-compat-exp/src/popup_redirect.ts b/packages-exp/auth-compat-exp/src/popup_redirect.ts index 0e83ca90fbd..3f38c75ed65 100644 --- a/packages-exp/auth-compat-exp/src/popup_redirect.ts +++ b/packages-exp/auth-compat-exp/src/popup_redirect.ts @@ -16,9 +16,15 @@ */ import * as exp from '@firebase/auth-exp/internal'; -import { _isCordova } from './platform'; +import { _isCordova, _isLikelyCordova } from './platform'; const _assert: typeof exp._assert = exp._assert; +const BROWSER_RESOLVER: exp.PopupRedirectResolverInternal = exp._getInstance( + exp.browserPopupRedirectResolver +); +const CORDOVA_RESOLVER: exp.PopupRedirectResolverInternal = exp._getInstance( + exp.cordovaPopupRedirectResolver +); /** Platform-agnostic popup-redirect resolver */ export class CompatPopupRedirectResolver @@ -40,11 +46,7 @@ export class CompatPopupRedirectResolver // We haven't yet determined whether or not we're in Cordova; go ahead // and determine that state now. const isCordova = await _isCordova(); - this.underlyingResolver = exp._getInstance( - isCordova - ? exp.cordovaPopupRedirectResolver - : exp.browserPopupRedirectResolver - ); + this.underlyingResolver = isCordova ? CORDOVA_RESOLVER : BROWSER_RESOLVER; return this.assertedUnderlyingResolver._initialize(auth); } @@ -87,6 +89,10 @@ export class CompatPopupRedirectResolver return this.assertedUnderlyingResolver._originValidation(auth); } + get _shouldInitProactively(): boolean { + return _isLikelyCordova() || BROWSER_RESOLVER._shouldInitProactively; + } + private get assertedUnderlyingResolver(): exp.PopupRedirectResolverInternal { _assert(this.underlyingResolver, exp.AuthErrorCode.INTERNAL_ERROR); return this.underlyingResolver; diff --git a/packages-exp/auth-exp/src/core/auth/auth_impl.ts b/packages-exp/auth-exp/src/core/auth/auth_impl.ts index bddb14d1262..d9a3da0aa79 100644 --- a/packages-exp/auth-exp/src/core/auth/auth_impl.ts +++ b/packages-exp/auth-exp/src/core/auth/auth_impl.ts @@ -132,6 +132,12 @@ export class AuthImpl implements AuthInternal, _FirebaseService { return; } + // Initialize the resolver early if necessary (only applicable to web: + // this will cause the iframe to load immediately in certain cases) + if (this._popupRedirectResolver?._shouldInitProactively) { + await this._popupRedirectResolver._initialize(this); + } + await this.initializeCurrentUser(popupRedirectResolver); if (this._deleted) { diff --git a/packages-exp/auth-exp/src/core/auth/initialize.test.ts b/packages-exp/auth-exp/src/core/auth/initialize.test.ts index 412115a6b9e..147ca45ee3b 100644 --- a/packages-exp/auth-exp/src/core/auth/initialize.test.ts +++ b/packages-exp/auth-exp/src/core/auth/initialize.test.ts @@ -117,6 +117,7 @@ describe('core/auth/initialize', () => { cb(true); } async _originValidation(): Promise {} + _shouldInitProactively = false; async _completeRedirectFn( _auth: Auth, _resolver: PopupRedirectResolver, diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts index bc847589849..e7ffb04f4d0 100644 --- a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts @@ -22,7 +22,8 @@ import { ProviderId } from '../../model/public_types'; import * as sinon from 'sinon'; -import { _getInstance } from '../util/instantiator'; +import * as sinonChai from 'sinon-chai'; +import { _clearInstanceMap, _getInstance } from '../util/instantiator'; import { MockPersistenceLayer, TestAuth, @@ -39,24 +40,24 @@ import { PopupRedirectResolverInternal } from '../../model/popup_redirect'; import { BASE_AUTH_EVENT } from '../../../test/helpers/iframe_event'; -import { PersistenceInternal } from '../persistence'; -import { InMemoryPersistence } from '../persistence/in_memory'; import { UserCredentialImpl } from '../user/user_credential_impl'; import * as idpTasks from '../strategies/idp'; -import { expect } from 'chai'; +import { expect, use } from 'chai'; import { AuthErrorCode } from '../errors'; +import { RedirectPersistence } from '../../../test/helpers/redirect_persistence'; + +use(sinonChai); const MATCHING_EVENT_ID = 'matching-event-id'; const OTHER_EVENT_ID = 'wrong-id'; -class RedirectPersistence extends InMemoryPersistence {} - describe('core/strategies/redirect', () => { let auth: AuthInternal; let redirectAction: RedirectAction; let eventManager: AuthEventManager; let resolver: PopupRedirectResolver; let idpStubs: sinon.SinonStubbedInstance; + let redirectPersistence: RedirectPersistence; beforeEach(async () => { eventManager = new AuthEventManager(({} as unknown) as TestAuth); @@ -67,11 +68,16 @@ describe('core/strategies/redirect', () => { )._redirectPersistence = RedirectPersistence; auth = await testAuth(); redirectAction = new RedirectAction(auth, _getInstance(resolver), false); + redirectPersistence = _getInstance(RedirectPersistence); + + // Default to has redirect for most test + redirectPersistence.hasPendingRedirect = true; }); afterEach(() => { sinon.restore(); _clearRedirectOutcomes(); + _clearInstanceMap(); }); function iframeEvent(event: Partial): void { @@ -86,16 +92,11 @@ describe('core/strategies/redirect', () => { } async function reInitAuthWithRedirectUser(eventId: string): Promise { - const redirectPersistence: PersistenceInternal = _getInstance( - RedirectPersistence - ); const mainPersistence = new MockPersistenceLayer(); const oldAuth = await testAuth(); const user = testUser(oldAuth, 'uid'); user._redirectEventId = eventId; - sinon - .stub(redirectPersistence, '_get') - .returns(Promise.resolve(user.toJSON())); + redirectPersistence.redirectUser = user.toJSON(); sinon.stub(mainPersistence, '_get').returns(Promise.resolve(user.toJSON())); auth = await testAuth(resolver, mainPersistence); @@ -194,4 +195,18 @@ describe('core/strategies/redirect', () => { expect(await promise).to.eq(cred); expect(await redirectAction.execute()).to.eq(cred); }); + + it('bypasses initialization if no key set', async () => { + await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); + const resolverInstance = _getInstance( + resolver + ); + + sinon.spy(resolverInstance, '_initialize'); + redirectPersistence.hasPendingRedirect = false; + + expect(await redirectAction.execute()).to.eq(null); + expect(await redirectAction.execute()).to.eq(null); + expect(resolverInstance._initialize).not.to.have.been.called; + }); }); diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.ts b/packages-exp/auth-exp/src/core/strategies/redirect.ts index 254f31f9c54..0794d6576dc 100644 --- a/packages-exp/auth-exp/src/core/strategies/redirect.ts +++ b/packages-exp/auth-exp/src/core/strategies/redirect.ts @@ -22,8 +22,13 @@ import { PopupRedirectResolverInternal } from '../../model/popup_redirect'; import { UserCredentialInternal } from '../../model/user'; +import { PersistenceInternal } from '../persistence'; +import { _persistenceKeyName } from '../persistence/persistence_user_manager'; +import { _getInstance } from '../util/instantiator'; import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation'; +const PENDING_REDIRECT_KEY = 'pendingRedirect'; + // We only get one redirect outcome for any one auth, so just store it // in here. const redirectOutcomeMap: Map< @@ -61,7 +66,11 @@ export class RedirectAction extends AbstractPopupRedirectOperation { let readyOutcome = redirectOutcomeMap.get(this.auth._key()); if (!readyOutcome) { try { - const result = await super.execute(); + const hasPendingRedirect = await _getAndClearPendingRedirectStatus( + this.resolver, + this.auth + ); + const result = hasPendingRedirect ? await super.execute() : null; readyOutcome = () => Promise.resolve(result); } catch (e) { readyOutcome = () => Promise.reject(e); @@ -98,6 +107,38 @@ export class RedirectAction extends AbstractPopupRedirectOperation { cleanUp(): void {} } +export async function _getAndClearPendingRedirectStatus( + resolver: PopupRedirectResolverInternal, + auth: AuthInternal +): Promise { + const key = pendingRedirectKey(auth); + const hasPendingRedirect = + (await resolverPersistence(resolver)._get(key)) === 'true'; + await resolverPersistence(resolver)._remove(key); + return hasPendingRedirect; +} + +export async function _setPendingRedirectStatus( + resolver: PopupRedirectResolverInternal, + auth: AuthInternal +): Promise { + return resolverPersistence(resolver)._set(pendingRedirectKey(auth), 'true'); +} + export function _clearRedirectOutcomes(): void { redirectOutcomeMap.clear(); } + +function resolverPersistence( + resolver: PopupRedirectResolverInternal +): PersistenceInternal { + return _getInstance(resolver._redirectPersistence); +} + +function pendingRedirectKey(auth: AuthInternal): string { + return _persistenceKeyName( + PENDING_REDIRECT_KEY, + auth.config.apiKey, + auth.name + ); +} diff --git a/packages-exp/auth-exp/src/core/util/browser.ts b/packages-exp/auth-exp/src/core/util/browser.ts index ea27c81b59a..e2b8fe5b87a 100644 --- a/packages-exp/auth-exp/src/core/util/browser.ts +++ b/packages-exp/auth-exp/src/core/util/browser.ts @@ -92,7 +92,7 @@ export function _isFirefox(ua = getUA()): boolean { return /firefox\//i.test(ua); } -export function _isSafari(userAgent: string): boolean { +export function _isSafari(userAgent = getUA()): boolean { const ua = userAgent.toLowerCase(); return ( ua.includes('safari/') && diff --git a/packages-exp/auth-exp/src/core/util/instantiator.ts b/packages-exp/auth-exp/src/core/util/instantiator.ts index 90ebc9411d8..92f40baaafe 100644 --- a/packages-exp/auth-exp/src/core/util/instantiator.ts +++ b/packages-exp/auth-exp/src/core/util/instantiator.ts @@ -46,3 +46,7 @@ export function _getInstance(cls: unknown): T { instanceCache.set(cls, instance); return instance; } + +export function _clearInstanceMap(): void { + instanceCache.clear(); +} diff --git a/packages-exp/auth-exp/src/model/popup_redirect.ts b/packages-exp/auth-exp/src/model/popup_redirect.ts index 8f90723c325..b2c658c398c 100644 --- a/packages-exp/auth-exp/src/model/popup_redirect.ts +++ b/packages-exp/auth-exp/src/model/popup_redirect.ts @@ -80,6 +80,9 @@ export interface EventManager { } export interface PopupRedirectResolverInternal extends PopupRedirectResolver { + // Whether or not to initialize the event manager early + _shouldInitProactively: boolean; + _initialize(auth: AuthInternal): Promise; _openPopup( auth: AuthInternal, diff --git a/packages-exp/auth-exp/src/platform_browser/auth.test.ts b/packages-exp/auth-exp/src/platform_browser/auth.test.ts index cfce3a4b2ca..d2104db6af2 100644 --- a/packages-exp/auth-exp/src/platform_browser/auth.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/auth.test.ts @@ -46,6 +46,7 @@ import { PopupRedirectResolverInternal } from '../model/popup_redirect'; import { UserCredentialImpl } from '../core/user/user_credential_impl'; import { UserInternal } from '../model/user'; import { _createError } from '../core/util/assert'; +import { makeMockPopupRedirectResolver } from '../../test/helpers/mock_popup_redirect_resolver'; use(sinonChai); use(chaiAsPromised); @@ -191,6 +192,28 @@ describe('core/auth/initializeAuth', () => { expect(reload._reloadWithoutSaving).not.to.have.been.called; }); + it('does not early-initialize the resolver if _shouldInitProactively is false', async () => { + const popupRedirectResolver = makeMockPopupRedirectResolver(); + const resolverInternal: PopupRedirectResolverInternal = _getInstance( + popupRedirectResolver + ); + sinon.stub(resolverInternal, '_shouldInitProactively').value(false); + sinon.spy(resolverInternal, '_initialize'); + await initAndWait(inMemoryPersistence, popupRedirectResolver); + expect(resolverInternal._initialize).not.to.have.been.called; + }); + + it('early-initializes the resolver if _shouldInitProactively is true', async () => { + const popupRedirectResolver = makeMockPopupRedirectResolver(); + const resolverInternal: PopupRedirectResolverInternal = _getInstance( + popupRedirectResolver + ); + sinon.stub(resolverInternal, '_shouldInitProactively').value(true); + sinon.spy(resolverInternal, '_initialize'); + await initAndWait(inMemoryPersistence, popupRedirectResolver); + expect(resolverInternal._initialize).to.have.been.called; + }); + it('reloads non-redirect users', async () => { sinon .stub(_getInstance(inMemoryPersistence), '_get') diff --git a/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts b/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts index 4b936a091a3..26a4deb2ec9 100644 --- a/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts +++ b/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts @@ -37,6 +37,7 @@ import { browserSessionPersistence } from './persistence/session_storage'; import { _open, AuthPopup } from './util/popup'; import { _getRedirectResult } from './strategies/redirect'; import { _getRedirectUrl } from '../core/util/handler'; +import { _isIOS, _isMobileBrowser, _isSafari } from '../core/util/browser'; /** * The special web storage event @@ -162,6 +163,11 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal { return this.originValidationPromises[key]; } + get _shouldInitProactively(): boolean { + // Mobile browsers and Safari need to optimistically initialize + return _isMobileBrowser() || _isSafari() || _isIOS(); + } + _completeRedirectFn = _getRedirectResult; } diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts index 40d2a83cf7b..a2892c89e4a 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts @@ -45,11 +45,10 @@ import { UserInternal } from '../../model/user'; import { AuthEventManager } from '../../core/auth/auth_event_manager'; import { AuthErrorCode } from '../../core/errors'; import { PersistenceInternal } from '../../core/persistence'; -import { InMemoryPersistence } from '../../core/persistence/in_memory'; import { OAuthProvider } from '../../core/providers/oauth'; import * as link from '../../core/user/link_unlink'; import { UserCredentialImpl } from '../../core/user/user_credential_impl'; -import { _getInstance } from '../../core/util/instantiator'; +import { _clearInstanceMap, _getInstance } from '../../core/util/instantiator'; import * as idpTasks from '../../core/strategies/idp'; import { getRedirectResult, @@ -60,6 +59,7 @@ import { } from './redirect'; import { FirebaseError } from '@firebase/util'; import { _clearRedirectOutcomes } from '../../core/strategies/redirect'; +import { RedirectPersistence } from '../../../test/helpers/redirect_persistence'; use(sinonChai); use(chaiAsPromised); @@ -67,8 +67,6 @@ use(chaiAsPromised); const MATCHING_EVENT_ID = 'matching-event-id'; const OTHER_EVENT_ID = 'wrong-id'; -class RedirectPersistence extends InMemoryPersistence {} - describe('platform_browser/strategies/redirect', () => { let auth: TestAuth; let eventManager: AuthEventManager; @@ -85,11 +83,15 @@ describe('platform_browser/strategies/redirect', () => { )._redirectPersistence = RedirectPersistence; auth = await testAuth(resolver); idpStubs = sinon.stub(idpTasks); + _getInstance( + RedirectPersistence + ).hasPendingRedirect = true; }); afterEach(() => { sinon.restore(); _clearRedirectOutcomes(); + _clearInstanceMap(); }); context('signInWithRedirect', () => { @@ -299,16 +301,14 @@ describe('platform_browser/strategies/redirect', () => { } async function reInitAuthWithRedirectUser(eventId: string): Promise { - const redirectPersistence: PersistenceInternal = _getInstance( + const redirectPersistence: RedirectPersistence = _getInstance( RedirectPersistence ); const mainPersistence = new MockPersistenceLayer(); const oldAuth = await testAuth(); const user = testUser(oldAuth, 'uid'); user._redirectEventId = eventId; - sinon - .stub(redirectPersistence, '_get') - .returns(Promise.resolve(user.toJSON())); + redirectPersistence.redirectUser = user.toJSON(); sinon .stub(mainPersistence, '_get') .returns(Promise.resolve(user.toJSON())); @@ -427,7 +427,9 @@ describe('platform_browser/strategies/redirect', () => { type: AuthEventType.LINK_VIA_REDIRECT }); expect(await promise).to.eq(cred); - expect(redirectPersistence._remove).to.have.been.called; + expect(redirectPersistence._remove).to.have.been.calledWith( + 'firebase:redirectUser:test-api-key:test-app' + ); expect(auth._currentUser?._redirectEventId).to.be.undefined; expect(auth.persistenceLayer.lastObjectSet?._redirectEventId).to.be .undefined; @@ -451,7 +453,9 @@ describe('platform_browser/strategies/redirect', () => { type: AuthEventType.LINK_VIA_REDIRECT }); expect(await promise).to.eq(cred); - expect(redirectPersistence._remove).not.to.have.been.called; + expect(redirectPersistence._remove).not.to.have.been.calledWith( + 'firebase:redirectUser:test-api-key:test-app' + ); expect(auth._currentUser?._redirectEventId).not.to.be.undefined; expect(auth.persistenceLayer.lastObjectSet?._redirectEventId).not.to.be .undefined; diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts index 3c56a645e2e..f1563a4f5ac 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts @@ -32,7 +32,10 @@ import { _generateEventId } from '../../core/util/event_id'; import { AuthEventType } from '../../model/popup_redirect'; import { UserInternal } from '../../model/user'; import { _withDefaultResolver } from '../../core/util/resolver'; -import { RedirectAction } from '../../core/strategies/redirect'; +import { + RedirectAction, + _setPendingRedirectStatus +} from '../../core/strategies/redirect'; /** * Authenticates a Firebase client using a full-page redirect flow. @@ -93,7 +96,10 @@ export async function _signInWithRedirect( AuthErrorCode.ARGUMENT_ERROR ); - return _withDefaultResolver(authInternal, resolver)._openRedirect( + const resolverInternal = _withDefaultResolver(authInternal, resolver); + await _setPendingRedirectStatus(resolverInternal, authInternal); + + return resolverInternal._openRedirect( authInternal, provider, AuthEventType.SIGN_IN_VIA_REDIRECT @@ -153,6 +159,7 @@ export async function _reauthenticateWithRedirect( // Allow the resolver to error before persisting the redirect user const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); + await _setPendingRedirectStatus(resolverInternal, userInternal.auth); const eventId = await prepareUserForRedirect(userInternal); return resolverInternal._openRedirect( @@ -209,8 +216,9 @@ export async function _linkWithRedirect( // Allow the resolver to error before persisting the redirect user const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); - await _assertLinkedStatus(false, userInternal, provider.providerId); + await _setPendingRedirectStatus(resolverInternal, userInternal.auth); + const eventId = await prepareUserForRedirect(userInternal); return resolverInternal._openRedirect( userInternal.auth, diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts index 3973522aa1b..b006159d05b 100644 --- a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts @@ -52,6 +52,7 @@ const INITIAL_EVENT_TIMEOUT_MS = 500; class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal { readonly _redirectPersistence = browserSessionPersistence; + readonly _shouldInitProactively = true; // This is lightweight for Cordova private readonly eventManagers = new Map(); _completeRedirectFn = _getRedirectResult; diff --git a/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts b/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts index 90098f016e5..83372936667 100644 --- a/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts +++ b/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts @@ -56,6 +56,8 @@ export function makeMockPopupRedirectResolver( _redirectPersistence?: Persistence; + _shouldInitProactively = false; + async _completeRedirectFn(): Promise {} async _originValidation(): Promise {} diff --git a/packages-exp/auth-exp/test/helpers/redirect_persistence.ts b/packages-exp/auth-exp/test/helpers/redirect_persistence.ts new file mode 100644 index 00000000000..e6a06d66838 --- /dev/null +++ b/packages-exp/auth-exp/test/helpers/redirect_persistence.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PersistenceValue } from '../../src/core/persistence'; +import { InMemoryPersistence } from '../../src/core/persistence/in_memory'; + +/** Helper class for handling redirect persistence */ +export class RedirectPersistence extends InMemoryPersistence { + hasPendingRedirect = false; + redirectUser: object | null = null; + + async _get(key: string): Promise { + if (key.includes('pendingRedirect')) { + return this.hasPendingRedirect.toString() as T; + } else if (key.includes('redirectUser')) { + return this.redirectUser as T | null; + } + + throw new Error(`Unexpected redirect persistence key requested: ${key}`); + } +}