voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
309 lines (275 loc) • 9.38 kB
text/typescript
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
FirebaseApp,
FirebaseOptions,
FirebaseNamespace,
FirebaseAppConfig
} from '@firebase/app-types';
import {
_FirebaseApp,
_FirebaseNamespace,
FirebaseService,
FirebaseServiceNamespace
} from '@firebase/app-types/private';
import { deepExtend, contains } from '@firebase/util';
import { FirebaseAppImpl } from './firebaseApp';
import { ERROR_FACTORY, AppError } from './errors';
import { FirebaseAppLiteImpl } from './lite/firebaseAppLite';
import { DEFAULT_ENTRY_NAME, PLATFORM_LOG_STRING } from './constants';
import { version } from '../../firebase/package.json';
import { logger } from './logger';
import {
setUserLogHandler,
setLogLevel,
LogCallback,
LogOptions
} from '@firebase/logger';
import { Component, ComponentType, Name } from '@firebase/component';
/**
* Because auth can't share code with other components, we attach the utility functions
* in an internal namespace to share code.
* This function return a firebase namespace object without
* any utility functions, so it can be shared between the regular firebaseNamespace and
* the lite version.
*/
export function createFirebaseNamespaceCore(
firebaseAppImpl: typeof FirebaseAppImpl | typeof FirebaseAppLiteImpl
): FirebaseNamespace {
const apps: { [name: string]: FirebaseApp } = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const components = new Map<string, Component<any>>();
// A namespace is a plain JavaScript Object.
const namespace: FirebaseNamespace = {
// Hack to prevent Babel from modifying the object returned
// as the firebase namespace.
// @ts-ignore
__esModule: true,
initializeApp,
// @ts-ignore
app,
registerVersion,
setLogLevel,
onLog,
// @ts-ignore
apps: null,
SDK_VERSION: version,
INTERNAL: {
registerComponent,
removeApp,
components,
useAsService
}
};
// Inject a circular default export to allow Babel users who were previously
// using:
//
// import firebase from 'firebase';
// which becomes: var firebase = require('firebase').default;
//
// instead of
//
// import * as firebase from 'firebase';
// which becomes: var firebase = require('firebase');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(namespace as any)['default'] = namespace;
// firebase.apps is a read-only getter.
Object.defineProperty(namespace, 'apps', {
get: getApps
});
/**
* Called by App.delete() - but before any services associated with the App
* are deleted.
*/
function removeApp(name: string): void {
delete apps[name];
}
/**
* Get the App object for a given name (or DEFAULT).
*/
function app(name?: string): FirebaseApp {
name = name || DEFAULT_ENTRY_NAME;
if (!contains(apps, name)) {
throw ERROR_FACTORY.create(AppError.NO_APP, { appName: name });
}
return apps[name];
}
// @ts-ignore
app['App'] = firebaseAppImpl;
/**
* Create a new App instance (name must be unique).
*/
function initializeApp(
options: FirebaseOptions,
config?: FirebaseAppConfig
): FirebaseApp;
function initializeApp(options: FirebaseOptions, name?: string): FirebaseApp;
function initializeApp(
options: FirebaseOptions,
rawConfig = {}
): FirebaseApp {
if (typeof rawConfig !== 'object' || rawConfig === null) {
const name = rawConfig;
rawConfig = { name };
}
const config = rawConfig as FirebaseAppConfig;
if (config.name === undefined) {
config.name = DEFAULT_ENTRY_NAME;
}
const { name } = config;
if (typeof name !== 'string' || !name) {
throw ERROR_FACTORY.create(AppError.BAD_APP_NAME, {
appName: String(name)
});
}
if (contains(apps, name)) {
throw ERROR_FACTORY.create(AppError.DUPLICATE_APP, { appName: name });
}
const app = new firebaseAppImpl(
options,
config,
namespace as _FirebaseNamespace
);
apps[name] = app;
return app;
}
/*
* Return an array of all the non-deleted FirebaseApps.
*/
function getApps(): FirebaseApp[] {
// Make a copy so caller cannot mutate the apps list.
return Object.keys(apps).map(name => apps[name]);
}
function registerComponent(
component: Component
): FirebaseServiceNamespace<FirebaseService> | null {
const componentName = component.name;
if (components.has(componentName)) {
logger.debug(
`There were multiple attempts to register component ${componentName}.`
);
return component.type === ComponentType.PUBLIC
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
(namespace as any)[componentName]
: null;
}
components.set(componentName, component);
// create service namespace for public components
if (component.type === ComponentType.PUBLIC) {
// The Service namespace is an accessor function ...
const serviceNamespace = (
appArg: FirebaseApp = app()
): FirebaseService => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (typeof (appArg as any)[componentName] !== 'function') {
// Invalid argument.
// This happens in the following case: firebase.storage('gs:/')
throw ERROR_FACTORY.create(AppError.INVALID_APP_ARGUMENT, {
appName: componentName
});
}
// Forward service instance lookup to the FirebaseApp.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (appArg as any)[componentName]();
};
// ... and a container for service-level properties.
if (component.serviceProps !== undefined) {
deepExtend(serviceNamespace, component.serviceProps);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(namespace as any)[componentName] = serviceNamespace;
// Patch the FirebaseAppImpl prototype
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(firebaseAppImpl.prototype as any)[componentName] =
// TODO: The eslint disable can be removed and the 'ignoreRestArgs'
// option added to the no-explicit-any rule when ESlint releases it.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function (...args: any) {
const serviceFxn = this._getService.bind(this, componentName);
return serviceFxn.apply(
this,
component.multipleInstances ? args : []
);
};
}
// add the component to existing app instances
for (const appName of Object.keys(apps)) {
(apps[appName] as _FirebaseApp)._addComponent(component);
}
return component.type === ComponentType.PUBLIC
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
(namespace as any)[componentName]
: null;
}
function registerVersion(
libraryKeyOrName: string,
version: string,
variant?: string
): void {
// TODO: We can use this check to whitelist strings when/if we set up
// a good whitelist system.
let library = PLATFORM_LOG_STRING[libraryKeyOrName] ?? libraryKeyOrName;
if (variant) {
library += `-${variant}`;
}
const libraryMismatch = library.match(/\s|\//);
const versionMismatch = version.match(/\s|\//);
if (libraryMismatch || versionMismatch) {
const warning = [
`Unable to register library "${library}" with version "${version}":`
];
if (libraryMismatch) {
warning.push(
`library name "${library}" contains illegal characters (whitespace or "/")`
);
}
if (libraryMismatch && versionMismatch) {
warning.push('and');
}
if (versionMismatch) {
warning.push(
`version name "${version}" contains illegal characters (whitespace or "/")`
);
}
logger.warn(warning.join(' '));
return;
}
registerComponent(
new Component(
`${library}-version` as Name,
() => ({ library, version }),
ComponentType.VERSION
)
);
}
function onLog(logCallback: LogCallback | null, options?: LogOptions): void {
if (logCallback !== null && typeof logCallback !== 'function') {
throw ERROR_FACTORY.create(AppError.INVALID_LOG_ARGUMENT);
}
setUserLogHandler(logCallback, options);
}
// Map the requested service to a registered service name
// (used to map auth to serverAuth service when needed).
function useAsService(app: FirebaseApp, name: string): string | null {
if (name === 'serverAuth') {
return null;
}
const useService = name;
return useService;
}
return namespace;
}