Skip to content

Commit dc6b447

Browse files
authored
[Auth] Fix halted initialization when proactive popup resolver init fails (#5777)
* Fix halted initialization when proactive popup resolver init fails * Fix lint * Add changeset
1 parent 0818685 commit dc6b447

File tree

7 files changed

+56
-11
lines changed

7 files changed

+56
-11
lines changed

.changeset/red-actors-care.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/auth": patch
3+
---
4+
5+
Fix errors during Auth initialization when the network is unavailable

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
135135
// Initialize the resolver early if necessary (only applicable to web:
136136
// this will cause the iframe to load immediately in certain cases)
137137
if (this._popupRedirectResolver?._shouldInitProactively) {
138-
await this._popupRedirectResolver._initialize(this);
138+
// If this fails, don't halt auth loading
139+
try {
140+
await this._popupRedirectResolver._initialize(this);
141+
} catch (e) { /* Ignore the error */ }
139142
}
140143

141144
await this.initializeCurrentUser(popupRedirectResolver);

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

+10
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ describe('core/auth/initializeAuth', () => {
216216
expect(resolverInternal._initialize).to.have.been.called;
217217
});
218218

219+
it('does not halt init if resolver fails', async () => {
220+
const popupRedirectResolver = makeMockPopupRedirectResolver();
221+
const resolverInternal: PopupRedirectResolverInternal = _getInstance(
222+
popupRedirectResolver
223+
);
224+
sinon.stub(resolverInternal, '_shouldInitProactively').value(true);
225+
sinon.stub(resolverInternal, '_initialize').rejects(new Error());
226+
await expect(initAndWait(inMemoryPersistence, popupRedirectResolver)).not.to.be.rejected;
227+
});
228+
219229
it('reloads non-redirect users', async () => {
220230
sinon
221231
.stub(_getInstance<PersistenceInternal>(inMemoryPersistence), '_get')

packages/auth/src/platform_browser/iframe/gapi.test.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ use(chaiAsPromised);
3333
describe('platform_browser/iframe/gapi', () => {
3434
let library: typeof gapi;
3535
let auth: TestAuth;
36+
let loadJsStub: sinon.SinonStub;
3637
function onJsLoad(globalLoadFnName: string): void {
3738
_window().gapi = library as typeof gapi;
3839
_window()[globalLoadFnName]();
3940
}
4041

4142
beforeEach(async () => {
42-
sinon.stub(js, '_loadJS').callsFake(url => {
43+
loadJsStub = sinon.stub(js, '_loadJS').callsFake(url => {
4344
onJsLoad(url.split('onload=')[1]);
4445
return Promise.resolve(new Event('load'));
4546
});
@@ -134,4 +135,10 @@ describe('platform_browser/iframe/gapi', () => {
134135
);
135136
expect(_loadGapi(auth)).not.to.eq(firstAttempt);
136137
});
138+
139+
it('rejects if gapi itself does not load', async () => {
140+
const error = new Error();
141+
loadJsStub.rejects(error);
142+
await expect(_loadGapi(auth)).to.be.rejectedWith(error);
143+
});
137144
});

packages/auth/src/platform_browser/iframe/gapi.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ function loadGapi(auth: AuthInternal): Promise<gapi.iframes.Context> {
103103
}
104104
};
105105
// Load GApi loader.
106-
return js._loadJS(`https://apis.google.com/js/api.js?onload=${cbName}`);
106+
return js._loadJS(`https://apis.google.com/js/api.js?onload=${cbName}`).catch(e => reject(e));
107107
}
108108
}).catch(error => {
109109
// Reset cached promise to allow for retrial.

packages/auth/src/platform_browser/popup_redirect.test.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,26 @@ describe('platform_browser/popup_redirect', () => {
5454
let auth: TestAuth;
5555
let onIframeMessage: (event: GapiAuthEvent) => Promise<void>;
5656
let iframeSendStub: sinon.SinonStub;
57+
let loadGapiStub: sinon.SinonStub;
5758

5859
beforeEach(async () => {
5960
auth = await testAuth();
6061
resolver = new (browserPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolverInternal>)();
6162

6263
sinon.stub(validateOrigin, '_validateOrigin').returns(Promise.resolve());
6364
iframeSendStub = sinon.stub();
65+
loadGapiStub = sinon.stub(gapiLoader, '_loadGapi');
66+
setGapiStub();
6467

65-
sinon.stub(gapiLoader, '_loadGapi').returns(
68+
sinon.stub(authWindow._window(), 'gapi').value({
69+
iframes: {
70+
CROSS_ORIGIN_IFRAMES_FILTER: 'cross-origin-iframes-filter'
71+
}
72+
});
73+
});
74+
75+
function setGapiStub(): void {
76+
loadGapiStub.returns(
6677
Promise.resolve(({
6778
open: () =>
6879
Promise.resolve({
@@ -74,13 +85,7 @@ describe('platform_browser/popup_redirect', () => {
7485
})
7586
} as unknown) as gapi.iframes.Context)
7687
);
77-
78-
sinon.stub(authWindow._window(), 'gapi').value({
79-
iframes: {
80-
CROSS_ORIGIN_IFRAMES_FILTER: 'cross-origin-iframes-filter'
81-
}
82-
});
83-
});
88+
}
8489

8590
afterEach(() => {
8691
sinon.restore();
@@ -241,6 +246,14 @@ describe('platform_browser/popup_redirect', () => {
241246
expect(resolver._initialize(secondAuth)).to.eq(secondPromise);
242247
});
243248

249+
it('clears the cache if the initialize fails', async () => {
250+
const error = new Error();
251+
loadGapiStub.rejects(error);
252+
await expect(resolver._initialize(auth)).to.be.rejectedWith(error);
253+
setGapiStub(); // Reset the gapi load stub
254+
await expect(resolver._initialize(auth)).not.to.be.rejected;
255+
});
256+
244257
it('iframe event goes through to the manager', async () => {
245258
const manager = (await resolver._initialize(auth)) as AuthEventManager;
246259
sinon.stub(manager, 'onEvent').returns(true);

packages/auth/src/platform_browser/popup_redirect.ts

+7
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal {
111111

112112
const promise = this.initAndGetManager(auth);
113113
this.eventManagers[key] = { promise };
114+
115+
// If the promise is rejected, the key should be removed so that the
116+
// operation can be retried later.
117+
promise.catch(() => {
118+
delete this.eventManagers[key];
119+
});
120+
114121
return promise;
115122
}
116123

0 commit comments

Comments
 (0)