Skip to content

Commit ad6dfaa

Browse files
committed
Popup strategy implementation
1 parent 49a7240 commit ad6dfaa

File tree

3 files changed

+189
-12
lines changed

3 files changed

+189
-12
lines changed

packages-exp/auth-exp/demo/src/index.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,14 @@ import {
4848
unlink,
4949
getMultiFactorResolver,
5050
multiFactor,
51-
PhoneMultiFactorGenerator
51+
PhoneMultiFactorGenerator,
52+
OAuthProvider,
53+
signInWithPopup,
54+
BrowserPopupRedirectResolver
5255
} from '@firebase/auth-exp';
5356

57+
const browserPopupRedirectResolver = new BrowserPopupRedirectResolver();
58+
5459
import { config } from './config';
5560
import {
5661
log,
@@ -1174,10 +1179,10 @@ function onPopupRedirectAddCustomParam(event) {
11741179
* Performs the corresponding popup/redirect action for a generic provider.
11751180
*/
11761181
function onPopupRedirectGenericProviderClick() {
1177-
alertNotImplemented();
1178-
// var providerId = $('#popup-redirect-generic-providerid').val();
1179-
// var provider = new firebase.auth.OAuthProvider(providerId);
1180-
// signInWithPopupRedirect(provider);
1182+
// alertNotImplemented();
1183+
var providerId = $('#popup-redirect-generic-providerid').val();
1184+
var provider = new OAuthProvider(providerId);
1185+
signInWithPopupRedirect(provider);
11811186
}
11821187

11831188
/**
@@ -1224,6 +1229,9 @@ function onPopupRedirectProviderClick(event) {
12241229
* sign in.
12251230
*/
12261231
function signInWithPopupRedirect(provider) {
1232+
var glob = {
1233+
signInWithPopup,
1234+
}
12271235
var action = $('input[name=popup-redirect-action]:checked').val();
12281236
var type = $('input[name=popup-redirect-type]:checked').val();
12291237
var method = null;
@@ -1279,20 +1287,21 @@ function signInWithPopupRedirect(provider) {
12791287
console.log('Provider:');
12801288
console.log(provider);
12811289
if (type == 'popup') {
1282-
inst[method](provider).then(function(response) {
1290+
glob[method](inst, provider, browserPopupRedirectResolver).then(function(response) {
12831291
console.log('Popup response:');
12841292
console.log(response);
12851293
alertSuccess(action + ' with ' + provider['providerId'] + ' successful!');
12861294
logAdditionalUserInfo(response);
12871295
onAuthSuccess(activeUser());
12881296
}, onAuthError);
12891297
} else {
1290-
try {
1291-
inst[method](provider).catch(onAuthError);
1292-
} catch (error) {
1293-
console.log('Error while calling ' + method);
1294-
console.error(error);
1295-
}
1298+
alertNotImplemented();
1299+
// try {
1300+
// inst[method](provider).catch(onAuthError);
1301+
// } catch (error) {
1302+
// console.log('Error while calling ' + method);
1303+
// console.error(error);
1304+
// }
12961305
}
12971306
}
12981307

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import * as externs from '@firebase/auth-types-exp';
19+
20+
import { Auth } from '../../model/auth';
21+
import {
22+
AuthEvent, AuthEventConsumer, AuthEventType, EventFilter, EventManager, PopupRedirectResolver
23+
} from '../../model/popup_redirect';
24+
import { UserCredential } from '../../model/user';
25+
import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors';
26+
import { Delay } from '../util/delay';
27+
import { _generateEventId } from '../util/event_id';
28+
import { AuthPopup } from '../util/popup';
29+
import * as idp from './idp';
30+
31+
const AUTH_EVENT_TIMEOUT = new Delay(2000, 2001);
32+
const WINDOW_CLOSE_TIMEOUT = new Delay(2000, 10000);
33+
34+
interface PendingPromise {
35+
resolve: (cred: UserCredential) => void;
36+
reject: (error: Error) => void;
37+
}
38+
39+
export async function signInWithPopup(authExtern: externs.Auth, provider: externs.AuthProvider, resolverExtern: externs.PopupRedirectResolver) {
40+
const auth = authExtern as Auth;
41+
const resolver = resolverExtern as PopupRedirectResolver;
42+
43+
const resultManager = new PopupResultManager(auth, AuthEventType.SIGN_IN_VIA_POPUP, idp._signIn, provider, resolver);
44+
const cred = await resultManager.getNewPendingPromise();
45+
46+
await auth.updateCurrentUser(cred.user);
47+
return cred;
48+
}
49+
50+
export class PopupResultManager implements AuthEventConsumer {
51+
private static pendingPromise: PendingPromise | null = null;
52+
private authWindow: AuthPopup | null = null;
53+
private pollId: number | null = null;
54+
private eventManager: EventManager | null = null;
55+
56+
constructor(
57+
private readonly auth: Auth,
58+
readonly filter: AuthEventType,
59+
private readonly idpTask: idp.IdpTask,
60+
private readonly provider: externs.AuthProvider,
61+
private readonly resolver: PopupRedirectResolver) {
62+
63+
}
64+
65+
getNewPendingPromise(
66+
): Promise<UserCredential> {
67+
if (PopupResultManager.pendingPromise) {
68+
// There was already a pending promise. Expire it.
69+
this.broadcastResult(
70+
null,
71+
AUTH_ERROR_FACTORY.create(AuthErrorCode.EXPIRED_POPUP_REQUEST, {
72+
appName: this.auth.name,
73+
})
74+
);
75+
}
76+
77+
return new Promise<UserCredential>(async (resolve, reject) => {
78+
PopupResultManager.pendingPromise = { resolve, reject };
79+
80+
this.eventManager = await this.resolver.initialize(this.auth);
81+
const eventId = _generateEventId();
82+
this.authWindow = await this.resolver.openPopup(this.auth, this.provider, AuthEventType.SIGN_IN_VIA_POPUP, eventId);
83+
this.authWindow.associatedEvent = eventId;
84+
85+
this.eventManager.registerConsumer(this);
86+
87+
// Handle user closure. Notice this does *not* use await
88+
this.pollUserCancellation(this.auth.name);
89+
});
90+
}
91+
92+
isMatchingEvent(eventId: string | null): boolean {
93+
return !!eventId && this.authWindow?.associatedEvent === eventId;
94+
}
95+
96+
async onAuthEvent(event: AuthEvent): Promise<void> {
97+
98+
const { urlResponse, sessionId, postBody, tenantId, error } = event;
99+
if (error) {
100+
this.broadcastResult(null, error);
101+
return;
102+
}
103+
104+
const params: idp.IdpTaskParams = {
105+
auth: this.auth,
106+
requestUri: urlResponse!,
107+
sessionId: sessionId!,
108+
tenantId: tenantId || undefined,
109+
postBody: postBody || undefined,
110+
};
111+
112+
try {
113+
this.broadcastResult(await this.idpTask(params));
114+
} catch (e) {
115+
this.broadcastResult(null, e);
116+
}
117+
}
118+
119+
private broadcastResult(cred: UserCredential | null, error?: Error) {
120+
if (this.authWindow) {
121+
this.authWindow.close();
122+
}
123+
124+
if (this.pollId) {
125+
window.clearTimeout(this.pollId);
126+
}
127+
128+
if (PopupResultManager.pendingPromise) {
129+
if (error) {
130+
PopupResultManager.pendingPromise.reject(error);
131+
} else {
132+
PopupResultManager.pendingPromise.resolve(cred!);
133+
}
134+
}
135+
136+
this.cleanUp();
137+
}
138+
139+
private cleanUp() {
140+
this.authWindow = null;
141+
PopupResultManager.pendingPromise = null;
142+
this.pollId = null;
143+
this.eventManager?.unregisterConsumer(this);
144+
}
145+
146+
private pollUserCancellation(appName: string) {
147+
const poll = () => {
148+
if (this.authWindow?.window.closed) {
149+
this.pollId = window.setTimeout(() => {
150+
this.pollId = null;
151+
this.broadcastResult(
152+
null,
153+
AUTH_ERROR_FACTORY.create(AuthErrorCode.POPUP_CLOSED_BY_USER, {
154+
appName,
155+
})
156+
);
157+
}, AUTH_EVENT_TIMEOUT.get());
158+
}
159+
160+
this.pollId = window.setTimeout(poll, WINDOW_CLOSE_TIMEOUT.get());
161+
};
162+
163+
poll();
164+
}
165+
}

packages-exp/auth-exp/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,6 @@ export async function deleteUser(user: externs.User): Promise<void> {
120120
export { PhoneMultiFactorGenerator } from './mfa/assertions/phone';
121121
export { getMultiFactorResolver } from './mfa/mfa_resolver';
122122
export { multiFactor } from './mfa/mfa_user';
123+
export { BrowserPopupRedirectResolver } from './platform_browser/popup_redirect';
124+
export { signInWithPopup } from './core/strategies/popup';
125+
export { OAuthProvider } from './core/providers/oauth';

0 commit comments

Comments
 (0)