UNPKG

@react-native-firebase/app

Version:

A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Sto

300 lines (258 loc) 8.5 kB
/* * Copyright (c) 2016-present Invertase Limited & Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this library 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 { isString, createDeprecationProxy } from '../../common'; import FirebaseApp from '../../FirebaseApp'; import SDK_VERSION from '../../version'; import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants'; import FirebaseModule from '../FirebaseModule'; import { getApp, getApps, initializeApp, setLogLevel, setReactNativeAsyncStorage, setOnAppCreate, setOnAppDestroy, } from './app'; // firebase.X let FIREBASE_ROOT = null; const NAMESPACE_REGISTRY = {}; const APP_MODULE_INSTANCE = {}; const MODULE_GETTER_FOR_APP = {}; const MODULE_GETTER_FOR_ROOT = {}; /** * Attaches module namespace getters on every newly created app. * * Structured like this to avoid metro require cycles. */ setOnAppCreate(app => { for (let i = 0; i < KNOWN_NAMESPACES.length; i++) { const moduleNamespace = KNOWN_NAMESPACES[i]; Object.defineProperty(app, moduleNamespace, { enumerable: false, get: firebaseAppModuleProxy.bind(null, app, moduleNamespace), }); } }); /** * Destroys all APP_MODULE_INSTANCE & MODULE_GETTER_FOR_APP objects relating to the * recently destroyed app. * * Structured like this to avoid metro require cycles. */ setOnAppDestroy(app => { delete APP_MODULE_INSTANCE[app.name]; delete MODULE_GETTER_FOR_APP[app.name]; }); /** * * @param app * @param moduleNamespace * @returns {*} */ function getOrCreateModuleForApp(app, moduleNamespace) { if (MODULE_GETTER_FOR_APP[app.name] && MODULE_GETTER_FOR_APP[app.name][moduleNamespace]) { return MODULE_GETTER_FOR_APP[app.name][moduleNamespace]; } if (!MODULE_GETTER_FOR_APP[app.name]) { MODULE_GETTER_FOR_APP[app.name] = {}; } const { hasCustomUrlOrRegionSupport, hasMultiAppSupport, ModuleClass } = NAMESPACE_REGISTRY[moduleNamespace]; // modules such as analytics only run on the default app if (!hasMultiAppSupport && app.name !== DEFAULT_APP_NAME) { throw new Error( [ `You attempted to call "firebase.app('${app.name}').${moduleNamespace}" but; ${moduleNamespace} does not support multiple Firebase Apps.`, '', `Ensure you access ${moduleNamespace} from the default application only.`, ].join('\r\n'), ); } // e.g. firebase.storage(customUrlOrRegion), firebase.functions(customUrlOrRegion), firebase.firestore(databaseId), firebase.database(url) function firebaseModuleWithArgs(customUrlOrRegionOrDatabaseId) { if (customUrlOrRegionOrDatabaseId !== undefined) { if (!hasCustomUrlOrRegionSupport) { // TODO throw Module does not support arguments error } if (!isString(customUrlOrRegionOrDatabaseId)) { // TODO throw Module first argument must be a string error } } const key = customUrlOrRegionOrDatabaseId ? `${customUrlOrRegionOrDatabaseId}:${moduleNamespace}` : moduleNamespace; if (!APP_MODULE_INSTANCE[app.name]) { APP_MODULE_INSTANCE[app.name] = {}; } if (!APP_MODULE_INSTANCE[app.name][key]) { const module = createDeprecationProxy( new ModuleClass(app, NAMESPACE_REGISTRY[moduleNamespace], customUrlOrRegionOrDatabaseId), ); APP_MODULE_INSTANCE[app.name][key] = module; } return APP_MODULE_INSTANCE[app.name][key]; } MODULE_GETTER_FOR_APP[app.name][moduleNamespace] = firebaseModuleWithArgs; return MODULE_GETTER_FOR_APP[app.name][moduleNamespace]; } /** * * @param moduleNamespace * @returns {*} */ function getOrCreateModuleForRoot(moduleNamespace) { if (MODULE_GETTER_FOR_ROOT[moduleNamespace]) { return MODULE_GETTER_FOR_ROOT[moduleNamespace]; } const { statics, hasMultiAppSupport, ModuleClass } = NAMESPACE_REGISTRY[moduleNamespace]; // e.g. firebase.storage(app) function firebaseModuleWithApp(app) { const _app = app || getApp(); if (!(_app instanceof FirebaseApp)) { throw new Error( [ `"firebase.${moduleNamespace}(app)" arg expects a FirebaseApp instance or undefined.`, '', 'Ensure the arg provided is a Firebase app instance; or no args to use the default Firebase app.', ].join('\r\n'), ); } // modules such as analytics only run on the default app if (!hasMultiAppSupport && _app.name !== DEFAULT_APP_NAME) { throw new Error( [ `You attempted to call "firebase.${moduleNamespace}(app)" but; ${moduleNamespace} does not support multiple Firebase Apps.`, '', `Ensure the app provided is the default Firebase app only and not the "${_app.name}" app.`, ].join('\r\n'), ); } if (!APP_MODULE_INSTANCE[_app.name]) { APP_MODULE_INSTANCE[_app.name] = {}; } if (!APP_MODULE_INSTANCE[_app.name][moduleNamespace]) { const module = createDeprecationProxy( new ModuleClass(_app, NAMESPACE_REGISTRY[moduleNamespace]), ); APP_MODULE_INSTANCE[_app.name][moduleNamespace] = module; } return APP_MODULE_INSTANCE[_app.name][moduleNamespace]; } Object.assign(firebaseModuleWithApp, statics || {}); // Object.freeze(firebaseModuleWithApp); // Wrap around statics, e.g. firebase.firestore.FieldValue, removed freeze as it stops proxy working. it is deprecated anyway MODULE_GETTER_FOR_ROOT[moduleNamespace] = createDeprecationProxy(firebaseModuleWithApp); return MODULE_GETTER_FOR_ROOT[moduleNamespace]; } /** * * @param firebaseNamespace * @param moduleNamespace * @returns {*} */ function firebaseRootModuleProxy(_firebaseNamespace, moduleNamespace) { if (NAMESPACE_REGISTRY[moduleNamespace]) { return getOrCreateModuleForRoot(moduleNamespace); } moduleWithDashes = moduleNamespace .split(/(?=[A-Z])/) .join('-') .toLowerCase(); throw new Error( [ `You attempted to use 'firebase.${moduleNamespace}' but this module could not be found.`, '', `Ensure you have installed and imported the '@react-native-firebase/${moduleWithDashes}' package.`, ].join('\r\n'), ); } /** * * @param app * @param moduleNamespace * @returns {*} */ export function firebaseAppModuleProxy(app, moduleNamespace) { if (NAMESPACE_REGISTRY[moduleNamespace]) { app._checkDestroyed(); return getOrCreateModuleForApp(app, moduleNamespace); } moduleWithDashes = moduleNamespace .split(/(?=[A-Z])/) .join('-') .toLowerCase(); throw new Error( [ `You attempted to use "firebase.app('${app.name}').${moduleNamespace}" but this module could not be found.`, '', `Ensure you have installed and imported the '@react-native-firebase/${moduleWithDashes}' package.`, ].join('\r\n'), ); } /** * * @returns {*} */ export function createFirebaseRoot() { FIREBASE_ROOT = { initializeApp, setReactNativeAsyncStorage, get app() { return getApp; }, get apps() { return getApps(); }, SDK_VERSION, setLogLevel, }; for (let i = 0; i < KNOWN_NAMESPACES.length; i++) { const namespace = KNOWN_NAMESPACES[i]; Object.defineProperty(FIREBASE_ROOT, namespace, { enumerable: false, get: firebaseRootModuleProxy.bind(null, FIREBASE_ROOT, namespace), }); } return FIREBASE_ROOT; } /** * * @returns {*} */ export function getFirebaseRoot() { if (FIREBASE_ROOT) { return FIREBASE_ROOT; } return createFirebaseRoot(); } /** * * @param options * @returns {*} */ export function createModuleNamespace(options = {}) { const { namespace, ModuleClass } = options; if (!NAMESPACE_REGISTRY[namespace]) { // validation only for internal / module dev usage if (FirebaseModule.__extended__ !== ModuleClass.__extended__) { throw new Error('INTERNAL ERROR: ModuleClass must be an instance of FirebaseModule.'); } NAMESPACE_REGISTRY[namespace] = Object.assign({}, options); } return getFirebaseRoot()[namespace]; }