Skip to content

Commit 7124db2

Browse files
committed
Update event manager and abstract popup redirect operation to handle redirect events
1 parent 37e930c commit 7124db2

File tree

4 files changed

+82
-36
lines changed

4 files changed

+82
-36
lines changed

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

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,89 @@
1616
*/
1717

1818
import {
19-
AuthEvent,
20-
AuthEventConsumer,
21-
EventManager
19+
AuthEvent, AuthEventConsumer, AuthEventType, EventManager
2220
} from '../../model/popup_redirect';
2321
import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors';
2422

2523
export class AuthEventManager implements EventManager {
2624
private readonly consumers: Set<AuthEventConsumer> = new Set();
25+
private queuedRedirectEvent: AuthEvent | null = null;
26+
private hasHandledPotentialRedirect = false;
2727

2828
constructor(private readonly appName: string) {}
2929

3030
registerConsumer(authEventConsumer: AuthEventConsumer): void {
3131
this.consumers.add(authEventConsumer);
32+
33+
if (this.queuedRedirectEvent && this.isEventForConsumer(this.queuedRedirectEvent, authEventConsumer)) {
34+
this.sendToConsumer(this.queuedRedirectEvent, authEventConsumer);
35+
this.queuedRedirectEvent = null;
36+
}
3237
}
3338

3439
unregisterConsumer(authEventConsumer: AuthEventConsumer): void {
3540
this.consumers.delete(authEventConsumer);
3641
}
3742

38-
onEvent(event: AuthEvent): void {
43+
onEvent(event: AuthEvent): boolean {
44+
let handled = false;
3945
this.consumers.forEach(consumer => {
40-
if (
41-
consumer.filter === event.type &&
42-
consumer.isMatchingEvent(event.eventId)
43-
) {
44-
if (event.error) {
45-
console.error('ERROR');
46-
const code =
47-
(event.error.code?.split('auth/')[1] as AuthErrorCode) ||
48-
AuthErrorCode.INTERNAL_ERROR;
49-
consumer.onError(
50-
AUTH_ERROR_FACTORY.create(code, {
51-
appName: this.appName
52-
})
53-
);
54-
} else {
55-
consumer.onAuthEvent(event);
56-
}
46+
if (this.isEventForConsumer(event, consumer)) {
47+
handled = true;
48+
this.sendToConsumer(event, consumer);
5749
}
5850
});
51+
52+
// The redirect event is always available immediately, unlike popup
53+
// events (which happen in the normal app lifetime). Since the user
54+
// may open the iframe (through a popup method) before getRedirectResult()
55+
// is called, we need to queue up the redirect event so the user has access
56+
// to it later. On the other hand, if we get a "unknown" auth event with
57+
// the message "no-auth-event", we know there will never be a redirect event
58+
// for this session.
59+
if (event.type === AuthEventType.UNKNOWN && event.error?.code === `auth/${AuthErrorCode.NO_AUTH_EVENT}`) {
60+
this.hasHandledPotentialRedirect = true;
61+
return true;
62+
63+
} else if (!handled && isRedirectEvent(event.type) && !this.hasHandledPotentialRedirect) {
64+
this.queuedRedirectEvent = event;
65+
handled = true;
66+
}
67+
68+
this.hasHandledPotentialRedirect = this.hasHandledPotentialRedirect || isRedirectEvent(event.type);
69+
70+
return handled;
71+
}
72+
73+
private sendToConsumer(event: AuthEvent, consumer: AuthEventConsumer): void {
74+
if (event.error) {
75+
console.error('ERROR');
76+
const code =
77+
(event.error.code?.split('auth/')[1] as AuthErrorCode) ||
78+
AuthErrorCode.INTERNAL_ERROR;
79+
consumer.onError(
80+
AUTH_ERROR_FACTORY.create(code, {
81+
appName: this.appName
82+
})
83+
);
84+
} else {
85+
consumer.onAuthEvent(event);
86+
}
87+
}
88+
89+
private isEventForConsumer(event: AuthEvent, consumer: AuthEventConsumer): boolean {
90+
const eventIdMatches = consumer.eventId === null || (!!event.eventId && event.eventId === consumer.eventId);
91+
return consumer.filter.includes(event.type) && eventIdMatches;
92+
}
93+
}
94+
95+
function isRedirectEvent(type: AuthEventType): boolean {
96+
switch (type) {
97+
case AuthEventType.SIGN_IN_VIA_REDIRECT:
98+
case AuthEventType.LINK_VIA_REDIRECT:
99+
case AuthEventType.REAUTH_VIA_REDIRECT:
100+
return true;
101+
default:
102+
return false;
59103
}
60104
}

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ import { FirebaseError } from '@firebase/util';
1919

2020
import { Auth } from '../../model/auth';
2121
import {
22-
AuthEvent,
23-
AuthEventConsumer,
24-
AuthEventType,
25-
EventManager,
26-
PopupRedirectResolver
22+
AuthEvent, AuthEventConsumer, AuthEventType, EventManager, PopupRedirectResolver
2723
} from '../../model/popup_redirect';
2824
import { User, UserCredential } from '../../model/user';
2925
import { AuthErrorCode } from '../errors';
@@ -43,15 +39,18 @@ export abstract class AbstractPopupRedirectOperation
4339
implements AuthEventConsumer {
4440
private pendingPromise: PendingPromise | null = null;
4541
private eventManager: EventManager | null = null;
42+
readonly filter: AuthEventType[];
4643

4744
abstract eventId: string | null;
4845

4946
constructor(
5047
protected readonly auth: Auth,
51-
readonly filter: AuthEventType,
48+
filter: AuthEventType|AuthEventType[],
5249
protected readonly resolver: PopupRedirectResolver,
5350
protected user?: User
54-
) {}
51+
) {
52+
this.filter = Array.isArray(filter) ? filter : [filter];
53+
}
5554

5655
abstract onExecution(): Promise<void>;
5756

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818
import * as externs from '@firebase/auth-types-exp';
1919

2020
import { Auth } from '../../model/auth';
21-
import {
22-
AuthEventType,
23-
PopupRedirectResolver
24-
} from '../../model/popup_redirect';
21+
import { AuthEventType, PopupRedirectResolver } from '../../model/popup_redirect';
2522
import { User } from '../../model/user';
2623
import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors';
2724
import { Delay } from '../util/delay';
@@ -100,7 +97,7 @@ class PopupOperation extends AbstractPopupRedirectOperation {
10097

10198
constructor(
10299
auth: Auth,
103-
readonly filter: AuthEventType,
100+
filter: AuthEventType,
104101
private readonly provider: externs.AuthProvider,
105102
resolver: PopupRedirectResolver,
106103
user?: User
@@ -118,7 +115,7 @@ class PopupOperation extends AbstractPopupRedirectOperation {
118115
this.authWindow = await this.resolver._openPopup(
119116
this.auth,
120117
this.provider,
121-
this.filter,
118+
this.filter[0], // There's always one, see constructor
122119
eventId
123120
);
124121
this.authWindow.associatedEvent = eventId;

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ export interface AuthEvent {
6262
}
6363

6464
export interface AuthEventConsumer {
65-
readonly filter: AuthEventType;
66-
isMatchingEvent(eventId: string | null): boolean;
65+
readonly filter: AuthEventType[];
66+
eventId: string|null;
6767
onAuthEvent(event: AuthEvent): unknown;
6868
onError(error: FirebaseError): unknown;
6969
}
@@ -81,4 +81,10 @@ export interface PopupRedirectResolver extends externs.PopupRedirectResolver {
8181
authType: AuthEventType,
8282
eventId?: string
8383
): Promise<AuthPopup>;
84+
_openRedirect(
85+
auth: Auth,
86+
provider: externs.AuthProvider,
87+
authType: AuthEventType,
88+
eventId?: string
89+
): Promise<never>;
8490
}

0 commit comments

Comments
 (0)