Skip to content

Commit 5c1a83e

Browse files
authored
Add Provider.initialize() (#4595)
* let factory accept options * add initialize() to Provider * use provider.initialize in perf * use provider.initialize in Auth * use provider.initialize in firestore * fix errors * Create clever-apricots-look.md
1 parent b6a7a59 commit 5c1a83e

File tree

23 files changed

+161
-57
lines changed

23 files changed

+161
-57
lines changed

.changeset/clever-apricots-look.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@firebase/component": minor
3+
"@firebase/database": patch
4+
"@firebase/firestore": patch
5+
"@firebase/functions": patch
6+
"@firebase/remote-config": patch
7+
"@firebase/storage": patch
8+
---
9+
10+
Component facotry now takes an options object. And added `Provider.initialize()` that can be used to pass an options object to the component factory.

packages-exp/analytics-compat/src/index.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,13 @@ declare module '@firebase/component' {
4141
}
4242

4343
const factory: InstanceFactory<'analytics-compat'> = (
44-
container: ComponentContainer,
45-
regionOrCustomDomain?: string
44+
container: ComponentContainer
4645
) => {
4746
// Dependencies
4847
const app = container.getProvider('app-compat').getImmediate();
4948
const analyticsServiceExp = container
5049
.getProvider('analytics-exp')
51-
.getImmediate({
52-
identifier: regionOrCustomDomain
53-
});
50+
.getImmediate();
5451

5552
return new AnalyticsService(app as FirebaseApp, analyticsServiceExp);
5653
};

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth {
3434
_fail(auth, AuthErrorCode.ALREADY_INITIALIZED);
3535
}
3636

37-
const auth = provider.getImmediate() as AuthImpl;
38-
_initializeAuthInstance(auth, deps);
37+
const auth = provider.initialize({ options: deps }) as AuthImpl;
3938

4039
return auth;
4140
}

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { _assert } from '../util/assert';
2525
import { _getClientVersion, ClientPlatform } from '../util/version';
2626
import { _castAuth, AuthImpl, DefaultConfig } from './auth_impl';
2727
import { AuthInterop } from './firebase_internal';
28+
import { Dependencies } from '../../model/auth';
29+
import { _initializeAuthInstance } from './initialize';
2830

2931
export const enum _ComponentName {
3032
AUTH = 'auth-exp',
@@ -53,7 +55,7 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
5355
_registerComponent(
5456
new Component(
5557
_ComponentName.AUTH,
56-
container => {
58+
(container, { options: deps }: { options?: Dependencies }) => {
5759
const app = container.getProvider('app-exp').getImmediate()!;
5860
const { apiKey, authDomain } = app.options;
5961
return (app => {
@@ -66,7 +68,11 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
6668
apiScheme: DefaultConfig.API_SCHEME,
6769
sdkClientVersion: _getClientVersion(clientPlatform)
6870
};
69-
return new AuthImpl(app, config);
71+
72+
const authInstance = new AuthImpl(app, config);
73+
_initializeAuthInstance(authInstance, deps);
74+
75+
return authInstance;
7076
})(app);
7177
},
7278
ComponentType.PUBLIC

packages-exp/functions-exp/src/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const DEFAULT_REGION = 'us-central1';
3030
export function registerFunctions(fetchImpl: typeof fetch): void {
3131
const factory: InstanceFactory<'functions'> = (
3232
container: ComponentContainer,
33-
regionOrCustomDomain?: string
33+
{ instanceIdentifier: regionOrCustomDomain }
3434
) => {
3535
// Dependencies
3636
const app = container.getProvider('app-exp').getImmediate();

packages-exp/performance-exp/src/index.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ export function initializePerformance(
7070
throw ERROR_FACTORY.create(ErrorCode.ALREADY_INITIALIZED);
7171
}
7272

73-
const perfInstance = provider.getImmediate() as PerformanceController;
74-
perfInstance._init(settings);
73+
const perfInstance = provider.initialize({
74+
options: settings
75+
}) as PerformanceController;
7576
return perfInstance;
7677
}
7778

@@ -89,7 +90,8 @@ export function trace(
8990
}
9091

9192
const factory: InstanceFactory<'performance-exp'> = (
92-
container: ComponentContainer
93+
container: ComponentContainer,
94+
{ options: settings }: { options?: PerformanceSettings }
9395
) => {
9496
// Dependencies
9597
const app = container.getProvider('app-exp').getImmediate();
@@ -104,7 +106,10 @@ const factory: InstanceFactory<'performance-exp'> = (
104106
throw ERROR_FACTORY.create(ErrorCode.NO_WINDOW);
105107
}
106108
setupApi(window);
107-
return new PerformanceController(app, installations);
109+
const perfInstance = new PerformanceController(app, installations);
110+
perfInstance._init(settings);
111+
112+
return perfInstance;
108113
};
109114

110115
function registerPerformance(): void {

packages-exp/remote-config-compat/src/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import firebase, { _FirebaseNamespace } from '@firebase/app-compat';
1919
import {
2020
Component,
2121
ComponentContainer,
22-
ComponentType
22+
ComponentType,
23+
InstanceFactoryOptions
2324
} from '@firebase/component';
2425
import { RemoteConfigCompatImpl } from './remoteConfig';
2526
import { name as packageName, version } from '../package.json';
@@ -48,7 +49,7 @@ function registerRemoteConfigCompat(
4849

4950
function remoteConfigFactory(
5051
container: ComponentContainer,
51-
namespace?: string
52+
{ instanceIdentifier: namespace }: InstanceFactoryOptions
5253
): RemoteConfigCompatImpl {
5354
const app = container.getProvider('app-compat').getImmediate();
5455
// The following call will always succeed because rc `import {...} from '@firebase/remote-config-exp'`

packages-exp/remote-config-exp/src/register.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import {
2222
import {
2323
Component,
2424
ComponentType,
25-
ComponentContainer
25+
ComponentContainer,
26+
InstanceFactoryOptions
2627
} from '@firebase/component';
2728
import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger';
2829
import { RemoteConfig } from './public_types';
@@ -53,7 +54,7 @@ export function registerRemoteConfig(): void {
5354

5455
function remoteConfigFactory(
5556
container: ComponentContainer,
56-
namespace?: string
57+
{ instanceIdentifier: namespace }: InstanceFactoryOptions
5758
): RemoteConfig {
5859
/* Dependencies */
5960
// getImmediate for FirebaseApp will always succeed

packages/component/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ export {
2323
InstanceFactory,
2424
InstantiationMode,
2525
NameServiceMapping,
26-
Name
26+
Name,
27+
InstanceFactoryOptions
2728
} from './src/types';

packages/component/src/provider.test.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,34 @@ describe('Provider', () => {
101101
expect(() => provider.setComponent(component)).to.not.throw();
102102
});
103103

104+
describe('initialize()', () => {
105+
it('throws if the provider is already initialized', () => {
106+
provider.setComponent(getFakeComponent('test', () => ({})));
107+
provider.initialize();
108+
109+
expect(() => provider.initialize()).to.throw();
110+
});
111+
112+
it('throws if the component has not been registered', () => {
113+
expect(() => provider.initialize()).to.throw();
114+
});
115+
116+
it('accepts an options parameter and passes it to the instance factory', () => {
117+
const options = {
118+
configurable: true,
119+
test: true
120+
};
121+
provider.setComponent(
122+
getFakeComponent('test', (_container, opts) => ({
123+
options: opts.options
124+
}))
125+
);
126+
const instance = provider.initialize({ options });
127+
128+
expect((instance as any).options).to.deep.equal(options);
129+
});
130+
});
131+
104132
describe('Provider (multipleInstances = false)', () => {
105133
describe('getImmediate()', () => {
106134
it('throws if the service is not available', () => {
@@ -155,7 +183,7 @@ describe('Provider', () => {
155183
});
156184
});
157185

158-
describe('provideFactory()', () => {
186+
describe('setComponent()', () => {
159187
it('instantiates the service if there is a pending promise and the service is eager', () => {
160188
// create a pending promise
161189
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -320,7 +348,7 @@ describe('Provider', () => {
320348
});
321349
});
322350

323-
describe('provideFactory()', () => {
351+
describe('setComponent()', () => {
324352
it('instantiates services for the pending promises for all instance identifiers', async () => {
325353
/* eslint-disable @typescript-eslint/no-floating-promises */
326354
// create 3 promises for 3 different identifiers

packages/component/src/provider.ts

+50-14
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
import { Deferred } from '@firebase/util';
1919
import { ComponentContainer } from './component_container';
2020
import { DEFAULT_ENTRY_NAME } from './constants';
21-
import { InstantiationMode, Name, NameServiceMapping } from './types';
21+
import {
22+
InitializeOptions,
23+
InstantiationMode,
24+
Name,
25+
NameServiceMapping
26+
} from './types';
2227
import { Component } from './component';
2328

2429
/**
@@ -51,7 +56,9 @@ export class Provider<T extends Name> {
5156
this.instancesDeferred.set(normalizedIdentifier, deferred);
5257
// If the service instance is available, resolve the promise with it immediately
5358
try {
54-
const instance = this.getOrInitializeService(normalizedIdentifier);
59+
const instance = this.getOrInitializeService({
60+
instanceIdentifier: normalizedIdentifier
61+
});
5562
if (instance) {
5663
deferred.resolve(instance);
5764
}
@@ -92,7 +99,9 @@ export class Provider<T extends Name> {
9299
// if multipleInstances is not supported, use the default name
93100
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
94101
try {
95-
const instance = this.getOrInitializeService(normalizedIdentifier);
102+
const instance = this.getOrInitializeService({
103+
instanceIdentifier: normalizedIdentifier
104+
});
96105

97106
if (!instance) {
98107
if (optional) {
@@ -129,7 +138,7 @@ export class Provider<T extends Name> {
129138
// if the service is eager, initialize the default instance
130139
if (isComponentEager(component)) {
131140
try {
132-
this.getOrInitializeService(DEFAULT_ENTRY_NAME);
141+
this.getOrInitializeService({ instanceIdentifier: DEFAULT_ENTRY_NAME });
133142
} catch (e) {
134143
// when the instance factory for an eager Component throws an exception during the eager
135144
// initialization, it should not cause a fatal error.
@@ -151,7 +160,9 @@ export class Provider<T extends Name> {
151160

152161
try {
153162
// `getOrInitializeService()` should always return a valid instance since a component is guaranteed. use ! to make typescript happy.
154-
const instance = this.getOrInitializeService(normalizedIdentifier)!;
163+
const instance = this.getOrInitializeService({
164+
instanceIdentifier: normalizedIdentifier
165+
})!;
155166
instanceDeferred.resolve(instance);
156167
} catch (e) {
157168
// when the instance factory throws an exception, it should not cause
@@ -190,16 +201,41 @@ export class Provider<T extends Name> {
190201
return this.instances.has(identifier);
191202
}
192203

193-
private getOrInitializeService(
194-
identifier: string
195-
): NameServiceMapping[T] | null {
196-
let instance = this.instances.get(identifier);
204+
initialize(opts: InitializeOptions = {}): NameServiceMapping[T] {
205+
const { instanceIdentifier = DEFAULT_ENTRY_NAME, options = {} } = opts;
206+
const normalizedIdentifier = this.normalizeInstanceIdentifier(
207+
instanceIdentifier
208+
);
209+
if (this.isInitialized(normalizedIdentifier)) {
210+
throw Error(
211+
`${this.name}(${normalizedIdentifier}) has already been initialized`
212+
);
213+
}
214+
215+
if (!this.isComponentSet()) {
216+
throw Error(`Component ${this.name} has not been registered yet`);
217+
}
218+
219+
return this.getOrInitializeService({
220+
instanceIdentifier: normalizedIdentifier,
221+
options
222+
})!;
223+
}
224+
225+
private getOrInitializeService({
226+
instanceIdentifier,
227+
options = {}
228+
}: {
229+
instanceIdentifier: string;
230+
options?: Record<string, unknown>;
231+
}): NameServiceMapping[T] | null {
232+
let instance = this.instances.get(instanceIdentifier);
197233
if (!instance && this.component) {
198-
instance = this.component.instanceFactory(
199-
this.container,
200-
normalizeIdentifierForFactory(identifier)
201-
) as NameServiceMapping[T];
202-
this.instances.set(identifier, instance);
234+
instance = this.component.instanceFactory(this.container, {
235+
instanceIdentifier: normalizeIdentifierForFactory(instanceIdentifier),
236+
options
237+
});
238+
this.instances.set(instanceIdentifier, instance);
203239
}
204240

205241
return instance || null;

packages/component/src/types.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ export const enum ComponentType {
3636
VERSION = 'VERSION'
3737
}
3838

39+
export interface InstanceFactoryOptions {
40+
instanceIdentifier?: string;
41+
options?: {};
42+
}
43+
44+
export type InitializeOptions = InstanceFactoryOptions;
45+
3946
/**
4047
* Factory to create an instance of type T, given a ComponentContainer.
4148
* ComponentContainer is the IOC container that provides {@link Provider}
@@ -46,7 +53,7 @@ export const enum ComponentType {
4653
*/
4754
export type InstanceFactory<T extends Name> = (
4855
container: ComponentContainer,
49-
instanceIdentifier?: string
56+
options: InstanceFactoryOptions
5057
) => NameServiceMapping[T];
5158

5259
export interface Dictionary {

packages/database/exp/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function registerDatabase(): void {
3535
_registerComponent(
3636
new Component(
3737
'database-exp',
38-
(container, url) => {
38+
(container, { instanceIdentifier: url }) => {
3939
const app = container.getProvider('app-exp').getImmediate()!;
4040
const authProvider = container.getProvider('auth-internal');
4141
return new FirebaseDatabase(app, authProvider, url);

packages/database/index.node.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function registerDatabase(instance: FirebaseNamespace) {
8484
const namespace = (instance as _FirebaseNamespace).INTERNAL.registerComponent(
8585
new Component(
8686
'database',
87-
(container, url) => {
87+
(container, { instanceIdentifier: url }) => {
8888
/* Dependencies */
8989
// getImmediate for FirebaseApp will always succeed
9090
const app = container.getProvider('app').getImmediate();

packages/database/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function registerDatabase(instance: FirebaseNamespace) {
4444
const namespace = (instance as _FirebaseNamespace).INTERNAL.registerComponent(
4545
new Component(
4646
'database',
47-
(container, url) => {
47+
(container, { instanceIdentifier: url }) => {
4848
/* Dependencies */
4949
// getImmediate for FirebaseApp will always succeed
5050
const app = container.getProvider('app').getImmediate();

packages/firestore/exp/register.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Component, ComponentType } from '@firebase/component';
2020

2121
import { version } from '../package.json';
2222
import { FirebaseFirestore } from '../src/exp/database';
23+
import { Settings } from '../src/exp/settings';
2324

2425
declare module '@firebase/component' {
2526
interface NameServiceMapping {
@@ -31,12 +32,16 @@ export function registerFirestore(): void {
3132
_registerComponent(
3233
new Component(
3334
'firestore-exp',
34-
container => {
35+
(container, { options: settings }: { options?: Settings }) => {
3536
const app = container.getProvider('app-exp').getImmediate()!;
36-
return ((app, auth) => new FirebaseFirestore(app, auth))(
37+
const firestoreInstance = new FirebaseFirestore(
3738
app,
3839
container.getProvider('auth-internal')
3940
);
41+
if (settings) {
42+
firestoreInstance._setSettings(settings);
43+
}
44+
return firestoreInstance;
4045
},
4146
ComponentType.PUBLIC
4247
)

0 commit comments

Comments
 (0)