Skip to content

Commit 75f6f25

Browse files
committed
Implement cross-window browser events for auth-next
1 parent 55d3c26 commit 75f6f25

File tree

10 files changed

+382
-68
lines changed

10 files changed

+382
-68
lines changed

packages-exp/auth-exp/src/core/persistence/in_memory.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717

1818
import * as externs from '@firebase/auth-types-exp';
1919

20-
import { Persistence, PersistenceType, PersistenceValue } from '../persistence';
20+
import {
21+
Persistence,
22+
PersistenceType,
23+
PersistenceValue,
24+
StorageEventListener
25+
} from '../persistence';
2126

2227
export class InMemoryPersistence implements Persistence {
2328
static type: 'NONE' = 'NONE';
@@ -40,6 +45,16 @@ export class InMemoryPersistence implements Persistence {
4045
async remove(key: string): Promise<void> {
4146
delete this.storage[key];
4247
}
48+
49+
addListener(_key: string, _listener: StorageEventListener): void {
50+
// Listeners are not supported for in-memory storage since it cannot be shared across windows/workers
51+
return;
52+
}
53+
54+
removeListener(_key: string, _listener: StorageEventListener): void {
55+
// Listeners are not supported for in-memory storage since it cannot be shared across windows/workers
56+
return;
57+
}
4358
}
4459

4560
export const inMemoryPersistence: externs.Persistence = InMemoryPersistence;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,16 @@ export type PersistenceValue = PersistedBlob | string;
3131

3232
export const STORAGE_AVAILABLE_KEY = '__sak';
3333

34+
export interface StorageEventListener {
35+
(value: PersistenceValue | null): void;
36+
}
37+
3438
export interface Persistence {
3539
type: PersistenceType;
3640
isAvailable(): Promise<boolean>;
3741
set(key: string, value: PersistenceValue): Promise<void>;
3842
get<T extends PersistenceValue>(key: string): Promise<T | null>;
3943
remove(key: string): Promise<void>;
44+
addListener(key: string, listener: StorageEventListener): void;
45+
removeListener(key: string, listener: StorageEventListener): void;
4046
}

packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
*/
1717

1818
import { ApiKey, AppName, Auth } from '../../model/auth';
19-
import { PersistedBlob, Persistence } from '../persistence';
20-
import { _getInstance } from '../util/instantiator';
21-
import { inMemoryPersistence } from './in_memory';
2219
import { User } from '../../model/user';
20+
import { AuthErrorCode } from '../errors';
21+
import { PersistedBlob, Persistence, PersistenceValue } from '../persistence';
2322
import { UserImpl } from '../user/user_impl';
23+
import { assert } from '../util/assert';
24+
import { _getInstance } from '../util/instantiator';
25+
import { inMemoryPersistence } from './in_memory';
2426

2527
export const _AUTH_USER_KEY_NAME = 'authUser';
2628
export const _REDIRECT_USER_KEY_NAME = 'redirectUser';
@@ -35,9 +37,15 @@ function _persistenceKeyName(
3537
return `${PERSISTENCE_NAMESPACE}:${key}:${apiKey}:${appName}`;
3638
}
3739

40+
export interface UserEventListener {
41+
(): void;
42+
}
43+
3844
export class PersistenceUserManager {
3945
private readonly fullUserKey: string;
4046
private readonly fullPersistenceKey: string;
47+
private listener: UserEventListener | null = null;
48+
4149
private constructor(
4250
public persistence: Persistence,
4351
private readonly auth: Auth,
@@ -84,6 +92,29 @@ export class PersistenceUserManager {
8492
}
8593
}
8694

95+
_onStorageEvent(_value: PersistenceValue | null): void {
96+
assert(this.listener, AuthErrorCode.INTERNAL_ERROR, {
97+
appName: this.auth.name
98+
});
99+
this.listener();
100+
}
101+
102+
async addListener(listener: UserEventListener): Promise<void> {
103+
assert(!this.listener, AuthErrorCode.INTERNAL_ERROR, {
104+
appName: this.auth.name
105+
});
106+
this.listener = listener;
107+
this.persistence.addListener(this.fullUserKey, this._onStorageEvent);
108+
}
109+
110+
removeListener(listener: UserEventListener): void {
111+
assert(this.listener === listener, AuthErrorCode.INTERNAL_ERROR, {
112+
appName: this.auth.name
113+
});
114+
this.persistence.removeListener(this.fullUserKey, this._onStorageEvent);
115+
this.listener = null;
116+
}
117+
87118
static async create(
88119
auth: Auth,
89120
persistenceHierarchy: Persistence[],

packages-exp/auth-exp/src/core/user/reload.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,7 @@ export async function _reloadWithoutSaving(user: User): Promise<void> {
5151
phoneNumber: coreAccount.phoneNumber || null,
5252
tenantId: coreAccount.tenantId || null,
5353
providerData: mergeProviderData(user.providerData, newProviderData),
54-
metadata: new UserMetadata(
55-
coreAccount.createdAt,
56-
coreAccount.lastLoginAt
57-
)
54+
metadata: new UserMetadata(coreAccount.createdAt, coreAccount.lastLoginAt)
5855
};
5956

6057
Object.assign(user, updates);

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

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { isIE, getUA } from '@firebase/util';
19+
20+
interface NavigatorStandalone extends Navigator {
21+
standalone?: unknown;
22+
}
23+
24+
interface Document {
25+
documentMode?: number;
26+
}
27+
1828
/**
1929
* Enums for Browser name.
2030
*/
@@ -40,36 +50,31 @@ export function _getBrowserName(userAgent: string): BrowserName | string {
4050
const ua = userAgent.toLowerCase();
4151
if (ua.includes('opera/') || ua.includes('opr/') || ua.includes('opios/')) {
4252
return BrowserName.OPERA;
43-
} else if (ua.includes('iemobile')) {
53+
} else if (_isIEMobile(ua)) {
4454
// Windows phone IEMobile browser.
4555
return BrowserName.IEMOBILE;
4656
} else if (ua.includes('msie') || ua.includes('trident/')) {
4757
return BrowserName.IE;
4858
} else if (ua.includes('edge/')) {
4959
return BrowserName.EDGE;
50-
} else if (ua.includes('firefox/')) {
60+
} else if (_isFirefox(ua)) {
5161
return BrowserName.FIREFOX;
5262
} else if (ua.includes('silk/')) {
5363
return BrowserName.SILK;
54-
} else if (ua.includes('blackberry')) {
64+
} else if (_isBlackBerry(ua)) {
5565
// Blackberry browser.
5666
return BrowserName.BLACKBERRY;
57-
} else if (ua.includes('webos')) {
67+
} else if (_isWebOS(ua)) {
5868
// WebOS default browser.
5969
return BrowserName.WEBOS;
60-
} else if (
61-
ua.includes('safari/') &&
62-
!ua.includes('chrome/') &&
63-
!ua.includes('crios/') &&
64-
!ua.includes('android')
65-
) {
70+
} else if (_isSafari(ua)) {
6671
return BrowserName.SAFARI;
6772
} else if (
68-
(ua.includes('chrome/') || ua.includes('crios/')) &&
73+
(ua.includes('chrome/') || _isChromeIOS(ua)) &&
6974
!ua.includes('edge/')
7075
) {
7176
return BrowserName.CHROME;
72-
} else if (ua.includes('android')) {
77+
} else if (_isAndroid(ua)) {
7378
// Android stock browser.
7479
return BrowserName.ANDROID;
7580
} else {
@@ -82,3 +87,71 @@ export function _getBrowserName(userAgent: string): BrowserName | string {
8287
}
8388
return BrowserName.OTHER;
8489
}
90+
91+
export function _isFirefox(ua: string): boolean {
92+
return /firefox\//i.test(ua);
93+
}
94+
95+
export function _isSafari(userAgent: string): boolean {
96+
const ua = userAgent.toLowerCase();
97+
return (
98+
ua.includes('safari/') &&
99+
!ua.includes('chrome/') &&
100+
!ua.includes('crios/') &&
101+
!ua.includes('android')
102+
);
103+
}
104+
105+
export function _isChromeIOS(ua: string): boolean {
106+
return /crios\//i.test(ua);
107+
}
108+
109+
export function _isIEMobile(ua: string): boolean {
110+
return /iemobile/i.test(ua);
111+
}
112+
113+
export function _isAndroid(ua: string): boolean {
114+
return /android\//i.test(ua);
115+
}
116+
117+
export function _isBlackBerry(ua: string): boolean {
118+
return /blackberry/i.test(ua);
119+
}
120+
121+
export function _isWebOS(ua: string): boolean {
122+
return /webos/i.test(ua);
123+
}
124+
125+
export function _isIOS(ua: string): boolean {
126+
return /iphone|ipad|ipod/i.test(ua);
127+
}
128+
129+
export function _isIOSStandalone(ua: string): boolean {
130+
return _isIOS(ua) && !!(window.navigator as NavigatorStandalone)?.standalone;
131+
}
132+
133+
export function _isIE10(): boolean {
134+
return isIE() && (document as Document).documentMode === 10;
135+
}
136+
137+
export function _isMobileBrowser(ua: string = getUA()): boolean {
138+
// TODO: implement getBrowserName equivalent for OS.
139+
return (
140+
_isIOS(ua) ||
141+
_isAndroid(ua) ||
142+
_isWebOS(ua) ||
143+
_isBlackBerry(ua) ||
144+
/windows phone/i.test(ua) ||
145+
_isIEMobile(ua)
146+
);
147+
}
148+
149+
export function _isIframe(): boolean {
150+
try {
151+
// Check that the current window is not the top window.
152+
// If so, return true.
153+
return !!(window && window !== window.top);
154+
} catch (e) {
155+
return false;
156+
}
157+
}

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

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)