Skip to content

Commit 4fc04e9

Browse files
authored
Merge d13b99c into c1e9229
2 parents c1e9229 + d13b99c commit 4fc04e9

31 files changed

+1193
-281
lines changed

packages-exp/auth-exp/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { Auth } from '@firebase/auth-types-exp';
2121
import { initializeAuth } from './src';
2222
import { registerAuth } from './src/core/auth/register';
2323
import { ClientPlatform } from './src/core/util/version';
24-
import { browserLocalPersistence } from './src/platform_browser/persistence/browser';
24+
import { browserLocalPersistence } from './src/platform_browser/persistence/local_storage';
2525
import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db';
2626
import { browserPopupRedirectResolver } from './src/platform_browser/popup_redirect';
2727

@@ -31,10 +31,8 @@ export * from './src';
3131
// Additional DOM dependend functionality
3232

3333
// persistence
34-
export {
35-
browserLocalPersistence,
36-
browserSessionPersistence
37-
} from './src/platform_browser/persistence/browser';
34+
export { browserLocalPersistence } from './src/platform_browser/persistence/local_storage';
35+
export { browserSessionPersistence } from './src/platform_browser/persistence/session_storage';
3836
export { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db';
3937

4038
// providers

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import { indexedDBLocalPersistence } from './src/platform_browser/persistence/in
2929
// Core functionality shared by all clients
3030
export * from './src';
3131

32+
// persistence
33+
export { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db';
34+
3235
registerAuth(ClientPlatform.WORKER);
3336

3437
export function getAuth(app = getApp()): Auth {

packages-exp/auth-exp/src/core/auth/auth_impl.test.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,4 +265,126 @@ describe('core/auth/auth_impl', () => {
265265
});
266266
});
267267
});
268+
269+
describe('#_onStorageEvent', () => {
270+
let authStateCallback: sinon.SinonSpy;
271+
let idTokenCallback: sinon.SinonSpy;
272+
273+
beforeEach(async () => {
274+
authStateCallback = sinon.spy();
275+
idTokenCallback = sinon.spy();
276+
auth._onAuthStateChanged(authStateCallback);
277+
auth._onIdTokenChanged(idTokenCallback);
278+
await auth.updateCurrentUser(null); // force event handlers to clear out
279+
authStateCallback.resetHistory();
280+
idTokenCallback.resetHistory();
281+
});
282+
283+
context('previously logged out', () => {
284+
context('still logged out', () => {
285+
it('should do nothing', async () => {
286+
await auth._onStorageEvent();
287+
288+
expect(authStateCallback).not.to.have.been.called;
289+
expect(idTokenCallback).not.to.have.been.called;
290+
});
291+
});
292+
293+
context('now logged in', () => {
294+
let user: User;
295+
296+
beforeEach(() => {
297+
user = testUser(auth, 'uid');
298+
persistenceStub.get.returns(Promise.resolve(user.toJSON()));
299+
});
300+
301+
it('should update the current user', async () => {
302+
await auth._onStorageEvent();
303+
304+
expect(auth.currentUser?.toJSON()).to.eql(user.toJSON());
305+
expect(authStateCallback).to.have.been.called;
306+
expect(idTokenCallback).to.have.been.called;
307+
});
308+
});
309+
});
310+
311+
context('previously logged in', () => {
312+
let user: User;
313+
314+
beforeEach(async () => {
315+
user = testUser(auth, 'uid', undefined, true);
316+
await auth.updateCurrentUser(user);
317+
authStateCallback.resetHistory();
318+
idTokenCallback.resetHistory();
319+
});
320+
321+
context('now logged out', () => {
322+
beforeEach(() => {
323+
persistenceStub.get.returns(Promise.resolve(null));
324+
});
325+
326+
it('should log out', async () => {
327+
await auth._onStorageEvent();
328+
329+
expect(auth.currentUser).to.be.null;
330+
expect(authStateCallback).to.have.been.called;
331+
expect(idTokenCallback).to.have.been.called;
332+
});
333+
});
334+
335+
context('still logged in as same user', () => {
336+
it('should do nothing if nothing changed', async () => {
337+
persistenceStub.get.returns(Promise.resolve(user.toJSON()));
338+
339+
await auth._onStorageEvent();
340+
341+
expect(auth.currentUser?.toJSON()).to.eql(user.toJSON());
342+
expect(authStateCallback).not.to.have.been.called;
343+
expect(idTokenCallback).not.to.have.been.called;
344+
});
345+
346+
it('should update fields if they have changed', async () => {
347+
const userObj = user.toJSON();
348+
userObj['displayName'] = 'other-name';
349+
persistenceStub.get.returns(Promise.resolve(userObj));
350+
351+
await auth._onStorageEvent();
352+
353+
expect(auth.currentUser?.uid).to.eq(user.uid);
354+
expect(auth.currentUser?.displayName).to.eq('other-name');
355+
expect(authStateCallback).not.to.have.been.called;
356+
expect(idTokenCallback).not.to.have.been.called;
357+
});
358+
359+
it('should update tokens if they have changed', async () => {
360+
const userObj = user.toJSON();
361+
(userObj['stsTokenManager'] as any)['accessToken'] =
362+
'new-access-token';
363+
persistenceStub.get.returns(Promise.resolve(userObj));
364+
365+
await auth._onStorageEvent();
366+
367+
expect(auth.currentUser?.uid).to.eq(user.uid);
368+
expect(auth.currentUser?.stsTokenManager.accessToken).to.eq(
369+
'new-access-token'
370+
);
371+
expect(authStateCallback).not.to.have.been.called;
372+
expect(idTokenCallback).to.have.been.called;
373+
});
374+
});
375+
376+
context('now logged in as different user', () => {
377+
it('should re-login as the new user', async () => {
378+
const newUser = testUser(auth, 'other-uid', undefined, true);
379+
persistenceStub.get.returns(Promise.resolve(newUser.toJSON()));
380+
381+
await auth._onStorageEvent();
382+
383+
expect(auth.currentUser?.toJSON()).to.eql(newUser.toJSON());
384+
expect(authStateCallback).to.have.been.called;
385+
expect(idTokenCallback).to.have.been.called;
386+
});
387+
});
388+
});
389+
});
268390
});

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,34 @@ export class AuthImplCompat<T extends User> implements Auth, _FirebaseService {
107107
return this._initializationPromise;
108108
}
109109

110+
/**
111+
* If the persistence is changed in another window, the user manager will let us know
112+
*/
113+
async _onStorageEvent(): Promise<void> {
114+
const user = await this.persistenceManager!.getCurrentUser();
115+
116+
if (!this.currentUser && !user) {
117+
// No change, do nothing (was signed out and remained signed out).
118+
return;
119+
}
120+
121+
// If the same user is to be synchronized.
122+
if (this.currentUser && user && this.currentUser.uid === user.uid) {
123+
// Data update, simply copy data changes.
124+
this.currentUser._copy(user);
125+
// If tokens changed from previous user tokens, this will trigger
126+
// notifyAuthListeners_.
127+
await this.currentUser.getIdToken();
128+
return;
129+
}
130+
131+
// Update current Auth state. Either a new login or logout.
132+
await this.updateCurrentUser(user);
133+
// TODO: If a new user is signed in, enabled popup and redirect on that user.
134+
// Notify external Auth changes of Auth change event.
135+
this.notifyAuthListeners();
136+
}
137+
110138
_createUser(params: UserParameters): T {
111139
return new this._userProvider(params);
112140
}

packages-exp/auth-exp/src/core/auth/firebase_internal.test.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,9 @@ describe('src/core/auth/firebase_internal', () => {
7070
beforeEach(async () => {
7171
user = testUser(auth, 'uid', undefined, true);
7272
await auth.updateCurrentUser(user);
73-
sinon.stub(user.stsTokenManager, 'getToken').returns(
74-
Promise.resolve({
75-
accessToken: 'access-token',
76-
refreshToken: 'refresh-tken',
77-
wasRefreshed: true
78-
})
79-
);
73+
sinon
74+
.stub(user.stsTokenManager, 'getToken')
75+
.returns(Promise.resolve('new-access-token'));
8076
sinon
8177
.stub(user, '_startProactiveRefresh')
8278
.callsFake(() => (isProactiveRefresh = true));

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.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import * as sinonChai from 'sinon-chai';
2323
import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth';
2424
import { UserImpl } from '../user/user_impl';
2525
import { _getInstance } from '../util/instantiator';
26-
import { Persistence, PersistenceType } from './';
26+
import { Persistence, PersistenceType, StorageEventListener } from './';
2727
import { inMemoryPersistence } from './in_memory';
2828
import { PersistenceUserManager } from './persistence_user_manager';
2929

@@ -42,7 +42,9 @@ function makePersistence(
4242
get() {
4343
return Promise.resolve(null);
4444
},
45-
remove: async () => {}
45+
remove: async () => {},
46+
addListener(_key: string, _listener: StorageEventListener) {},
47+
removeListener(_key: string, _listener: StorageEventListener) {}
4648
};
4749

4850
const stub = sinon.stub(persistence);

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

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

1818
import { ApiKey, AppName, Auth } from '../../model/auth';
19+
import { User } from '../../model/user';
1920
import { PersistedBlob, Persistence } from '../persistence';
21+
import { UserImpl } from '../user/user_impl';
2022
import { _getInstance } from '../util/instantiator';
2123
import { inMemoryPersistence } from './in_memory';
22-
import { User } from '../../model/user';
23-
import { UserImpl } from '../user/user_impl';
2424

2525
export const _AUTH_USER_KEY_NAME = 'authUser';
2626
export const _REDIRECT_USER_KEY_NAME = 'redirectUser';
@@ -38,6 +38,7 @@ function _persistenceKeyName(
3838
export class PersistenceUserManager {
3939
private readonly fullUserKey: string;
4040
private readonly fullPersistenceKey: string;
41+
4142
private constructor(
4243
public persistence: Persistence,
4344
private readonly auth: Auth,
@@ -50,6 +51,10 @@ export class PersistenceUserManager {
5051
config.apiKey,
5152
name
5253
);
54+
this.persistence.addListener(
55+
this.fullUserKey,
56+
auth._onStorageEvent.bind(auth)
57+
);
5358
}
5459

5560
setCurrentUser(user: User): Promise<void> {
@@ -84,6 +89,13 @@ export class PersistenceUserManager {
8489
}
8590
}
8691

92+
delete(): void {
93+
this.persistence.removeListener(
94+
this.fullUserKey,
95+
this.auth._onStorageEvent
96+
);
97+
}
98+
8799
static async create(
88100
auth: Auth,
89101
persistenceHierarchy: Persistence[],

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
ProviderUserInfo
3232
} from '../../api/account_management/account';
3333
import { _reloadWithoutSaving, reload } from './reload';
34-
import { UserMetadata } from './user_impl';
34+
import { UserMetadata } from './user_metadata';
3535

3636
use(chaiAsPromised);
3737
use(sinonChai);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
ProviderUserInfo
2323
} from '../../api/account_management/account';
2424
import { User } from '../../model/user';
25-
import { UserMetadata } from './user_impl';
25+
import { UserMetadata } from './user_metadata';
2626
import { assert } from '../util/assert';
2727
import { AuthErrorCode } from '../errors';
2828

packages-exp/auth-exp/src/core/user/token_manager.test.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,7 @@ describe('core/user/token_manager', () => {
110110
expect(stsTokenManager.refreshToken).to.eq('new-refresh-token');
111111
expect(stsTokenManager.expirationTime).to.eq(now + 3_600_000);
112112

113-
expect(tokens).to.eql({
114-
accessToken: 'new-access-token',
115-
refreshToken: 'new-refresh-token',
116-
wasRefreshed: true
117-
});
113+
expect(tokens).to.eql('new-access-token');
118114
});
119115

120116
it('refreshes the token if token is expired', async () => {
@@ -130,11 +126,7 @@ describe('core/user/token_manager', () => {
130126
expect(stsTokenManager.refreshToken).to.eq('new-refresh-token');
131127
expect(stsTokenManager.expirationTime).to.eq(now + 3_600_000);
132128

133-
expect(tokens).to.eql({
134-
accessToken: 'new-access-token',
135-
refreshToken: 'new-refresh-token',
136-
wasRefreshed: true
137-
});
129+
expect(tokens).to.eql('new-access-token');
138130
});
139131
});
140132

@@ -162,11 +154,7 @@ describe('core/user/token_manager', () => {
162154
});
163155

164156
const tokens = (await stsTokenManager.getToken(auth))!;
165-
expect(tokens).to.eql({
166-
accessToken: 'token',
167-
refreshToken: 'refresh',
168-
wasRefreshed: false
169-
});
157+
expect(tokens).to.eql('token');
170158
});
171159
});
172160

0 commit comments

Comments
 (0)