278 lines
12 KiB
JavaScript
278 lines
12 KiB
JavaScript
/*! firebase-admin v13.5.0 */
|
|
"use strict";
|
|
/*!
|
|
* @license
|
|
* Copyright 2021 Google Inc.
|
|
*
|
|
* 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.
|
|
*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.FIREBASE_CONFIG_VAR = exports.defaultAppStore = exports.AppStore = void 0;
|
|
exports.initializeApp = initializeApp;
|
|
exports.getApp = getApp;
|
|
exports.getApps = getApps;
|
|
exports.deleteApp = deleteApp;
|
|
const fs = require("fs");
|
|
const validator = require("../utils/validator");
|
|
const error_1 = require("../utils/error");
|
|
const credential_internal_1 = require("./credential-internal");
|
|
const firebase_app_1 = require("./firebase-app");
|
|
const fastDeepEqual = require('fast-deep-equal');
|
|
const DEFAULT_APP_NAME = '[DEFAULT]';
|
|
class AppStore {
|
|
constructor() {
|
|
this.appStore = new Map();
|
|
}
|
|
initializeApp(options, appName = DEFAULT_APP_NAME) {
|
|
validateAppNameFormat(appName);
|
|
let autoInit = false;
|
|
if (typeof options === 'undefined') {
|
|
autoInit = true;
|
|
options = loadOptionsFromEnvVar();
|
|
options.credential = (0, credential_internal_1.getApplicationDefault)();
|
|
}
|
|
// Check if an app already exists and, if so, ensure its `AppOptions` match
|
|
// those of this `initializeApp` request.
|
|
if (!this.appStore.has(appName)) {
|
|
const app = new firebase_app_1.FirebaseApp(options, appName, autoInit, this);
|
|
this.appStore.set(app.name, app);
|
|
return app;
|
|
}
|
|
const currentApp = this.appStore.get(appName);
|
|
// Ensure the `autoInit` state matches the existing app's. If not, throw.
|
|
if (currentApp.autoInit() !== autoInit) {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_APP_OPTIONS, `A Firebase app named "${appName}" already exists with a different configuration.`);
|
|
}
|
|
if (autoInit) {
|
|
// Auto-initialization is triggered when no options were passed to
|
|
// `initializeApp`. With no options to compare, simply return the App.
|
|
return currentApp;
|
|
}
|
|
// Ensure the options objects don't break deep equal comparisons.
|
|
validateAppOptionsSupportDeepEquals(options, currentApp);
|
|
// `FirebaseApp()` adds a synthesized `Credential` to `app.options` upon
|
|
// app construction. Run a comparison w/o `Credential` to see if the base
|
|
// configurations match. Return the existing app if so.
|
|
const currentAppOptions = { ...currentApp.options };
|
|
delete currentAppOptions.credential;
|
|
if (!fastDeepEqual(options, currentAppOptions)) {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.DUPLICATE_APP, `A Firebase app named "${appName}" already exists with a different configuration.`);
|
|
}
|
|
return currentApp;
|
|
}
|
|
getApp(appName = DEFAULT_APP_NAME) {
|
|
validateAppNameFormat(appName);
|
|
if (!this.appStore.has(appName)) {
|
|
let errorMessage = (appName === DEFAULT_APP_NAME)
|
|
? 'The default Firebase app does not exist. ' : `Firebase app named "${appName}" does not exist. `;
|
|
errorMessage += 'Make sure you call initializeApp() before using any of the Firebase services.';
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.NO_APP, errorMessage);
|
|
}
|
|
return this.appStore.get(appName);
|
|
}
|
|
getApps() {
|
|
// Return a copy so the caller cannot mutate the array
|
|
return Array.from(this.appStore.values());
|
|
}
|
|
deleteApp(app) {
|
|
if (typeof app !== 'object' || app === null || !('options' in app)) {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_ARGUMENT, 'Invalid app argument.');
|
|
}
|
|
// Make sure the given app already exists.
|
|
const existingApp = getApp(app.name);
|
|
// Delegate delete operation to the App instance itself. That will also remove the App
|
|
// instance from the AppStore.
|
|
return existingApp.delete();
|
|
}
|
|
clearAllApps() {
|
|
const promises = [];
|
|
this.getApps().forEach((app) => {
|
|
promises.push(this.deleteApp(app));
|
|
});
|
|
return Promise.all(promises).then();
|
|
}
|
|
/**
|
|
* Removes the specified App instance from the store. This is currently called by the
|
|
* {@link FirebaseApp.delete} method. Can be removed once the app deletion is handled
|
|
* entirely by the {@link deleteApp} top-level function.
|
|
*/
|
|
removeApp(appName) {
|
|
this.appStore.delete(appName);
|
|
}
|
|
}
|
|
exports.AppStore = AppStore;
|
|
/**
|
|
* Validates that the `requestedOptions` and the `existingApp` options objects
|
|
* do not have fields that would break deep equals comparisons.
|
|
*
|
|
* @param requestedOptions The incoming `AppOptions` of a new `initailizeApp`
|
|
* request.
|
|
* @param existingApp An existing `FirebaseApp` with internal `options` to
|
|
* compare against.
|
|
*
|
|
* @throws FirebaseAppError if the objects cannot be deeply compared.
|
|
*
|
|
* @internal
|
|
*/
|
|
function validateAppOptionsSupportDeepEquals(requestedOptions, existingApp) {
|
|
// http.Agent checks.
|
|
if (typeof requestedOptions.httpAgent !== 'undefined') {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_APP_OPTIONS, `Firebase app named "${existingApp.name}" already exists and initializeApp was` +
|
|
' invoked with an optional http.Agent. The SDK cannot confirm the equality' +
|
|
' of http.Agent objects with the existing app. Please use getApp or getApps to reuse' +
|
|
' the existing app instead.');
|
|
}
|
|
else if (typeof existingApp.options.httpAgent !== 'undefined') {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_APP_OPTIONS, `An existing app named "${existingApp.name}" already exists with a different` +
|
|
' options configuration: httpAgent.');
|
|
}
|
|
// Credential checks.
|
|
if (typeof requestedOptions.credential !== 'undefined') {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_APP_OPTIONS, `Firebase app named "${existingApp.name}" already exists and initializeApp was` +
|
|
' invoked with an optional Credential. The SDK cannot confirm the equality' +
|
|
' of Credential objects with the existing app. Please use getApp or getApps' +
|
|
' to reuse the existing app instead.');
|
|
}
|
|
if (existingApp.customCredential()) {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_APP_OPTIONS, `An existing app named "${existingApp.name}" already exists with a different` +
|
|
' options configuration: Credential.');
|
|
}
|
|
}
|
|
/**
|
|
* Checks to see if the provided appName is a non-empty string and throws if it
|
|
* is not.
|
|
*
|
|
* @param appName A string representation of an App name.
|
|
*
|
|
* @throws FirebaseAppError if appName is not of type string or is empty.
|
|
*
|
|
* @internal
|
|
*/
|
|
function validateAppNameFormat(appName) {
|
|
if (!validator.isNonEmptyString(appName)) {
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_APP_NAME, `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`);
|
|
}
|
|
}
|
|
exports.defaultAppStore = new AppStore();
|
|
/**
|
|
* Initializes the `App` instance.
|
|
*
|
|
* Creates a new instance of {@link App} if one doesn't exist, or returns an existing
|
|
* `App` instance if one exists with the same `appName` and `options`.
|
|
*
|
|
* Note, due to the inablity to compare `http.Agent` objects and `Credential` objects,
|
|
* this function cannot support idempotency if either of `options.httpAgent` or
|
|
* `options.credential` are defined. When either is defined, subsequent invocations will
|
|
* throw a `FirebaseAppError` instead of returning an `App` object.
|
|
*
|
|
* For example, to safely initialize an app that may already exist:
|
|
*
|
|
* ```javascript
|
|
* let app;
|
|
* try {
|
|
* app = getApp("myApp");
|
|
* } catch (error) {
|
|
* app = initializeApp({ credential: myCredential }, "myApp");
|
|
* }
|
|
* ```
|
|
*
|
|
* @param options - Optional A set of {@link AppOptions} for the `App` instance.
|
|
* If not present, `initializeApp` will try to initialize with the options from the
|
|
* `FIREBASE_CONFIG` environment variable. If the environment variable contains a
|
|
* string that starts with `{` it will be parsed as JSON, otherwise it will be
|
|
* assumed to be pointing to a file.
|
|
* @param appName - Optional name of the `App` instance.
|
|
*
|
|
* @returns A new App instance, or the existing App if the instance already exists with
|
|
* the provided configuration.
|
|
*
|
|
* @throws FirebaseAppError if an `App` with the same name has already been
|
|
* initialized with a different set of `AppOptions`.
|
|
* @throws FirebaseAppError if an existing `App` exists and `options.httpAgent`
|
|
* or `options.credential` are defined. This is due to the function's inability to
|
|
* determine if the existing `App`'s `options` equate to the `options` parameter
|
|
* of this function. It's recommended to use {@link getApp} or {@link getApps} if your
|
|
* implementation uses either of these two fields in `AppOptions`.
|
|
*/
|
|
function initializeApp(options, appName = DEFAULT_APP_NAME) {
|
|
return exports.defaultAppStore.initializeApp(options, appName);
|
|
}
|
|
/**
|
|
* Returns an existing {@link App} instance for the provided name. If no name
|
|
* is provided the the default app name is used.
|
|
*
|
|
* @param appName - Optional name of the `App` instance.
|
|
*
|
|
* @returns An existing `App` instance that matches the name provided.
|
|
*
|
|
* @throws FirebaseAppError if no `App` exists for the given name.
|
|
* @throws FirebaseAppError if the `appName` is malformed.
|
|
*/
|
|
function getApp(appName = DEFAULT_APP_NAME) {
|
|
return exports.defaultAppStore.getApp(appName);
|
|
}
|
|
/**
|
|
* A (read-only) array of all initialized apps.
|
|
*
|
|
* @returns An array containing all initialized apps.
|
|
*/
|
|
function getApps() {
|
|
return exports.defaultAppStore.getApps();
|
|
}
|
|
/**
|
|
* Renders this given `App` unusable and frees the resources of
|
|
* all associated services (though it does *not* clean up any backend
|
|
* resources). When running the SDK locally, this method
|
|
* must be called to ensure graceful termination of the process.
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* deleteApp(app)
|
|
* .then(function() {
|
|
* console.log("App deleted successfully");
|
|
* })
|
|
* .catch(function(error) {
|
|
* console.log("Error deleting app:", error);
|
|
* });
|
|
* ```
|
|
*/
|
|
function deleteApp(app) {
|
|
return exports.defaultAppStore.deleteApp(app);
|
|
}
|
|
/**
|
|
* Constant holding the environment variable name with the default config.
|
|
* If the environment variable contains a string that starts with '{' it will be parsed as JSON,
|
|
* otherwise it will be assumed to be pointing to a file.
|
|
*/
|
|
exports.FIREBASE_CONFIG_VAR = 'FIREBASE_CONFIG';
|
|
/**
|
|
* Parse the file pointed to by the FIREBASE_CONFIG_VAR, if it exists.
|
|
* Or if the FIREBASE_CONFIG_ENV contains a valid JSON object, parse it directly.
|
|
* If the environment variable contains a string that starts with '{' it will be parsed as JSON,
|
|
* otherwise it will be assumed to be pointing to a file.
|
|
*/
|
|
function loadOptionsFromEnvVar() {
|
|
const config = process.env[exports.FIREBASE_CONFIG_VAR];
|
|
if (!validator.isNonEmptyString(config)) {
|
|
return {};
|
|
}
|
|
try {
|
|
const contents = config.startsWith('{') ? config : fs.readFileSync(config, 'utf8');
|
|
return JSON.parse(contents);
|
|
}
|
|
catch (error) {
|
|
// Throw a nicely formed error message if the file contents cannot be parsed
|
|
throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_APP_OPTIONS, 'Failed to parse app options file: ' + error);
|
|
}
|
|
}
|