Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Provider.initialize() #4595

Merged
merged 7 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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