Skip to content

Commit 0c70559

Browse files
sam-gcavolkovi
authored andcommitted
Make the _getInstance() instantiator a util for the whole SDK (#3369)
* Make the _getInstance() instantiator a util for the whole SDK * PR feedback, added some debugAsserts * Formatting
1 parent a788667 commit 0c70559

File tree

11 files changed

+85
-43
lines changed

11 files changed

+85
-43
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ import { FirebaseError } from '@firebase/util';
2626
import { testUser } from '../../../test/mock_auth';
2727
import { Auth } from '../../model/auth';
2828
import { User } from '../../model/user';
29-
import { _getInstance, Persistence } from '../persistence';
29+
import { Persistence } from '../persistence';
3030
import { browserLocalPersistence } from '../persistence/browser';
3131
import { inMemoryPersistence } from '../persistence/in_memory';
3232
import { PersistenceUserManager } from '../persistence/persistence_user_manager';
33+
import { _getInstance } from '../util/instantiator';
3334
import * as navigator from '../util/navigator';
3435
import { _getClientVersion, ClientPlatform } from '../util/version';
3536
import {
@@ -109,7 +110,7 @@ describe('core/auth/auth_impl', () => {
109110
describe('#setPersistence', () => {
110111
it('swaps underlying persistence', async () => {
111112
const newPersistence = browserLocalPersistence;
112-
const newStub = sinon.stub(_getInstance(newPersistence));
113+
const newStub = sinon.stub(_getInstance<Persistence>(newPersistence));
113114
persistenceStub.get.returns(
114115
Promise.resolve(testUser(auth, 'test').toPlainObject())
115116
);
@@ -308,7 +309,7 @@ describe('core/auth/initializeAuth', () => {
308309

309310
it('pulls the user from storage', async () => {
310311
sinon
311-
.stub(_getInstance(inMemoryPersistence), 'get')
312+
.stub(_getInstance<Persistence>(inMemoryPersistence), 'get')
312313
.returns(Promise.resolve(testUser({}, 'uid').toPlainObject()));
313314
const auth = await initAndWait(inMemoryPersistence);
314315
expect(auth.currentUser!.uid).to.eq('uid');

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ import {
3131
import { Auth, Dependencies } from '../../model/auth';
3232
import { User } from '../../model/user';
3333
import { AuthErrorCode } from '../errors';
34-
import { _getInstance, Persistence } from '../persistence';
34+
import { Persistence } from '../persistence';
3535
import { PersistenceUserManager } from '../persistence/persistence_user_manager';
3636
import { assert } from '../util/assert';
37+
import { _getInstance } from '../util/instantiator';
3738
import { _getUserLanguage } from '../util/navigator';
3839
import { _getClientVersion, ClientPlatform } from '../util/version';
3940

@@ -218,7 +219,7 @@ export function initializeAuth(
218219
const hierarchy = (Array.isArray(persistence)
219220
? persistence
220221
: [persistence]
221-
).map(_getInstance);
222+
).map<Persistence>(_getInstance);
222223
const { apiKey, authDomain } = app.options;
223224

224225
// TODO: platform needs to be determined using heuristics

packages-exp/auth-exp/src/core/persistence/browser.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { expect } from 'chai';
1919
import * as sinon from 'sinon';
2020

2121
import { testUser } from '../../../test/mock_auth';
22-
import { _getInstance, PersistedBlob, Persistence, PersistenceType } from './';
22+
import { _getInstance } from '../util/instantiator';
23+
import { PersistedBlob, Persistence, PersistenceType } from './';
2324
import { browserLocalPersistence, browserSessionPersistence } from './browser';
2425

2526
describe('core/persistence/browser', () => {
@@ -74,7 +75,7 @@ describe('core/persistence/browser', () => {
7475
});
7576

7677
describe('browserSessionPersistence', () => {
77-
const persistence = _getInstance(browserSessionPersistence);
78+
const persistence: Persistence = _getInstance(browserSessionPersistence);
7879

7980
it('should work with persistence type', async () => {
8081
const key = 'my-super-special-persistence-type';

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
import { expect } from 'chai';
1919

2020
import { testUser } from '../../../test/mock_auth';
21-
import { _getInstance, PersistenceType } from './';
21+
import { _getInstance } from '../util/instantiator';
22+
import { Persistence, PersistenceType } from './';
2223
import { inMemoryPersistence } from './in_memory';
2324

24-
const persistence = _getInstance(inMemoryPersistence);
25+
const persistence: Persistence = _getInstance(inMemoryPersistence);
2526

2627
describe('core/persistence/in_memory', () => {
2728
it('should work with persistence type', async () => {

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

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

18-
import * as externs from '@firebase/auth-types-exp';
19-
2018
export enum PersistenceType {
2119
SESSION = 'SESSION',
2220
LOCAL = 'LOCAL',
@@ -42,27 +40,3 @@ export interface Persistence {
4240
get<T extends PersistenceValue>(key: string): Promise<T | null>;
4341
remove(key: string): Promise<void>;
4442
}
45-
46-
/**
47-
* We can't directly export all of the different types of persistence as
48-
* constants: this would cause tree-shaking libraries to keep all of the
49-
* various persistence classes in the bundle, even if they're not used, since
50-
* the system can't prove those constructors don't side-effect. Instead, the
51-
* persistence classes themselves all have a static method called _getInstance()
52-
* which does the instantiation.
53-
*/
54-
export interface PersistenceInstantiator extends externs.Persistence {
55-
new (): Persistence;
56-
}
57-
58-
const persistenceCache: Map<externs.Persistence, Persistence> = new Map();
59-
60-
export function _getInstance(cls: externs.Persistence): Persistence {
61-
if (persistenceCache.has(cls)) {
62-
return persistenceCache.get(cls)!;
63-
}
64-
65-
const persistence = new (cls as PersistenceInstantiator)();
66-
persistenceCache.set(cls, persistence);
67-
return persistence;
68-
}

packages-exp/auth-exp/src/core/persistence/indexed_db.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ import { expect } from 'chai';
1919
import * as sinon from 'sinon';
2020

2121
import { testUser } from '../../../test/mock_auth';
22-
import { _getInstance, PersistenceType } from './';
22+
import { _getInstance } from '../util/instantiator';
23+
import { Persistence, PersistenceType } from './';
2324
import { indexedDBLocalPersistence } from './indexed_db';
2425

25-
const persistence = _getInstance(indexedDBLocalPersistence);
26+
const persistence: Persistence = _getInstance(indexedDBLocalPersistence);
2627

2728
describe('core/persistence/indexed_db', () => {
2829
afterEach(sinon.restore);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import * as sinonChai from 'sinon-chai';
2323
import { testAuth, testUser } from '../../../test/mock_auth';
2424
import { Auth } from '../../model/auth';
2525
import { UserImpl } from '../user/user_impl';
26-
import { _getInstance, Persistence, PersistenceType } from './';
26+
import { _getInstance } from '../util/instantiator';
27+
import { Persistence, PersistenceType } from './';
2728
import { inMemoryPersistence } from './in_memory';
2829
import { PersistenceUserManager } from './persistence_user_manager';
2930

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717

1818
import { ApiKey, AppName, Auth } from '../../model/auth';
1919
import { User } from '../../model/user';
20-
import { _getInstance, PersistedBlob, Persistence } from '../persistence';
20+
import { PersistedBlob, Persistence } from '../persistence';
2121
import { UserImpl } from '../user/user_impl';
22+
import { _getInstance } from '../util/instantiator';
2223
import { inMemoryPersistence } from './in_memory';
2324

2425
export const _AUTH_USER_KEY_NAME = 'authUser';

packages-exp/auth-exp/src/core/persistence/react_native.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import { expect } from 'chai';
2020
import { ReactNativeAsyncStorage } from '@firebase/auth-types-exp';
2121

2222
import { testUser } from '../../../test/mock_auth';
23-
import { _getInstance, PersistedBlob, PersistenceType } from './';
23+
import { _getInstance } from '../util/instantiator';
24+
import { PersistedBlob, Persistence, PersistenceType } from './';
2425
import { getReactNativePersistence } from './react_native';
2526

2627
/**
@@ -48,7 +49,9 @@ class FakeAsyncStorage implements ReactNativeAsyncStorage {
4849

4950
describe('core/persistence/react', () => {
5051
const fakeAsyncStorage = new FakeAsyncStorage();
51-
const persistence = _getInstance(getReactNativePersistence(fakeAsyncStorage));
52+
const persistence: Persistence = _getInstance(
53+
getReactNativePersistence(fakeAsyncStorage)
54+
);
5255

5356
beforeEach(() => {
5457
fakeAsyncStorage.clear();

packages-exp/auth-exp/src/core/persistence/index.test.ts renamed to packages-exp/auth-exp/src/core/util/instantiator.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
import { expect } from 'chai';
1919

20-
import { _getInstance } from './';
20+
import { _getInstance } from './instantiator';
2121

22-
describe('src/core/persistence/index', () => {
22+
describe('src/core/util/instantiator', () => {
2323
context('_getInstance', () => {
2424
// All tests define their own classes since the Class object is used in the
2525
// global map.
@@ -66,5 +66,15 @@ describe('src/core/persistence/index', () => {
6666
expect(_getInstance(PersistenceA)).to.eq(a);
6767
expect(_getInstance(PersistenceB)).to.eq(b);
6868
});
69+
70+
it('instantiates any class', () => {
71+
class Test {}
72+
73+
const a = _getInstance(Test);
74+
const b = _getInstance(Test);
75+
76+
expect(a).to.be.instanceOf(Test);
77+
expect(a).to.eq(b);
78+
});
6979
});
7080
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 { debugAssert } from './assert';
19+
20+
/**
21+
* Our API has a lot of one-off constants that are used to do things.
22+
* Unfortunately we can't export these as classes instantiated directly since
23+
* the constructor may side effect and therefore can't be proven to be safely
24+
* culled. Instead, we export these classes themselves as a lowerCamelCase
25+
* constant, and instantiate them under the hood.
26+
*/
27+
export interface SingletonInstantiator<T> {
28+
new (): T;
29+
}
30+
31+
const instanceCache: Map<unknown, unknown> = new Map();
32+
33+
export function _getInstance<T>(cls: unknown): T {
34+
debugAssert(cls instanceof Function, 'Expected a class definition');
35+
let instance = instanceCache.get(cls) as T | undefined;
36+
37+
if (instance) {
38+
debugAssert(
39+
instance instanceof cls,
40+
'Instance stored in cache mismatched with class'
41+
);
42+
return instance;
43+
}
44+
45+
instance = new (cls as SingletonInstantiator<T>)();
46+
instanceCache.set(cls, instance);
47+
return instance;
48+
}

0 commit comments

Comments
 (0)