Skip to content

Commit

Permalink
Add Provider.initialize() (#4595)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Feiyang1 authored Mar 12, 2021
1 parent b6a7a59 commit 5c1a83e
Show file tree
Hide file tree
Showing 23 changed files with 161 additions and 57 deletions.
10 changes: 10 additions & 0 deletions .changeset/clever-apricots-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@firebase/component": minor
"@firebase/database": patch
"@firebase/firestore": patch
"@firebase/functions": patch
"@firebase/remote-config": patch
"@firebase/storage": patch
---

Component facotry now takes an options object. And added `Provider.initialize()` that can be used to pass an options object to the component factory.
7 changes: 2 additions & 5 deletions packages-exp/analytics-compat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,13 @@ declare module '@firebase/component' {
}

const factory: InstanceFactory<'analytics-compat'> = (
container: ComponentContainer,
regionOrCustomDomain?: string
container: ComponentContainer
) => {
// Dependencies
const app = container.getProvider('app-compat').getImmediate();
const analyticsServiceExp = container
.getProvider('analytics-exp')
.getImmediate({
identifier: regionOrCustomDomain
});
.getImmediate();

return new AnalyticsService(app as FirebaseApp, analyticsServiceExp);
};
Expand Down
3 changes: 1 addition & 2 deletions packages-exp/auth-exp/src/core/auth/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth {
_fail(auth, AuthErrorCode.ALREADY_INITIALIZED);
}

const auth = provider.getImmediate() as AuthImpl;
_initializeAuthInstance(auth, deps);
const auth = provider.initialize({ options: deps }) as AuthImpl;

return auth;
}
Expand Down
10 changes: 8 additions & 2 deletions packages-exp/auth-exp/src/core/auth/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { _assert } from '../util/assert';
import { _getClientVersion, ClientPlatform } from '../util/version';
import { _castAuth, AuthImpl, DefaultConfig } from './auth_impl';
import { AuthInterop } from './firebase_internal';
import { Dependencies } from '../../model/auth';
import { _initializeAuthInstance } from './initialize';

export const enum _ComponentName {
AUTH = 'auth-exp',
Expand Down Expand Up @@ -53,7 +55,7 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
_registerComponent(
new Component(
_ComponentName.AUTH,
container => {
(container, { options: deps }: { options?: Dependencies }) => {
const app = container.getProvider('app-exp').getImmediate()!;
const { apiKey, authDomain } = app.options;
return (app => {
Expand All @@ -66,7 +68,11 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
apiScheme: DefaultConfig.API_SCHEME,
sdkClientVersion: _getClientVersion(clientPlatform)
};
return new AuthImpl(app, config);

const authInstance = new AuthImpl(app, config);
_initializeAuthInstance(authInstance, deps);

return authInstance;
})(app);
},
ComponentType.PUBLIC
Expand Down
2 changes: 1 addition & 1 deletion packages-exp/functions-exp/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const DEFAULT_REGION = 'us-central1';
export function registerFunctions(fetchImpl: typeof fetch): void {
const factory: InstanceFactory<'functions'> = (
container: ComponentContainer,
regionOrCustomDomain?: string
{ instanceIdentifier: regionOrCustomDomain }
) => {
// Dependencies
const app = container.getProvider('app-exp').getImmediate();
Expand Down
13 changes: 9 additions & 4 deletions packages-exp/performance-exp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ export function initializePerformance(
throw ERROR_FACTORY.create(ErrorCode.ALREADY_INITIALIZED);
}

const perfInstance = provider.getImmediate() as PerformanceController;
perfInstance._init(settings);
const perfInstance = provider.initialize({
options: settings
}) as PerformanceController;
return perfInstance;
}

Expand All @@ -89,7 +90,8 @@ export function trace(
}

const factory: InstanceFactory<'performance-exp'> = (
container: ComponentContainer
container: ComponentContainer,
{ options: settings }: { options?: PerformanceSettings }
) => {
// Dependencies
const app = container.getProvider('app-exp').getImmediate();
Expand All @@ -104,7 +106,10 @@ const factory: InstanceFactory<'performance-exp'> = (
throw ERROR_FACTORY.create(ErrorCode.NO_WINDOW);
}
setupApi(window);
return new PerformanceController(app, installations);
const perfInstance = new PerformanceController(app, installations);
perfInstance._init(settings);

return perfInstance;
};

function registerPerformance(): void {
Expand Down
5 changes: 3 additions & 2 deletions packages-exp/remote-config-compat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import firebase, { _FirebaseNamespace } from '@firebase/app-compat';
import {
Component,
ComponentContainer,
ComponentType
ComponentType,
InstanceFactoryOptions
} from '@firebase/component';
import { RemoteConfigCompatImpl } from './remoteConfig';
import { name as packageName, version } from '../package.json';
Expand Down Expand Up @@ -48,7 +49,7 @@ function registerRemoteConfigCompat(

function remoteConfigFactory(
container: ComponentContainer,
namespace?: string
{ instanceIdentifier: namespace }: InstanceFactoryOptions
): RemoteConfigCompatImpl {
const app = container.getProvider('app-compat').getImmediate();
// The following call will always succeed because rc `import {...} from '@firebase/remote-config-exp'`
Expand Down
5 changes: 3 additions & 2 deletions packages-exp/remote-config-exp/src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
import {
Component,
ComponentType,
ComponentContainer
ComponentContainer,
InstanceFactoryOptions
} from '@firebase/component';
import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger';
import { RemoteConfig } from './public_types';
Expand Down Expand Up @@ -53,7 +54,7 @@ export function registerRemoteConfig(): void {

function remoteConfigFactory(
container: ComponentContainer,
namespace?: string
{ instanceIdentifier: namespace }: InstanceFactoryOptions
): RemoteConfig {
/* Dependencies */
// getImmediate for FirebaseApp will always succeed
Expand Down
3 changes: 2 additions & 1 deletion packages/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export {
InstanceFactory,
InstantiationMode,
NameServiceMapping,
Name
Name,
InstanceFactoryOptions
} from './src/types';
32 changes: 30 additions & 2 deletions packages/component/src/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,34 @@ describe('Provider', () => {
expect(() => provider.setComponent(component)).to.not.throw();
});

describe('initialize()', () => {
it('throws if the provider is already initialized', () => {
provider.setComponent(getFakeComponent('test', () => ({})));
provider.initialize();

expect(() => provider.initialize()).to.throw();
});

it('throws if the component has not been registered', () => {
expect(() => provider.initialize()).to.throw();
});

it('accepts an options parameter and passes it to the instance factory', () => {
const options = {
configurable: true,
test: true
};
provider.setComponent(
getFakeComponent('test', (_container, opts) => ({
options: opts.options
}))
);
const instance = provider.initialize({ options });

expect((instance as any).options).to.deep.equal(options);
});
});

describe('Provider (multipleInstances = false)', () => {
describe('getImmediate()', () => {
it('throws if the service is not available', () => {
Expand Down Expand Up @@ -155,7 +183,7 @@ describe('Provider', () => {
});
});

describe('provideFactory()', () => {
describe('setComponent()', () => {
it('instantiates the service if there is a pending promise and the service is eager', () => {
// create a pending promise
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Expand Down Expand Up @@ -320,7 +348,7 @@ describe('Provider', () => {
});
});

describe('provideFactory()', () => {
describe('setComponent()', () => {
it('instantiates services for the pending promises for all instance identifiers', async () => {
/* eslint-disable @typescript-eslint/no-floating-promises */
// create 3 promises for 3 different identifiers
Expand Down
64 changes: 50 additions & 14 deletions packages/component/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
import { Deferred } from '@firebase/util';
import { ComponentContainer } from './component_container';
import { DEFAULT_ENTRY_NAME } from './constants';
import { InstantiationMode, Name, NameServiceMapping } from './types';
import {
InitializeOptions,
InstantiationMode,
Name,
NameServiceMapping
} from './types';
import { Component } from './component';

/**
Expand Down Expand Up @@ -51,7 +56,9 @@ export class Provider<T extends Name> {
this.instancesDeferred.set(normalizedIdentifier, deferred);
// If the service instance is available, resolve the promise with it immediately
try {
const instance = this.getOrInitializeService(normalizedIdentifier);
const instance = this.getOrInitializeService({
instanceIdentifier: normalizedIdentifier
});
if (instance) {
deferred.resolve(instance);
}
Expand Down Expand Up @@ -92,7 +99,9 @@ export class Provider<T extends Name> {
// if multipleInstances is not supported, use the default name
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
try {
const instance = this.getOrInitializeService(normalizedIdentifier);
const instance = this.getOrInitializeService({
instanceIdentifier: normalizedIdentifier
});

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

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

private getOrInitializeService(
identifier: string
): NameServiceMapping[T] | null {
let instance = this.instances.get(identifier);
initialize(opts: InitializeOptions = {}): NameServiceMapping[T] {
const { instanceIdentifier = DEFAULT_ENTRY_NAME, options = {} } = opts;
const normalizedIdentifier = this.normalizeInstanceIdentifier(
instanceIdentifier
);
if (this.isInitialized(normalizedIdentifier)) {
throw Error(
`${this.name}(${normalizedIdentifier}) has already been initialized`
);
}

if (!this.isComponentSet()) {
throw Error(`Component ${this.name} has not been registered yet`);
}

return this.getOrInitializeService({
instanceIdentifier: normalizedIdentifier,
options
})!;
}

private getOrInitializeService({
instanceIdentifier,
options = {}
}: {
instanceIdentifier: string;
options?: Record<string, unknown>;
}): NameServiceMapping[T] | null {
let instance = this.instances.get(instanceIdentifier);
if (!instance && this.component) {
instance = this.component.instanceFactory(
this.container,
normalizeIdentifierForFactory(identifier)
) as NameServiceMapping[T];
this.instances.set(identifier, instance);
instance = this.component.instanceFactory(this.container, {
instanceIdentifier: normalizeIdentifierForFactory(instanceIdentifier),
options
});
this.instances.set(instanceIdentifier, instance);
}

return instance || null;
Expand Down
9 changes: 8 additions & 1 deletion packages/component/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ export const enum ComponentType {
VERSION = 'VERSION'
}

export interface InstanceFactoryOptions {
instanceIdentifier?: string;
options?: {};
}

export type InitializeOptions = InstanceFactoryOptions;

/**
* Factory to create an instance of type T, given a ComponentContainer.
* ComponentContainer is the IOC container that provides {@link Provider}
Expand All @@ -46,7 +53,7 @@ export const enum ComponentType {
*/
export type InstanceFactory<T extends Name> = (
container: ComponentContainer,
instanceIdentifier?: string
options: InstanceFactoryOptions
) => NameServiceMapping[T];

export interface Dictionary {
Expand Down
2 changes: 1 addition & 1 deletion packages/database/exp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function registerDatabase(): void {
_registerComponent(
new Component(
'database-exp',
(container, url) => {
(container, { instanceIdentifier: url }) => {
const app = container.getProvider('app-exp').getImmediate()!;
const authProvider = container.getProvider('auth-internal');
return new FirebaseDatabase(app, authProvider, url);
Expand Down
2 changes: 1 addition & 1 deletion packages/database/index.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function registerDatabase(instance: FirebaseNamespace) {
const namespace = (instance as _FirebaseNamespace).INTERNAL.registerComponent(
new Component(
'database',
(container, url) => {
(container, { instanceIdentifier: url }) => {
/* Dependencies */
// getImmediate for FirebaseApp will always succeed
const app = container.getProvider('app').getImmediate();
Expand Down
2 changes: 1 addition & 1 deletion packages/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function registerDatabase(instance: FirebaseNamespace) {
const namespace = (instance as _FirebaseNamespace).INTERNAL.registerComponent(
new Component(
'database',
(container, url) => {
(container, { instanceIdentifier: url }) => {
/* Dependencies */
// getImmediate for FirebaseApp will always succeed
const app = container.getProvider('app').getImmediate();
Expand Down
9 changes: 7 additions & 2 deletions packages/firestore/exp/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Component, ComponentType } from '@firebase/component';

import { version } from '../package.json';
import { FirebaseFirestore } from '../src/exp/database';
import { Settings } from '../src/exp/settings';

declare module '@firebase/component' {
interface NameServiceMapping {
Expand All @@ -31,12 +32,16 @@ export function registerFirestore(): void {
_registerComponent(
new Component(
'firestore-exp',
container => {
(container, { options: settings }: { options?: Settings }) => {
const app = container.getProvider('app-exp').getImmediate()!;
return ((app, auth) => new FirebaseFirestore(app, auth))(
const firestoreInstance = new FirebaseFirestore(
app,
container.getProvider('auth-internal')
);
if (settings) {
firestoreInstance._setSettings(settings);
}
return firestoreInstance;
},
ComponentType.PUBLIC
)
Expand Down
Loading

0 comments on commit 5c1a83e

Please sign in to comment.