diff --git a/.changeset/config.json b/.changeset/config.json index c14d2540ce3..a020bce206a 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -16,6 +16,7 @@ "@firebase/functions-types-exp", "@firebase/testing", "firebase-exp", + "@firebase/app-compat", "@firebase/changelog-generator", "firebase-size-analysis" ], diff --git a/common/api-review/app-exp.api.md b/common/api-review/app-exp.api.md index d635a36ec9b..bf230efd778 100644 --- a/common/api-review/app-exp.api.md +++ b/common/api-review/app-exp.api.md @@ -9,7 +9,7 @@ import { FirebaseApp } from '@firebase/app-types-exp'; import { FirebaseAppConfig } from '@firebase/app-types-exp'; import { FirebaseOptions } from '@firebase/app-types-exp'; import { LogCallback } from '@firebase/logger'; -import { LogLevel } from '@firebase/logger'; +import { LogLevelString } from '@firebase/logger'; import { LogOptions } from '@firebase/logger'; import { Name } from '@firebase/component'; import { Provider } from '@firebase/component'; @@ -29,6 +29,9 @@ export function _clearComponents(): void; // @internal export const _components: Map>; +// @internal +export const _DEFAULT_ENTRY_NAME = "[DEFAULT]"; + // @public export function deleteApp(app: FirebaseApp): Promise; @@ -47,8 +50,6 @@ export function initializeApp(options: FirebaseOptions, name?: string): Firebase // @public export function initializeApp(options: FirebaseOptions, config?: FirebaseAppConfig): FirebaseApp; -export { LogLevel } - // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; @@ -65,7 +66,7 @@ export function _removeServiceInstance(app: FirebaseApp, name: T export const SDK_VERSION: string; // @public -export function setLogLevel(logLevel: LogLevel): void; +export function setLogLevel(logLevel: LogLevelString): void; ``` diff --git a/package.json b/package.json index c1fa99bc508..5dfe0b8d098 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ "scripts": { "dev": "lerna run --parallel --scope @firebase/* --scope firebase --scope rxfire dev", "build": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --scope rxfire --ignore @firebase/app-exp build", - "build:exp": "lerna run --scope @firebase/*-exp --scope firebase-exp build", + "build:exp": "lerna run --scope @firebase/*-exp --scope @firebase/*-compat --scope firebase-exp build", "build:release": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --scope rxfire --ignore @firebase/*-exp prepare", - "build:exp:release": "yarn --cwd packages/app build:deps && lerna run --scope @firebase/*-exp --scope firebase-exp prepare && yarn --cwd packages-exp/app-exp typings:public", + "build:exp:release": "yarn --cwd packages/app build:deps && lerna run --scope @firebase/*-exp --scope @firebase/*-compat --scope firebase-exp prepare && yarn --cwd packages-exp/app-exp typings:public", "build:changed": "ts-node-script scripts/ci-test/build_changed.ts", "link:packages": "lerna exec --scope @firebase/* --scope firebase --scope rxfire -- yarn link", "stage:packages": "./scripts/prepublish.sh", diff --git a/packages-exp/app-compat/.eslintrc.js b/packages-exp/app-compat/.eslintrc.js new file mode 100644 index 00000000000..ca80aa0f69a --- /dev/null +++ b/packages-exp/app-compat/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 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. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/packages-exp/app-compat/README.md b/packages-exp/app-compat/README.md new file mode 100644 index 00000000000..bb7e8ac3386 --- /dev/null +++ b/packages-exp/app-compat/README.md @@ -0,0 +1,5 @@ +# @firebase/app-compat + +This is the compat package that recreates the v7 APIs. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/app-compat/karma.conf.js b/packages-exp/app-compat/karma.conf.js new file mode 100644 index 00000000000..c0737457c55 --- /dev/null +++ b/packages-exp/app-compat/karma.conf.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2020 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. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = ['test/**/*', 'src/**/*.test.ts']; + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/app-compat/package.json b/packages-exp/app-compat/package.json new file mode 100644 index 00000000000..ab50feca3cd --- /dev/null +++ b/packages-exp/app-compat/package.json @@ -0,0 +1,62 @@ +{ + "name": "@firebase/app-compat", + "version": "0.0.800", + "description": "The primary entrypoint to the Firebase JS SDK", + "author": "Firebase (https://firebase.google.com/)", + "private": true, + "main": "dist/index.cjs.js", + "browser": "dist/index.esm5.js", + "module": "dist/index.esm5.js", + "esm2017": "dist/index.esm2017.js", + "lite": "dist/index.lite.js", + "lite-esm2017": "dist/index.lite.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:deps": "lerna run --scope @firebase/app-compat --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p lint test:all", + "test:all": "run-p test:browser test:node", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:browser": "karma start --single-run", + "test:browser:debug": "karma start --browsers Chrome --auto-watch", + "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha test/**/*.test.* src/**/*.test.ts --config ../../config/mocharc.node.js", + "prepare": "yarn build" + }, + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-exp": "0.0.800", + "@firebase/util": "0.3.1", + "@firebase/logger": "0.2.6", + "@firebase/component": "0.1.18", + "tslib": "^1.11.1", + "dom-storage": "2.1.0", + "xmlhttprequest": "1.8.0" + }, + "devDependencies": { + "rollup": "2.26.5", + "rollup-plugin-json": "4.0.0", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-typescript2": "0.27.2", + "typescript": "4.0.2" + }, + "repository": { + "directory": "packages-exp/app-compat", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + } +} \ No newline at end of file diff --git a/packages-exp/app-compat/rollup.config.js b/packages-exp/app-compat/rollup.config.js new file mode 100644 index 00000000000..0a84c8c7b19 --- /dev/null +++ b/packages-exp/app-compat/rollup.config.js @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2018 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 typescriptPlugin from 'rollup-plugin-typescript2'; +import json from 'rollup-plugin-json'; +import typescript from 'typescript'; +import pkg from './package.json'; + +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = [ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.module, format: 'es', sourcemap: true } + ], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: 'src/index.lite.ts', + output: { + file: pkg.lite, + format: 'es', + sourcemap: true + }, + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: { + file: pkg.esm2017, + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: 'src/index.lite.ts', + output: { + file: pkg['lite-esm2017'], + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-compat/src/errors.ts b/packages-exp/app-compat/src/errors.ts new file mode 100644 index 00000000000..85ad5b1b682 --- /dev/null +++ b/packages-exp/app-compat/src/errors.ts @@ -0,0 +1,40 @@ +/** + * @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 { ErrorFactory, ErrorMap } from '@firebase/util'; + +export const enum AppError { + NO_APP = 'no-app', + INVALID_APP_ARGUMENT = 'invalid-app-argument' +} + +const ERRORS: ErrorMap = { + [AppError.NO_APP]: + "No Firebase App '{$appName}' has been created - " + + 'call Firebase App.initializeApp()', + [AppError.INVALID_APP_ARGUMENT]: + 'firebase.{$appName}() takes either no argument or a ' + + 'Firebase App instance.' +}; + +type ErrorParams = { [key in AppError]: { appName: string } }; + +export const ERROR_FACTORY = new ErrorFactory( + 'app-compat', + 'Firebase', + ERRORS +); diff --git a/packages-exp/app-compat/src/firebaseApp.ts b/packages-exp/app-compat/src/firebaseApp.ts new file mode 100644 index 00000000000..551d37e9f36 --- /dev/null +++ b/packages-exp/app-compat/src/firebaseApp.ts @@ -0,0 +1,145 @@ +/** + * @license + * Copyright 2020 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 } from '@firebase/app-types'; +import { + _FirebaseApp, + _FirebaseNamespace, + FirebaseService +} from '@firebase/app-types/private'; +import { + Component, + ComponentType, + Name, + ComponentContainer +} from '@firebase/component'; +import { _FirebaseAppInternal } from '@firebase/app-types-exp'; +import { + deleteApp, + _addComponent, + _addOrOverwriteComponent, + _DEFAULT_ENTRY_NAME +} from '@firebase/app-exp'; + +/** + * Global context object for a collection of services using + * a shared authentication state. + */ +export class FirebaseAppImpl implements FirebaseApp { + private readonly container: ComponentContainer; + + constructor( + private readonly app: _FirebaseAppInternal, + private readonly firebase: _FirebaseNamespace + ) { + // add itself to container + // TODO: change the component name to 'app-compat' before the official release + _addComponent(app, new Component('app', () => this, ComponentType.PUBLIC)); + this.container = app.container; + } + + get automaticDataCollectionEnabled(): boolean { + return this.app.automaticDataCollectionEnabled; + } + + set automaticDataCollectionEnabled(val) { + this.app.automaticDataCollectionEnabled = val; + } + + get name(): string { + return this.app.name; + } + + get options(): FirebaseOptions { + return this.app.options; + } + + delete(): Promise { + return new Promise(resolve => { + this.app.checkDestroyed(); + resolve(); + }).then(() => { + this.firebase.INTERNAL.removeApp(this.name); + return deleteApp(this.app); + }); + } + + /** + * Return a service instance associated with this app (creating it + * on demand), identified by the passed instanceIdentifier. + * + * NOTE: Currently storage and functions are the only ones that are leveraging this + * functionality. They invoke it by calling: + * + * ```javascript + * firebase.app().storage('STORAGE BUCKET ID') + * ``` + * + * The service name is passed to this already + * @internal + */ + _getService( + name: string, + instanceIdentifier: string = _DEFAULT_ENTRY_NAME + ): FirebaseService { + this.app.checkDestroyed(); + + // getImmediate will always succeed because _getService is only called for registered components. + return (this.app.container.getProvider(name as Name).getImmediate({ + identifier: instanceIdentifier + }) as unknown) as FirebaseService; + } + + /** + * Remove a service instance from the cache, so we will create a new instance for this service + * when people try to get it again. + * + * NOTE: currently only firestore uses this functionality to support firestore shutdown. + * + * @param name The service name + * @param instanceIdentifier instance identifier in case multiple instances are allowed + * @internal + */ + _removeServiceInstance( + name: string, + instanceIdentifier: string = _DEFAULT_ENTRY_NAME + ): void { + this.app.container + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .getProvider(name as any) + .clearInstance(instanceIdentifier); + } + + /** + * @param component the component being added to this app's container + * @internal + */ + _addComponent(component: Component): void { + _addComponent(this.app, component); + } + + _addOrOverwriteComponent(component: Component): void { + _addOrOverwriteComponent(this.app, component); + } +} + +// TODO: investigate why the following needs to be commented out +// Prevent dead-code elimination of these methods w/o invalid property +// copying. +// (FirebaseAppImpl.prototype.name && FirebaseAppImpl.prototype.options) || +// FirebaseAppImpl.prototype.delete || +// console.log('dc'); diff --git a/packages-exp/app-compat/src/firebaseNamespace.ts b/packages-exp/app-compat/src/firebaseNamespace.ts new file mode 100644 index 00000000000..3cb8d6da915 --- /dev/null +++ b/packages-exp/app-compat/src/firebaseNamespace.ts @@ -0,0 +1,54 @@ +/** + * @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 { FirebaseNamespace } from '@firebase/app-types'; +import { _FirebaseApp, _FirebaseNamespace } from '@firebase/app-types/private'; +import { createSubscribe, deepExtend, ErrorFactory } from '@firebase/util'; +import { FirebaseAppImpl } from './firebaseApp'; +import { createFirebaseNamespaceCore } from './firebaseNamespaceCore'; + +/** + * Return a firebase namespace object. + * + * In production, this will be called exactly once and the result + * assigned to the 'firebase' global. It may be called multiple times + * in unit tests. + */ +export function createFirebaseNamespace(): FirebaseNamespace { + const namespace = createFirebaseNamespaceCore(FirebaseAppImpl); + (namespace as _FirebaseNamespace).INTERNAL = { + ...(namespace as _FirebaseNamespace).INTERNAL, + createFirebaseNamespace, + extendNamespace, + createSubscribe, + ErrorFactory, + deepExtend + }; + + /** + * Patch the top-level firebase namespace with additional properties. + * + * firebase.INTERNAL.extendNamespace() + */ + function extendNamespace(props: { [prop: string]: unknown }): void { + deepExtend(namespace, props); + } + + return namespace; +} + +export const firebase = createFirebaseNamespace(); diff --git a/packages-exp/app-compat/src/firebaseNamespaceCore.ts b/packages-exp/app-compat/src/firebaseNamespaceCore.ts new file mode 100644 index 00000000000..41b1345d262 --- /dev/null +++ b/packages-exp/app-compat/src/firebaseNamespaceCore.ts @@ -0,0 +1,212 @@ +/** + * @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 +} from '@firebase/app-types'; // TODO: create @firebase/app-types-compat before the official release +import { + _FirebaseApp, + _FirebaseNamespace, + FirebaseService, + FirebaseServiceNamespace +} from '@firebase/app-types/private'; +import { + SDK_VERSION, + initializeApp, + registerVersion, + onLog, + setLogLevel, + _registerComponent, + _DEFAULT_ENTRY_NAME +} from '@firebase/app-exp'; +import { _FirebaseAppInternal } from '@firebase/app-types-exp'; +import { Component, ComponentType } from '@firebase/component'; + +import { deepExtend, contains } from '@firebase/util'; +import { FirebaseAppImpl } from './firebaseApp'; +import { ERROR_FACTORY, AppError } from './errors'; +import { FirebaseAppLiteImpl } from './lite/firebaseAppLite'; + +/** + * 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>(); + + // 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: initializeAppCompat, + // @ts-ignore + app, + registerVersion, + setLogLevel, + onLog, + // @ts-ignore + apps: null, + SDK_VERSION, + INTERNAL: { + registerComponent: registerComponentCompat, + removeApp, + 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 initializeAppCompat( + options: FirebaseOptions, + rawConfig = {} + ): FirebaseApp { + const app = initializeApp(options, rawConfig) as _FirebaseAppInternal; + const appCompat = new firebaseAppImpl(app, namespace as _FirebaseNamespace); + apps[app.name] = appCompat; + return appCompat; + } + + /* + * 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 registerComponentCompat( + component: Component + ): FirebaseServiceNamespace | null { + const componentName = component.name; + if ( + _registerComponent(component) && + component.type === ComponentType.PUBLIC + ) { + // create service namespace for public components + // 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 : [] + ); + }; + } + + return component.type === ComponentType.PUBLIC + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (namespace as any)[componentName] + : null; + } + + // 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; +} diff --git a/packages-exp/app-compat/src/index.lite.ts b/packages-exp/app-compat/src/index.lite.ts new file mode 100644 index 00000000000..c411c425822 --- /dev/null +++ b/packages-exp/app-compat/src/index.lite.ts @@ -0,0 +1,26 @@ +/** + * @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 { createFirebaseNamespaceLite } from './lite/firebaseNamespaceLite'; +import { registerCoreComponents } from './registerCoreComponents'; + +export const firebase = createFirebaseNamespaceLite(); + +registerCoreComponents('lite'); + +// eslint-disable-next-line import/no-default-export +export default firebase; diff --git a/packages-exp/app-compat/src/index.ts b/packages-exp/app-compat/src/index.ts new file mode 100644 index 00000000000..aa65496a4ef --- /dev/null +++ b/packages-exp/app-compat/src/index.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2020 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 { FirebaseNamespace } from '@firebase/app-types'; +import { isBrowser } from '@firebase/util'; +import { firebase as firebaseNamespace } from './firebaseNamespace'; +import { logger } from './logger'; +import { registerCoreComponents } from './registerCoreComponents'; + +// Firebase Lite detection +// eslint-disable-next-line @typescript-eslint/no-explicit-any +if (isBrowser() && (self as any).firebase !== undefined) { + logger.warn(` + Warning: Firebase is already defined in the global scope. Please make sure + Firebase library is only loaded once. + `); + + // eslint-disable-next-line + const sdkVersion = ((self as any).firebase as FirebaseNamespace).SDK_VERSION; + if (sdkVersion && sdkVersion.indexOf('LITE') >= 0) { + logger.warn(` + Warning: You are trying to load Firebase while using Firebase Performance standalone script. + You should load Firebase Performance with this instance of Firebase to avoid loading duplicate code. + `); + } +} + +export const firebase = firebaseNamespace; + +registerCoreComponents(); + +// eslint-disable-next-line import/no-default-export +export default firebase; diff --git a/packages-exp/app-compat/src/lite/firebaseAppLite.ts b/packages-exp/app-compat/src/lite/firebaseAppLite.ts new file mode 100644 index 00000000000..c6a33ce8db3 --- /dev/null +++ b/packages-exp/app-compat/src/lite/firebaseAppLite.ts @@ -0,0 +1,91 @@ +/** + * @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 } from '@firebase/app-types'; +import { + _FirebaseApp, + _FirebaseNamespace, + FirebaseService +} from '@firebase/app-types/private'; +import { + deleteApp, + _addComponent, + _DEFAULT_ENTRY_NAME +} from '@firebase/app-exp'; +import { _FirebaseAppInternal } from '@firebase/app-types-exp'; +import { Component, ComponentType, Name } from '@firebase/component'; + +/** + * Global context object for a collection of services using + * a shared authentication state. + */ +export class FirebaseAppLiteImpl implements FirebaseApp { + constructor( + private readonly app: _FirebaseAppInternal, + private readonly firebase: _FirebaseNamespace + ) { + // add itself to container + _addComponent(app, new Component('app', () => this, ComponentType.PUBLIC)); + } + + get automaticDataCollectionEnabled(): boolean { + return this.app.automaticDataCollectionEnabled; + } + + set automaticDataCollectionEnabled(val) { + this.automaticDataCollectionEnabled = val; + } + + get name(): string { + return this.app.name; + } + + get options(): FirebaseOptions { + return this.app.options; + } + + delete(): Promise { + this.firebase.INTERNAL.removeApp(this.name); + return deleteApp(this.app); + } + + /** + * Return a service instance associated with this app (creating it + * on demand), identified by the passed instanceIdentifier. + * + * NOTE: Currently storage is the only one that is leveraging this + * functionality. They invoke it by calling: + * + * ```javascript + * firebase.app().storage('STORAGE BUCKET ID') + * ``` + * + * The service name is passed to this already + * @internal + */ + _getService( + name: string, + instanceIdentifier: string = _DEFAULT_ENTRY_NAME + ): FirebaseService { + this.app.checkDestroyed(); + + // getImmediate will always succeed because _getService is only called for registered components. + return (this.app.container.getProvider(name as Name).getImmediate({ + identifier: instanceIdentifier + }) as unknown) as FirebaseService; + } +} diff --git a/packages-exp/app-compat/src/lite/firebaseNamespaceLite.ts b/packages-exp/app-compat/src/lite/firebaseNamespaceLite.ts new file mode 100644 index 00000000000..82c8afcaf4e --- /dev/null +++ b/packages-exp/app-compat/src/lite/firebaseNamespaceLite.ts @@ -0,0 +1,59 @@ +/** + * @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 { FirebaseNamespace } from '@firebase/app-types'; +import { + _FirebaseApp, + _FirebaseNamespace, + FirebaseServiceNamespace, + FirebaseService +} from '@firebase/app-types/private'; +import { FirebaseAppLiteImpl } from './firebaseAppLite'; +import { createFirebaseNamespaceCore } from '../firebaseNamespaceCore'; +import { Component, ComponentType } from '@firebase/component'; + +export function createFirebaseNamespaceLite(): FirebaseNamespace { + const namespace = createFirebaseNamespaceCore(FirebaseAppLiteImpl); + + namespace.SDK_VERSION = `${namespace.SDK_VERSION}_LITE`; + + const registerComponent = (namespace as _FirebaseNamespace).INTERNAL + .registerComponent; + (namespace as _FirebaseNamespace).INTERNAL.registerComponent = registerComponentForLite; + + /** + * This is a special implementation, so it only works with performance. + * only allow performance SDK to register. + */ + function registerComponentForLite( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + component: Component + ): FirebaseServiceNamespace | null { + // only allow performance to register with firebase lite + if ( + component.type === ComponentType.PUBLIC && + component.name !== 'performance' && + component.name !== 'installations' + ) { + throw Error(`${name} cannot register with the standalone perf instance`); + } + + return registerComponent(component); + } + + return namespace; +} diff --git a/packages-exp/app-compat/src/logger.ts b/packages-exp/app-compat/src/logger.ts new file mode 100644 index 00000000000..d78af3d39b2 --- /dev/null +++ b/packages-exp/app-compat/src/logger.ts @@ -0,0 +1,20 @@ +/** + * @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 { Logger } from '@firebase/logger'; + +export const logger = new Logger('@firebase/app-compat'); diff --git a/packages-exp/app-compat/src/registerCoreComponents.ts b/packages-exp/app-compat/src/registerCoreComponents.ts new file mode 100644 index 00000000000..6168c0b95d9 --- /dev/null +++ b/packages-exp/app-compat/src/registerCoreComponents.ts @@ -0,0 +1,26 @@ +/** + * @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 { _FirebaseNamespace } from '@firebase/app-types/private'; +import { registerVersion } from '@firebase/app-exp'; + +import { name, version } from '../package.json'; + +export function registerCoreComponents(variant?: string): void { + // Register `app` package. + registerVersion(name, version, variant); +} diff --git a/packages-exp/app-compat/test/firebaseAppCompat.test.ts b/packages-exp/app-compat/test/firebaseAppCompat.test.ts new file mode 100644 index 00000000000..e0d55213d44 --- /dev/null +++ b/packages-exp/app-compat/test/firebaseAppCompat.test.ts @@ -0,0 +1,366 @@ +/** + * @license + * Copyright 2020 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 './setup'; +import { expect } from 'chai'; +import { stub } from 'sinon'; +import { FirebaseNamespace, FirebaseOptions } from '@firebase/app-types'; +import { _FirebaseApp, _FirebaseNamespace } from '@firebase/app-types/private'; +import { _components, _clearComponents } from '@firebase/app-exp'; +import { ComponentType } from '@firebase/component'; + +import { createFirebaseNamespace } from '../src/firebaseNamespace'; +import { createFirebaseNamespaceLite } from '../src/lite/firebaseNamespaceLite'; + +import { createTestComponent, TestService } from './util'; + +executeFirebaseTests(); +executeFirebaseLiteTests(); + +function executeFirebaseTests(): void { + firebaseAppTests('Firebase App Tests', createFirebaseNamespace); + + describe('Firebase Service Registration', () => { + const firebase: FirebaseNamespace = createFirebaseNamespace(); + + afterEach(() => { + const deleteTasks = []; + for (const app of firebase.apps) { + deleteTasks.push(app.delete()); + } + return Promise.all(deleteTasks); + }); + + it('will do nothing if registerComponent is called again with the same name', () => { + const registerStub = stub( + (firebase as _FirebaseNamespace).INTERNAL, + 'registerComponent' + ).callThrough(); + + const testComponent = createTestComponent('test'); + + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + testComponent + ); + firebase.initializeApp({}); + const serviceNamespace = (firebase as any).test; + + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + testComponent + ); + + const serviceNamespace2 = (firebase as any).test; + + expect(serviceNamespace).to.eq(serviceNamespace2); + expect(registerStub).to.have.not.thrown(); + }); + + it('returns cached service instances', () => { + firebase.initializeApp({}); + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + createTestComponent('test') + ); + + const service = (firebase as any).test(); + + expect(service).to.eq((firebase as any).test()); + }); + + it(`creates a new instance of a service after removing the existing instance`, () => { + const app = firebase.initializeApp({}); + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + createTestComponent('test') + ); + + const service = (firebase as any).test(); + + expect(service).to.eq((firebase as any).test()); + + (app as _FirebaseApp)._removeServiceInstance('test'); + + expect(service, (firebase as any).test()); + }); + + it(`creates a new instance of a service after removing the existing instance - for service that supports multiple instances`, () => { + const app = firebase.initializeApp({}); + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + createTestComponent('multiInstance', true) + ); + + // default instance + const instance1 = (firebase.app() as any).multiInstance(); + const serviceIdentifier = 'custom instance identifier'; + const instance2 = (firebase.app() as any).multiInstance( + serviceIdentifier + ); + + (app as _FirebaseApp)._removeServiceInstance( + 'multiInstance', + serviceIdentifier + ); + + // default instance should not be changed + expect(instance1).to.eq((firebase.app() as any).multiInstance()); + + expect(instance2).to.not.eq( + (firebase.app() as any).multiInstance(serviceIdentifier) + ); + }); + }); + + describe('Firebase Version Registration', () => { + const firebase: FirebaseNamespace = createFirebaseNamespace(); + + afterEach(() => { + _clearComponents(); + }); + + it('will register an official version component without warnings', () => { + const warnStub = stub(console, 'warn'); + const initialSize = _components.size; + + firebase.registerVersion('@firebase/analytics', '1.2.3'); + expect(_components.get('fire-analytics-version')).to.exist; + expect(_components.size).to.equal(initialSize + 1); + + expect(warnStub.called).to.be.false; + }); + + it('will register an arbitrary version component without warnings', () => { + const warnStub = stub(console, 'warn'); + const initialSize = _components.size; + + firebase.registerVersion('angularfire', '1.2.3'); + expect(_components.get('angularfire-version')).to.exist; + expect(_components.size).to.equal(initialSize + 1); + + expect(warnStub.called).to.be.false; + }); + + it('will do nothing if registerVersion() is given illegal characters', () => { + const warnStub = stub(console, 'warn'); + const initialSize = _components.size; + + firebase.registerVersion('remote config', '1.2.3'); + expect(warnStub.args[0][1]).to.include('library name "remote config"'); + expect(_components.size).to.equal(initialSize); + + firebase.registerVersion('remote-config', '1.2/3'); + expect(warnStub.args[1][1]).to.include('version name "1.2/3"'); + expect(_components.size).to.equal(initialSize); + }); + }); +} + +function executeFirebaseLiteTests(): void { + firebaseAppTests('Firebase App Lite Tests', createFirebaseNamespaceLite); + + describe('Firebase Lite Service Registration', () => { + const firebase: FirebaseNamespace = createFirebaseNamespaceLite(); + + afterEach(() => { + const deleteTasks = []; + for (const app of firebase.apps) { + deleteTasks.push(app.delete()); + } + return Promise.all(deleteTasks); + }); + + it('allows Performance service to register', () => { + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + createTestComponent('performance') + ); + const app = firebase.initializeApp({}); + const perf = (app as any).performance(); + expect(perf).to.be.instanceof(TestService); + }); + + it('allows Installations service to register', () => { + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + createTestComponent('installations') + ); + const app = firebase.initializeApp({}); + const perf = (app as any).installations(); + expect(perf).to.be.instanceof(TestService); + }); + + it('does NOT allow services other than Performance and installations to register', () => { + expect(() => + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + createTestComponent('auth') + ) + ).to.throw(); + }); + + it('allows any private component to register', () => { + expect(() => + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + createTestComponent('auth-internal', false, ComponentType.PRIVATE) + ) + ).to.not.throw(); + }); + }); +} + +function firebaseAppTests( + testName: string, + firebaseNamespaceFactory: () => FirebaseNamespace +): void { + describe(testName, () => { + const firebase: FirebaseNamespace = firebaseNamespaceFactory(); + + afterEach(() => { + const deleteTasks = []; + for (const app of firebase.apps) { + deleteTasks.push(app.delete()); + } + return Promise.all(deleteTasks); + }); + + it('has no initial apps.', () => { + expect(firebase.apps.length).to.eq(0); + }); + + it('Can get app via firebase namespace.', () => { + const app = firebase.initializeApp({}); + expect(app).to.be.not.null; + }); + + it('can initialize DEFAULT App.', () => { + const app = firebase.initializeApp({}); + expect(firebase.apps.length).to.eq(1); + expect(app).to.eq(firebase.apps[0]); + expect(app.name).to.eq('[DEFAULT]'); + expect(firebase.app()).to.eq(app); + expect(firebase.app('[DEFAULT]')).to.eq(app); + }); + + it('can get options of App.', () => { + const options: FirebaseOptions = { projectId: 'projectId' }; + const app = firebase.initializeApp(options); + expect(app.options).to.deep.eq(options); + }); + + it('can delete App.', async () => { + const app = firebase.initializeApp({}); + expect(firebase.apps.length).to.eq(1); + await app.delete(); + expect(firebase.apps.length).to.eq(0); + }); + + it('can create named App.', () => { + const app = firebase.initializeApp({}, 'my-app'); + expect(firebase.apps.length).to.eq(1); + expect(app.name).to.eq('my-app'); + expect(firebase.app('my-app')).to.eq(app); + }); + + it('can create named App and DEFAULT app.', () => { + firebase.initializeApp({}, 'my-app'); + expect(firebase.apps.length).to.eq(1); + firebase.initializeApp({}); + expect(firebase.apps.length).to.eq(2); + }); + + it('duplicate DEFAULT initialize is an error.', () => { + firebase.initializeApp({}); + expect(() => firebase.initializeApp({})).throws(/\[DEFAULT\].*exists/i); + }); + + it('duplicate named App initialize is an error.', () => { + firebase.initializeApp({}, 'abc'); + + expect(() => firebase.initializeApp({}, 'abc')).throws(/'abc'.*exists/i); + }); + + it('automaticDataCollectionEnabled is `false` by default', () => { + const app = firebase.initializeApp({}, 'my-app'); + expect(app.automaticDataCollectionEnabled).to.eq(false); + }); + + it('automaticDataCollectionEnabled can be set via the config object', () => { + const app = firebase.initializeApp( + {}, + { automaticDataCollectionEnabled: true } + ); + expect(app.automaticDataCollectionEnabled).to.eq(true); + }); + + it('Modifying options object does not change options.', () => { + const options: FirebaseOptions = { + appId: 'original', + measurementId: 'someId' + }; + firebase.initializeApp(options); + options.appId = 'changed'; + delete options.measurementId; + expect(firebase.app().options).to.deep.eq({ + appId: 'original', + measurementId: 'someId' + }); + }); + + it('Error to use app after it is deleted.', async () => { + const app = firebase.initializeApp({}); + await app.delete(); + expect(() => console.log(app.name)).throws(/already.*deleted/); + }); + + it('OK to create same-name app after it is deleted.', async () => { + const app = firebase.initializeApp({}, 'app-name'); + await app.delete(); + + const app2 = firebase.initializeApp({}, 'app-name'); + expect(app).to.not.eq(app2, 'Expect new instance.'); + // But original app id still orphaned. + expect(() => console.log(app.name)).throws(/already.*deleted/); + }); + + it('OK to use Object.prototype member names as app name.', () => { + const app = firebase.initializeApp({}, 'toString'); + expect(firebase.apps.length).to.eq(1); + expect(app.name).to.eq('toString'); + expect(firebase.app('toString')).to.eq(app); + }); + + it('Error to get uninitialized app using Object.prototype member name.', () => { + expect(() => firebase.app('toString')).throws(/'toString'.*created/i); + }); + + describe('Check for bad app names', () => { + const tests = ['', 123, false]; + for (const data of tests) { + it("where name == '" + data + "'", () => { + expect(() => firebase.initializeApp({}, data as string)).throws( + /Illegal app name/i + ); + }); + } + }); + + describe('Check for bad app names, passed as an object', () => { + const tests = ['', 123, false, null]; + for (const name of tests) { + it("where name == '" + name + "'", () => { + expect(() => + firebase.initializeApp({}, { name: name as string }) + ).throws(/Illegal app name/i); + }); + } + }); + }); +} diff --git a/packages-exp/app-compat/test/setup.ts b/packages-exp/app-compat/test/setup.ts new file mode 100644 index 00000000000..b1e3136529f --- /dev/null +++ b/packages-exp/app-compat/test/setup.ts @@ -0,0 +1,26 @@ +/** + * @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 { use } from 'chai'; +import { restore } from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +use(sinonChai); + +afterEach(async () => { + restore(); +}); diff --git a/packages-exp/app-compat/test/util.ts b/packages-exp/app-compat/test/util.ts new file mode 100644 index 00000000000..3161ca8bdb6 --- /dev/null +++ b/packages-exp/app-compat/test/util.ts @@ -0,0 +1,49 @@ +/** + * @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 { FirebaseService } from '@firebase/app-types/private'; +import { FirebaseApp } from '@firebase/app-types'; +import { ComponentType, Component } from '@firebase/component'; + +export class TestService implements FirebaseService { + constructor(private app_: FirebaseApp, public instanceIdentifier?: string) {} + + get app(): FirebaseApp { + return this.app_; + } + + delete(): Promise { + return new Promise((resolve: (v?: void) => void) => { + setTimeout(() => resolve(), 10); + }); + } +} + +export function createTestComponent( + name: string, + multiInstances = false, + type = ComponentType.PUBLIC +): Component { + const component = new Component( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + name as any, + container => new TestService(container.getProvider('app').getImmediate()), + type + ); + component.setMultipleInstances(multiInstances); + return component; +} diff --git a/packages-exp/app-compat/tsconfig.json b/packages-exp/app-compat/tsconfig.json new file mode 100644 index 00000000000..72e0736c2b7 --- /dev/null +++ b/packages-exp/app-compat/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "resolveJsonModule": true, + "downlevelIteration": true + }, + "exclude": ["dist/**/*"] +} diff --git a/packages-exp/app-exp/src/api.test.ts b/packages-exp/app-exp/src/api.test.ts index 307ab4b621f..8fcd4f93982 100644 --- a/packages-exp/app-exp/src/api.test.ts +++ b/packages-exp/app-exp/src/api.test.ts @@ -25,7 +25,6 @@ import { getApp, registerVersion, setLogLevel, - LogLevel, onLog } from './api'; import { DEFAULT_ENTRY_NAME } from './constants'; @@ -236,14 +235,14 @@ describe('API tests', () => { const logger = new Logger('@firebase/logger-test'); logger.warn('hello'); expect(warnSpy.called).to.be.true; - setLogLevel(LogLevel.WARN); + setLogLevel('warn'); logger.info('hi'); expect(infoSpy.called).to.be.false; logger.log('hi'); expect(logSpy.called).to.be.false; logSpy.resetHistory(); infoSpy.resetHistory(); - setLogLevel(LogLevel.DEBUG); + setLogLevel('debug'); logger.info('hi'); expect(infoSpy.called).to.be.true; logger.log('hi'); diff --git a/packages-exp/app-exp/src/api.ts b/packages-exp/app-exp/src/api.ts index 1b5f53d3c77..77589ae5560 100644 --- a/packages-exp/app-exp/src/api.ts +++ b/packages-exp/app-exp/src/api.ts @@ -34,7 +34,7 @@ import { FirebaseAppImpl } from './firebaseApp'; import { _apps, _components, _registerComponent } from './internal'; import { logger } from './logger'; import { - LogLevel, + LogLevelString, setLogLevel as setLogLevelImpl, LogCallback, LogOptions, @@ -300,8 +300,6 @@ export function onLog( * * @public */ -export function setLogLevel(logLevel: LogLevel): void { +export function setLogLevel(logLevel: LogLevelString): void { setLogLevelImpl(logLevel); } - -export { LogLevel } from '@firebase/logger'; diff --git a/packages-exp/app-exp/src/constants.ts b/packages-exp/app-exp/src/constants.ts index a545013612e..8147920c734 100644 --- a/packages-exp/app-exp/src/constants.ts +++ b/packages-exp/app-exp/src/constants.ts @@ -16,6 +16,7 @@ */ import { name as appName } from '../package.json'; +import { name as appCompatName } from '../../app-compat/package.json'; import { name as analyticsName } from '../../../packages/analytics/package.json'; import { name as authName } from '../../../packages/auth/package.json'; import { name as databaseName } from '../../../packages/database/package.json'; @@ -28,10 +29,16 @@ import { name as storageName } from '../../../packages/storage/package.json'; import { name as firestoreName } from '../../../packages/firestore/package.json'; import { name as packageName } from '../../../packages-exp/firebase-exp/package.json'; +/** + * The default app name + * + * @internal + */ export const DEFAULT_ENTRY_NAME = '[DEFAULT]'; export const PLATFORM_LOG_STRING = { [appName]: 'fire-core', + [appCompatName]: 'fire-core-compat', [analyticsName]: 'fire-analytics', [authName]: 'fire-auth', [databaseName]: 'fire-rtdb', diff --git a/packages-exp/app-exp/src/internal.ts b/packages-exp/app-exp/src/internal.ts index 3fb863d0db2..8421ef9f482 100644 --- a/packages-exp/app-exp/src/internal.ts +++ b/packages-exp/app-exp/src/internal.ts @@ -127,3 +127,8 @@ export function _removeServiceInstance( export function _clearComponents(): void { _components.clear(); } + +/** + * Exported in order to be used in app-compat package + */ +export { DEFAULT_ENTRY_NAME as _DEFAULT_ENTRY_NAME }; diff --git a/packages-exp/firebase-exp/compat/app/index.cdn.ts b/packages-exp/firebase-exp/compat/app/index.cdn.ts new file mode 100644 index 00000000000..4b44c87b205 --- /dev/null +++ b/packages-exp/firebase-exp/compat/app/index.cdn.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2020 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 firebase from '@firebase/app-compat'; +import { name, version } from '../../package.json'; + +firebase.registerVersion(name, version, 'app-compat-cdn'); + +export default firebase; diff --git a/packages-exp/firebase-exp/compat/app/index.ts b/packages-exp/firebase-exp/compat/app/index.ts new file mode 100644 index 00000000000..d37d12214ed --- /dev/null +++ b/packages-exp/firebase-exp/compat/app/index.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2020 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 firebase from '@firebase/app-compat'; +import { name, version } from '../../package.json'; + +firebase.registerVersion(name, version, 'app-compat'); + +export default firebase; diff --git a/packages-exp/firebase-exp/compat/app/package.json b/packages-exp/firebase-exp/compat/app/package.json new file mode 100644 index 00000000000..b4e10f5a47c --- /dev/null +++ b/packages-exp/firebase-exp/compat/app/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/app", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/app/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/index.cdn.ts b/packages-exp/firebase-exp/compat/index.cdn.ts new file mode 100644 index 00000000000..387883d1afd --- /dev/null +++ b/packages-exp/firebase-exp/compat/index.cdn.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2017 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. + */ + +console.warn(` +It looks like you're using the development build of the Firebase JS SDK. +When deploying Firebase apps to production, it is advisable to only import +the individual SDK components you intend to use. + +For the CDN builds, these are available in the following manner +(replace with the name of a component - i.e. auth, database, etc): + +https://www.gstatic.com/firebasejs/5.0.0/firebase-.js +`); + +import '@firebase/polyfill'; +import firebase from './app'; +import { name, version } from '../package.json'; + +// import './auth'; +// import './database'; +// import './firestore'; +// import './functions'; +// import './messaging'; +// import './storage'; +// import './performance'; +// import './analytics'; +// import './remote-config'; + +firebase.registerVersion(name, version, 'compat-cdn'); + +export default firebase; diff --git a/packages-exp/firebase-exp/compat/index.node.ts b/packages-exp/firebase-exp/compat/index.node.ts new file mode 100644 index 00000000000..ad532d78a4a --- /dev/null +++ b/packages-exp/firebase-exp/compat/index.node.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2017 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 firebase from './app'; +import { name, version } from '../package.json'; + +// import './auth'; +// import './database'; +// import './firestore'; +// import './functions'; + +firebase.registerVersion(name, version, 'compat-node'); + +export default firebase; diff --git a/packages-exp/firebase-exp/compat/index.perf.ts b/packages-exp/firebase-exp/compat/index.perf.ts new file mode 100644 index 00000000000..e2efa3bfdd0 --- /dev/null +++ b/packages-exp/firebase-exp/compat/index.perf.ts @@ -0,0 +1,24 @@ +/** + * @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 firebase from './app'; +// import './performance'; +import { name, version } from '../package.json'; + +firebase.registerVersion(name, version, 'compat-lite'); + +export default firebase; diff --git a/packages-exp/firebase-exp/compat/index.rn.ts b/packages-exp/firebase-exp/compat/index.rn.ts new file mode 100644 index 00000000000..0bfc80dd3ce --- /dev/null +++ b/packages-exp/firebase-exp/compat/index.rn.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 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 firebase from './app'; +import { name, version } from '../package.json'; + +// import './auth'; +// import './database'; +// // TODO(b/158625454): Storage doesn't actually work by default in RN (it uses +// // `atob`). We should provide a RN build that works out of the box. +// import './storage'; +// import './firestore'; + +firebase.registerVersion(name, version, 'compat-rn'); + +export default firebase; diff --git a/packages-exp/firebase-exp/compat/index.ts b/packages-exp/firebase-exp/compat/index.ts new file mode 100644 index 00000000000..89e4a793ebd --- /dev/null +++ b/packages-exp/firebase-exp/compat/index.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2017 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. + */ + +console.warn(` +It looks like you're using the development build of the Firebase JS SDK. +When deploying Firebase apps to production, it is advisable to only import +the individual SDK components you intend to use. + +For the module builds, these are available in the following manner +(replace with the name of a component - i.e. auth, database, etc): + +CommonJS Modules: +const firebase = require('firebase/app'); +require('firebase/'); + +ES Modules: +import firebase from 'firebase/app'; +import 'firebase/'; + +Typescript: +import * as firebase from 'firebase/app'; +import 'firebase/'; +`); + +import firebase from './app'; +import { name, version } from '../package.json'; + +// import './auth'; +// import './database'; +// import './firestore'; +// import './functions'; +// import './messaging'; +// import './storage'; +// import './performance'; +// import './analytics'; +// import './remote-config'; + +firebase.registerVersion(name, version, 'compat'); + +export default firebase; diff --git a/packages-exp/firebase-exp/compat/package.json b/packages-exp/firebase-exp/compat/package.json new file mode 100644 index 00000000000..ba13f52a70f --- /dev/null +++ b/packages-exp/firebase-exp/compat/package.json @@ -0,0 +1,11 @@ +{ + "name": "firebase-exp/compat", + "main": "dist/index.node.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "react-native": "dist/index.rn.cjs.js", + "typings": "index.d.ts", + "components": [ + "app" + ] +} \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/rollup.config.js b/packages-exp/firebase-exp/compat/rollup.config.js new file mode 100644 index 00000000000..e95220076b2 --- /dev/null +++ b/packages-exp/firebase-exp/compat/rollup.config.js @@ -0,0 +1,264 @@ +/** + * @license + * Copyright 2020 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 { resolve } from 'path'; +import resolveModule from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import sourcemaps from 'rollup-plugin-sourcemaps'; +import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { uglify } from 'rollup-plugin-uglify'; +import { terser } from 'rollup-plugin-terser'; +import json from 'rollup-plugin-json'; +import pkg from '../package.json'; +import compatPkg from './package.json'; +import appPkg from './app/package.json'; + +const external = Object.keys(pkg.dependencies || {}); + +/** + * Global UMD Build + */ +const GLOBAL_NAME = 'firebase'; + +function createUmdOutputConfig(output) { + return { + file: output, + format: 'umd', + sourcemap: true, + extend: true, + name: GLOBAL_NAME, + globals: { + '@firebase/app-compat': GLOBAL_NAME + }, + + /** + * use iife to avoid below error in the old Safari browser + * SyntaxError: Functions cannot be declared in a nested block in strict mode + * https://github.com/firebase/firebase-js-sdk/issues/1228 + * + */ + intro: ` + try { + (function() {`, + outro: ` + }).apply(this, arguments); + } catch(err) { + console.error(err); + throw new Error( + 'Cannot instantiate ${output} - ' + + 'be sure to load firebase-app.js first.' + ); + }` + }; +} + +const plugins = [sourcemaps(), resolveModule(), json(), commonjs()]; + +const typescriptPlugin = rollupTypescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + declaration: false + } + } +}); + +/** + * Individual Component Builds + */ +const appBuilds = [ + /** + * App Browser Builds + */ + { + input: `${__dirname}/app/index.ts`, + output: [ + { + file: resolve(__dirname, 'app', appPkg.main), + format: 'cjs', + sourcemap: true + }, + { + file: resolve(__dirname, 'app', appPkg.module), + format: 'es', + sourcemap: true + } + ], + plugins: [...plugins, typescriptPlugin], + external + }, + /** + * App UMD Builds + */ + { + input: `${__dirname}/app/index.cdn.ts`, + output: { + file: 'firebase-app-compat.js', + sourcemap: true, + format: 'umd', + name: GLOBAL_NAME + }, + plugins: [...plugins, typescriptPlugin, uglify()] + } +]; + +const componentBuilds = compatPkg.components + // The "app" component is treated differently because it doesn't depend on itself. + .filter(component => component !== 'app') + .map(component => { + const pkg = require(`${__dirname}/${component}/package.json`); + return [ + { + input: `${__dirname}/${component}/index.ts`, + output: [ + { + file: resolve(__dirname, component, pkg.main), + format: 'cjs', + sourcemap: true + }, + { + file: resolve(__dirname, component, pkg.module), + format: 'es', + sourcemap: true + } + ], + plugins: [...plugins, typescriptPlugin], + external + }, + { + input: `${__dirname}/${component}/index.ts`, + output: createUmdOutputConfig(`firebase-${component}-compat.js`), + plugins: [...plugins, typescriptPlugin, uglify()], + external: ['@firebase/app-compat'] + } + ]; + }) + .reduce((a, b) => a.concat(b), []); + +/** + * Complete Package Builds + */ +const completeBuilds = [ + /** + * App Browser Builds + */ + { + input: `${__dirname}/index.ts`, + output: [ + { + file: resolve(__dirname, compatPkg.module), + format: 'es', + sourcemap: true + } + ], + plugins: [...plugins, typescriptPlugin], + external + }, + { + input: `${__dirname}/index.cdn.ts`, + output: { + file: 'firebase-compat.js', + format: 'umd', + sourcemap: true, + name: GLOBAL_NAME + }, + plugins: [...plugins, typescriptPlugin, uglify()] + }, + /** + * App Node.js Builds + */ + { + input: `${__dirname}/index.node.ts`, + output: { + file: resolve(__dirname, compatPkg.main), + format: 'cjs', + sourcemap: true + }, + plugins: [...plugins, typescriptPlugin], + external + }, + /** + * App React Native Builds + */ + { + input: `${__dirname}/index.rn.ts`, + output: { + file: resolve(__dirname, compatPkg['react-native']), + format: 'cjs', + sourcemap: true + }, + plugins: [...plugins, typescriptPlugin], + external + }, + /** + * Performance script Build + */ + { + input: `${__dirname}/index.perf.ts`, + output: { + file: 'firebase-performance-standalone-compat.js', + format: 'umd', + sourcemap: true, + name: GLOBAL_NAME + }, + plugins: [ + sourcemaps(), + resolveModule({ + mainFields: ['lite', 'module', 'main'] + }), + typescriptPlugin, + json(), + commonjs(), + uglify() + ] + }, + /** + * Performance script Build in ES2017 + */ + { + input: `${__dirname}/index.perf.ts`, + output: { + file: 'firebase-performance-standalone-compat.es2017.js', + format: 'umd', + sourcemap: true, + name: GLOBAL_NAME + }, + plugins: [ + sourcemaps(), + resolveModule({ + mainFields: ['lite-esm2017', 'esm2017', 'module', 'main'] + }), + rollupTypescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017', + declaration: false + } + } + }), + json({ + preferConst: true + }), + commonjs(), + terser() + ] + } +]; + +export default [...appBuilds, ...componentBuilds, ...completeBuilds]; diff --git a/packages-exp/firebase-exp/package.json b/packages-exp/firebase-exp/package.json index 1baa8e8d672..602edcaf770 100644 --- a/packages-exp/firebase-exp/package.json +++ b/packages-exp/firebase-exp/package.json @@ -27,8 +27,9 @@ "url": "https://github.com/firebase/firebase-js-sdk.git" }, "scripts": { - "build": "rollup -c && gulp firebase-js", - "build:release": "rollup -c rollup.config.release.js && gulp firebase-js", + "build": "rollup -c && gulp firebase-js && yarn build:compat", + "build:release": "rollup -c rollup.config.release.js && gulp firebase-js && yarn build:compat", + "build:compat": "rollup -c compat/rollup.config.js", "dev": "rollup -c -w", "prepare": "yarn build:release", "test": "echo 'No test suite for firebase wrapper'", @@ -36,6 +37,7 @@ }, "dependencies": { "@firebase/app-exp": "0.0.800", + "@firebase/app-compat": "0.0.800", "@firebase/functions-exp": "0.0.800", "@firebase/firestore": "1.16.5" }, diff --git a/packages/app/.npmignore b/packages/app/.npmignore deleted file mode 100644 index 682c8f74a52..00000000000 --- a/packages/app/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -# Directories not needed by end users -/src -test - -# Files not needed by end users -gulpfile.js -index.ts -karma.conf.js -tsconfig.json \ No newline at end of file diff --git a/packages/firestore/exp/src/api/components.ts b/packages/firestore/exp/src/api/components.ts index 615d68176e1..be5bc5a43bc 100644 --- a/packages/firestore/exp/src/api/components.ts +++ b/packages/firestore/exp/src/api/components.ts @@ -23,7 +23,7 @@ import { OfflineComponentProvider, OnlineComponentProvider } from '../../../src/core/component_provider'; -import {handleUserChange, LocalStore} from '../../../src/local/local_store'; +import { handleUserChange, LocalStore } from '../../../src/local/local_store'; import { Deferred } from '../../../src/util/promise'; import { logDebug } from '../../../src/util/log'; import { SyncEngine } from '../../../src/core/sync_engine'; diff --git a/packages/firestore/exp/src/api/database.ts b/packages/firestore/exp/src/api/database.ts index 1b186ea8075..c6a619ec16f 100644 --- a/packages/firestore/exp/src/api/database.ts +++ b/packages/firestore/exp/src/api/database.ts @@ -69,7 +69,8 @@ const LOG_TAG = 'Firestore'; * The root reference to the Firestore database and the entry point for the * tree-shakeable SDK. */ -export class Firestore extends LiteFirestore +export class Firestore + extends LiteFirestore implements firestore.FirebaseFirestore, _FirebaseService { readonly _queue = new AsyncQueue(); readonly _persistenceKey: string; diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index 6037a465009..ba383a2e5e7 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -1015,7 +1015,8 @@ export class WriteBatch implements PublicWriteBatch { /** * A reference to a particular document in a collection in the database. */ -export class DocumentReference extends DocumentKeyReference +export class DocumentReference + extends DocumentKeyReference implements PublicDocumentReference { private _firestoreClient: FirestoreClient; @@ -1437,7 +1438,8 @@ export class DocumentSnapshot } } -export class QueryDocumentSnapshot extends DocumentSnapshot +export class QueryDocumentSnapshot + extends DocumentSnapshot implements PublicQueryDocumentSnapshot { data(options?: SnapshotOptions): T { const data = super.data(options); @@ -2303,7 +2305,8 @@ export class QuerySnapshot implements PublicQuerySnapshot { } } -export class CollectionReference extends Query +export class CollectionReference + extends Query implements PublicCollectionReference { constructor( readonly _path: ResourcePath, diff --git a/packages/firestore/src/api/field_value.ts b/packages/firestore/src/api/field_value.ts index b37c92a8d3f..d987bf53d8d 100644 --- a/packages/firestore/src/api/field_value.ts +++ b/packages/firestore/src/api/field_value.ts @@ -203,7 +203,8 @@ export class NumericIncrementFieldValueImpl extends SerializableFieldValue { } /** The public FieldValue class of the lite API. */ -export abstract class FieldValue extends SerializableFieldValue +export abstract class FieldValue + extends SerializableFieldValue implements PublicFieldValue { protected constructor() { super(); diff --git a/packages/rules-unit-testing/test/database.test.ts b/packages/rules-unit-testing/test/database.test.ts index 7b938803596..3846e31df10 100644 --- a/packages/rules-unit-testing/test/database.test.ts +++ b/packages/rules-unit-testing/test/database.test.ts @@ -90,7 +90,7 @@ describe('Testing Module Tests', function () { throw new Error('Expected otherFailure to fail.'); }) .catch(() => {}); - }) + }); it('initializeTestApp() with auth=null does not set access token', async function () { const app = firebase.initializeTestApp({