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
+ }
0 commit comments