Skip to content

Commit 57c73a0

Browse files
sam-gcyuchenshi
andauthored
[Auth] set key in storage before redirect, check this key before checking for redirect events (#4550)
* Set key in storage before redirect, only open iframe if that key is present * Formatting, License * Update packages-exp/auth-exp/src/platform_browser/auth.test.ts Co-authored-by: Yuchen Shi <[email protected]> * Update packages-exp/auth-exp/src/platform_browser/auth.test.ts Co-authored-by: Yuchen Shi <[email protected]> * PR feedback, fix compat tests * Formatting Co-authored-by: Yuchen Shi <[email protected]>
1 parent 5019599 commit 57c73a0

File tree

17 files changed

+228
-34
lines changed

17 files changed

+228
-34
lines changed

packages-exp/auth-compat-exp/src/platform.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,13 @@ export function _getClientPlatform(): impl.ClientPlatform {
162162
return impl.ClientPlatform.BROWSER;
163163
}
164164

165+
/** Quick check that indicates the platform *may* be Cordova */
166+
export function _isLikelyCordova(): boolean {
167+
return _isAndroidOrIosCordovaScheme() && typeof document !== 'undefined';
168+
}
169+
165170
export async function _isCordova(): Promise<boolean> {
166-
if (!_isAndroidOrIosCordovaScheme() || typeof document === 'undefined') {
171+
if (!_isLikelyCordova()) {
167172
return false;
168173
}
169174

packages-exp/auth-compat-exp/src/popup_redirect.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,45 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => {
126126
);
127127
});
128128
});
129+
130+
context('_shouldInitProactively', () => {
131+
it('returns true if platform may be cordova', () => {
132+
sinon.stub(platform, '_isLikelyCordova').returns(true);
133+
expect(compatResolver._shouldInitProactively).to.be.true;
134+
});
135+
136+
it('returns true if cordova is false but browser value is true', () => {
137+
sinon
138+
.stub(
139+
exp._getInstance<exp.PopupRedirectResolverInternal>(
140+
exp.browserPopupRedirectResolver
141+
),
142+
'_shouldInitProactively'
143+
)
144+
.value(true);
145+
sinon.stub(platform, '_isLikelyCordova').returns(false);
146+
expect(compatResolver._shouldInitProactively).to.be.true;
147+
});
148+
149+
it('returns false if not cordova and not browser early init', () => {
150+
sinon
151+
.stub(
152+
exp._getInstance<exp.PopupRedirectResolverInternal>(
153+
exp.browserPopupRedirectResolver
154+
),
155+
'_shouldInitProactively'
156+
)
157+
.value(false);
158+
sinon.stub(platform, '_isLikelyCordova').returns(false);
159+
expect(compatResolver._shouldInitProactively).to.be.false;
160+
});
161+
});
129162
});
130163

131164
class FakeResolver implements exp.PopupRedirectResolverInternal {
132165
_completeRedirectFn = async (): Promise<null> => null;
133166
_redirectPersistence = exp.inMemoryPersistence;
167+
_shouldInitProactively = true;
134168

135169
_initialize(): Promise<exp.EventManager> {
136170
throw new Error('Method not implemented.');

packages-exp/auth-compat-exp/src/popup_redirect.ts

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

1818
import * as exp from '@firebase/auth-exp/internal';
19-
import { _isCordova } from './platform';
19+
import { _isCordova, _isLikelyCordova } from './platform';
2020

2121
const _assert: typeof exp._assert = exp._assert;
22+
const BROWSER_RESOLVER: exp.PopupRedirectResolverInternal = exp._getInstance(
23+
exp.browserPopupRedirectResolver
24+
);
25+
const CORDOVA_RESOLVER: exp.PopupRedirectResolverInternal = exp._getInstance(
26+
exp.cordovaPopupRedirectResolver
27+
);
2228

2329
/** Platform-agnostic popup-redirect resolver */
2430
export class CompatPopupRedirectResolver
@@ -40,11 +46,7 @@ export class CompatPopupRedirectResolver
4046
// We haven't yet determined whether or not we're in Cordova; go ahead
4147
// and determine that state now.
4248
const isCordova = await _isCordova();
43-
this.underlyingResolver = exp._getInstance(
44-
isCordova
45-
? exp.cordovaPopupRedirectResolver
46-
: exp.browserPopupRedirectResolver
47-
);
49+
this.underlyingResolver = isCordova ? CORDOVA_RESOLVER : BROWSER_RESOLVER;
4850
return this.assertedUnderlyingResolver._initialize(auth);
4951
}
5052

@@ -87,6 +89,10 @@ export class CompatPopupRedirectResolver
8789
return this.assertedUnderlyingResolver._originValidation(auth);
8890
}
8991

92+
get _shouldInitProactively(): boolean {
93+
return _isLikelyCordova() || BROWSER_RESOLVER._shouldInitProactively;
94+
}
95+
9096
private get assertedUnderlyingResolver(): exp.PopupRedirectResolverInternal {
9197
_assert(this.underlyingResolver, exp.AuthErrorCode.INTERNAL_ERROR);
9298
return this.underlyingResolver;

packages-exp/auth-exp/src/core/auth/auth_impl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
132132
return;
133133
}
134134

135+
// Initialize the resolver early if necessary (only applicable to web:
136+
// this will cause the iframe to load immediately in certain cases)
137+
if (this._popupRedirectResolver?._shouldInitProactively) {
138+
await this._popupRedirectResolver._initialize(this);
139+
}
140+
135141
await this.initializeCurrentUser(popupRedirectResolver);
136142

137143
if (this._deleted) {

packages-exp/auth-exp/src/core/auth/initialize.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ describe('core/auth/initialize', () => {
117117
cb(true);
118118
}
119119
async _originValidation(): Promise<void> {}
120+
_shouldInitProactively = false;
120121
async _completeRedirectFn(
121122
_auth: Auth,
122123
_resolver: PopupRedirectResolver,

packages-exp/auth-exp/src/core/strategies/redirect.test.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import {
2222
ProviderId
2323
} from '../../model/public_types';
2424
import * as sinon from 'sinon';
25-
import { _getInstance } from '../util/instantiator';
25+
import * as sinonChai from 'sinon-chai';
26+
import { _clearInstanceMap, _getInstance } from '../util/instantiator';
2627
import {
2728
MockPersistenceLayer,
2829
TestAuth,
@@ -39,24 +40,24 @@ import {
3940
PopupRedirectResolverInternal
4041
} from '../../model/popup_redirect';
4142
import { BASE_AUTH_EVENT } from '../../../test/helpers/iframe_event';
42-
import { PersistenceInternal } from '../persistence';
43-
import { InMemoryPersistence } from '../persistence/in_memory';
4443
import { UserCredentialImpl } from '../user/user_credential_impl';
4544
import * as idpTasks from '../strategies/idp';
46-
import { expect } from 'chai';
45+
import { expect, use } from 'chai';
4746
import { AuthErrorCode } from '../errors';
47+
import { RedirectPersistence } from '../../../test/helpers/redirect_persistence';
48+
49+
use(sinonChai);
4850

4951
const MATCHING_EVENT_ID = 'matching-event-id';
5052
const OTHER_EVENT_ID = 'wrong-id';
5153

52-
class RedirectPersistence extends InMemoryPersistence {}
53-
5454
describe('core/strategies/redirect', () => {
5555
let auth: AuthInternal;
5656
let redirectAction: RedirectAction;
5757
let eventManager: AuthEventManager;
5858
let resolver: PopupRedirectResolver;
5959
let idpStubs: sinon.SinonStubbedInstance<typeof idpTasks>;
60+
let redirectPersistence: RedirectPersistence;
6061

6162
beforeEach(async () => {
6263
eventManager = new AuthEventManager(({} as unknown) as TestAuth);
@@ -67,11 +68,16 @@ describe('core/strategies/redirect', () => {
6768
)._redirectPersistence = RedirectPersistence;
6869
auth = await testAuth();
6970
redirectAction = new RedirectAction(auth, _getInstance(resolver), false);
71+
redirectPersistence = _getInstance(RedirectPersistence);
72+
73+
// Default to has redirect for most test
74+
redirectPersistence.hasPendingRedirect = true;
7075
});
7176

7277
afterEach(() => {
7378
sinon.restore();
7479
_clearRedirectOutcomes();
80+
_clearInstanceMap();
7581
});
7682

7783
function iframeEvent(event: Partial<AuthEvent>): void {
@@ -86,16 +92,11 @@ describe('core/strategies/redirect', () => {
8692
}
8793

8894
async function reInitAuthWithRedirectUser(eventId: string): Promise<void> {
89-
const redirectPersistence: PersistenceInternal = _getInstance(
90-
RedirectPersistence
91-
);
9295
const mainPersistence = new MockPersistenceLayer();
9396
const oldAuth = await testAuth();
9497
const user = testUser(oldAuth, 'uid');
9598
user._redirectEventId = eventId;
96-
sinon
97-
.stub(redirectPersistence, '_get')
98-
.returns(Promise.resolve(user.toJSON()));
99+
redirectPersistence.redirectUser = user.toJSON();
99100
sinon.stub(mainPersistence, '_get').returns(Promise.resolve(user.toJSON()));
100101

101102
auth = await testAuth(resolver, mainPersistence);
@@ -194,4 +195,18 @@ describe('core/strategies/redirect', () => {
194195
expect(await promise).to.eq(cred);
195196
expect(await redirectAction.execute()).to.eq(cred);
196197
});
198+
199+
it('bypasses initialization if no key set', async () => {
200+
await reInitAuthWithRedirectUser(MATCHING_EVENT_ID);
201+
const resolverInstance = _getInstance<PopupRedirectResolverInternal>(
202+
resolver
203+
);
204+
205+
sinon.spy(resolverInstance, '_initialize');
206+
redirectPersistence.hasPendingRedirect = false;
207+
208+
expect(await redirectAction.execute()).to.eq(null);
209+
expect(await redirectAction.execute()).to.eq(null);
210+
expect(resolverInstance._initialize).not.to.have.been.called;
211+
});
197212
});

packages-exp/auth-exp/src/core/strategies/redirect.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ import {
2222
PopupRedirectResolverInternal
2323
} from '../../model/popup_redirect';
2424
import { UserCredentialInternal } from '../../model/user';
25+
import { PersistenceInternal } from '../persistence';
26+
import { _persistenceKeyName } from '../persistence/persistence_user_manager';
27+
import { _getInstance } from '../util/instantiator';
2528
import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation';
2629

30+
const PENDING_REDIRECT_KEY = 'pendingRedirect';
31+
2732
// We only get one redirect outcome for any one auth, so just store it
2833
// in here.
2934
const redirectOutcomeMap: Map<
@@ -61,7 +66,11 @@ export class RedirectAction extends AbstractPopupRedirectOperation {
6166
let readyOutcome = redirectOutcomeMap.get(this.auth._key());
6267
if (!readyOutcome) {
6368
try {
64-
const result = await super.execute();
69+
const hasPendingRedirect = await _getAndClearPendingRedirectStatus(
70+
this.resolver,
71+
this.auth
72+
);
73+
const result = hasPendingRedirect ? await super.execute() : null;
6574
readyOutcome = () => Promise.resolve(result);
6675
} catch (e) {
6776
readyOutcome = () => Promise.reject(e);
@@ -98,6 +107,38 @@ export class RedirectAction extends AbstractPopupRedirectOperation {
98107
cleanUp(): void {}
99108
}
100109

110+
export async function _getAndClearPendingRedirectStatus(
111+
resolver: PopupRedirectResolverInternal,
112+
auth: AuthInternal
113+
): Promise<boolean> {
114+
const key = pendingRedirectKey(auth);
115+
const hasPendingRedirect =
116+
(await resolverPersistence(resolver)._get(key)) === 'true';
117+
await resolverPersistence(resolver)._remove(key);
118+
return hasPendingRedirect;
119+
}
120+
121+
export async function _setPendingRedirectStatus(
122+
resolver: PopupRedirectResolverInternal,
123+
auth: AuthInternal
124+
): Promise<void> {
125+
return resolverPersistence(resolver)._set(pendingRedirectKey(auth), 'true');
126+
}
127+
101128
export function _clearRedirectOutcomes(): void {
102129
redirectOutcomeMap.clear();
103130
}
131+
132+
function resolverPersistence(
133+
resolver: PopupRedirectResolverInternal
134+
): PersistenceInternal {
135+
return _getInstance(resolver._redirectPersistence);
136+
}
137+
138+
function pendingRedirectKey(auth: AuthInternal): string {
139+
return _persistenceKeyName(
140+
PENDING_REDIRECT_KEY,
141+
auth.config.apiKey,
142+
auth.name
143+
);
144+
}

packages-exp/auth-exp/src/core/util/browser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export function _isFirefox(ua = getUA()): boolean {
9292
return /firefox\//i.test(ua);
9393
}
9494

95-
export function _isSafari(userAgent: string): boolean {
95+
export function _isSafari(userAgent = getUA()): boolean {
9696
const ua = userAgent.toLowerCase();
9797
return (
9898
ua.includes('safari/') &&

packages-exp/auth-exp/src/core/util/instantiator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ export function _getInstance<T>(cls: unknown): T {
4646
instanceCache.set(cls, instance);
4747
return instance;
4848
}
49+
50+
export function _clearInstanceMap(): void {
51+
instanceCache.clear();
52+
}

packages-exp/auth-exp/src/model/popup_redirect.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export interface EventManager {
8080
}
8181

8282
export interface PopupRedirectResolverInternal extends PopupRedirectResolver {
83+
// Whether or not to initialize the event manager early
84+
_shouldInitProactively: boolean;
85+
8386
_initialize(auth: AuthInternal): Promise<EventManager>;
8487
_openPopup(
8588
auth: AuthInternal,

packages-exp/auth-exp/src/platform_browser/auth.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { PopupRedirectResolverInternal } from '../model/popup_redirect';
4646
import { UserCredentialImpl } from '../core/user/user_credential_impl';
4747
import { UserInternal } from '../model/user';
4848
import { _createError } from '../core/util/assert';
49+
import { makeMockPopupRedirectResolver } from '../../test/helpers/mock_popup_redirect_resolver';
4950

5051
use(sinonChai);
5152
use(chaiAsPromised);
@@ -191,6 +192,28 @@ describe('core/auth/initializeAuth', () => {
191192
expect(reload._reloadWithoutSaving).not.to.have.been.called;
192193
});
193194

195+
it('does not early-initialize the resolver if _shouldInitProactively is false', async () => {
196+
const popupRedirectResolver = makeMockPopupRedirectResolver();
197+
const resolverInternal: PopupRedirectResolverInternal = _getInstance(
198+
popupRedirectResolver
199+
);
200+
sinon.stub(resolverInternal, '_shouldInitProactively').value(false);
201+
sinon.spy(resolverInternal, '_initialize');
202+
await initAndWait(inMemoryPersistence, popupRedirectResolver);
203+
expect(resolverInternal._initialize).not.to.have.been.called;
204+
});
205+
206+
it('early-initializes the resolver if _shouldInitProactively is true', async () => {
207+
const popupRedirectResolver = makeMockPopupRedirectResolver();
208+
const resolverInternal: PopupRedirectResolverInternal = _getInstance(
209+
popupRedirectResolver
210+
);
211+
sinon.stub(resolverInternal, '_shouldInitProactively').value(true);
212+
sinon.spy(resolverInternal, '_initialize');
213+
await initAndWait(inMemoryPersistence, popupRedirectResolver);
214+
expect(resolverInternal._initialize).to.have.been.called;
215+
});
216+
194217
it('reloads non-redirect users', async () => {
195218
sinon
196219
.stub(_getInstance<PersistenceInternal>(inMemoryPersistence), '_get')

packages-exp/auth-exp/src/platform_browser/popup_redirect.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { browserSessionPersistence } from './persistence/session_storage';
3737
import { _open, AuthPopup } from './util/popup';
3838
import { _getRedirectResult } from './strategies/redirect';
3939
import { _getRedirectUrl } from '../core/util/handler';
40+
import { _isIOS, _isMobileBrowser, _isSafari } from '../core/util/browser';
4041

4142
/**
4243
* The special web storage event
@@ -162,6 +163,11 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal {
162163
return this.originValidationPromises[key];
163164
}
164165

166+
get _shouldInitProactively(): boolean {
167+
// Mobile browsers and Safari need to optimistically initialize
168+
return _isMobileBrowser() || _isSafari() || _isIOS();
169+
}
170+
165171
_completeRedirectFn = _getRedirectResult;
166172
}
167173

0 commit comments

Comments
 (0)