@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
262 lines (245 loc) • 9.29 kB
JavaScript
;
/*
* 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/index.js";
import { version as SDK_VERSION } from "../../version.js";
import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from "../constants.js";
import FirebaseModule from "../FirebaseModule.js";
import { getApp, getApps, initializeApp, setLogLevel, setReactNativeAsyncStorage, setOnAppCreate, setOnAppDestroy } from "./app.js";
// 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];
if (moduleNamespace) {
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 config = NAMESPACE_REGISTRY[moduleNamespace];
if (!config) {
throw new Error(`Module namespace '${moduleNamespace}' is not registered.`);
}
const {
hasCustomUrlOrRegionSupport,
hasMultiAppSupport,
ModuleClass
} = config;
// 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 moduleConfig = NAMESPACE_REGISTRY[moduleNamespace];
if (!moduleConfig) {
throw new Error(`Module namespace '${moduleNamespace}' is not registered.`);
}
const module = createDeprecationProxy(new ModuleClass(app, moduleConfig, 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 config = NAMESPACE_REGISTRY[moduleNamespace];
if (!config) {
throw new Error(`Module namespace '${moduleNamespace}' is not registered.`);
}
const {
statics,
hasMultiAppSupport,
ModuleClass
} = config;
// e.g. firebase.storage(app)
function firebaseModuleWithApp(app) {
const _app = app || getApp();
// Duck-type check for FirebaseApp (checking for required properties)
if (!_app || typeof _app !== 'object' || !('name' in _app) || !('options' in _app) || typeof _app.name !== 'string') {
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 moduleConfig = NAMESPACE_REGISTRY[moduleNamespace];
if (!moduleConfig) {
throw new Error(`Module namespace '${moduleNamespace}' is not registered.`);
}
const module = createDeprecationProxy(new ModuleClass(_app, moduleConfig));
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);
}
const 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]) {
// Call private _checkDestroyed method
app._checkDestroyed();
return getOrCreateModuleForApp(app, moduleNamespace);
}
const 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() {
// Create partial root object - module namespaces like 'utils' are added dynamically below
const 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];
if (namespace) {
Object.defineProperty(root, namespace, {
enumerable: false,
get: firebaseRootModuleProxy.bind(null, root, namespace)
});
}
}
FIREBASE_ROOT = root;
return 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
const firebaseModuleExtended = FirebaseModule.__extended__;
const moduleClassExtended = ModuleClass.__extended__;
if (firebaseModuleExtended !== moduleClassExtended) {
throw new Error('INTERNAL ERROR: ModuleClass must be an instance of FirebaseModule.');
}
NAMESPACE_REGISTRY[namespace] = Object.assign({}, options);
}
return getFirebaseRoot()[namespace];
}
//# sourceMappingURL=namespace.js.map