@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
234 lines (198 loc) • 7.09 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 { APP_NATIVE_MODULE } from '../constants';
import NativeFirebaseError from '../NativeFirebaseError';
import RNFBNativeEventEmitter from '../RNFBNativeEventEmitter';
import SharedEventEmitter from '../SharedEventEmitter';
import { getReactNativeModule } from '../nativeModule';
import { isAndroid, isIOS } from '../../common';
const NATIVE_MODULE_REGISTRY = {};
const NATIVE_MODULE_EVENT_SUBSCRIPTIONS = {};
function nativeModuleKey(module) {
return `${module._customUrlOrRegion || ''}:${module.app.name}:${module._config.namespace}`;
}
/**
* Wraps a native module method to provide
* auto prepended args and custom Error classes.
*
* @param namespace
* @param method
* @param argToPrepend
* @returns {Function}
*/
function nativeModuleMethodWrapped(namespace, method, argToPrepend) {
return (...args) => {
const possiblePromise = method(...[...argToPrepend, ...args]);
if (possiblePromise && possiblePromise.then) {
const jsStack = new Error().stack;
return possiblePromise.catch(nativeError =>
Promise.reject(new NativeFirebaseError(nativeError, jsStack, namespace)),
);
}
return possiblePromise;
};
}
/**
* Prepends all arguments in prependArgs to all native method calls
*
* @param namespace
* @param NativeModule
* @param argToPrepend
*/
function nativeModuleWrapped(namespace, NativeModule, argToPrepend) {
const native = {};
if (!NativeModule) {
return NativeModule;
}
let properties = Object.keys(Object.getPrototypeOf(NativeModule));
if (!properties.length) properties = Object.keys(NativeModule);
for (let i = 0, len = properties.length; i < len; i++) {
const property = properties[i];
if (typeof NativeModule[property] === 'function') {
native[property] = nativeModuleMethodWrapped(namespace, NativeModule[property], argToPrepend);
} else {
native[property] = NativeModule[property];
}
}
return native;
}
/**
* Initialises and wraps all the native module methods.
*
* @param module
* @returns {*}
*/
function initialiseNativeModule(module) {
const config = module._config;
const key = nativeModuleKey(module);
const {
namespace,
nativeEvents,
nativeModuleName,
hasMultiAppSupport,
hasCustomUrlOrRegionSupport,
disablePrependCustomUrlOrRegion,
} = config;
const multiModuleRoot = {};
const multiModule = Array.isArray(nativeModuleName);
const nativeModuleNames = multiModule ? nativeModuleName : [nativeModuleName];
for (let i = 0; i < nativeModuleNames.length; i++) {
const nativeModule = getReactNativeModule(nativeModuleNames[i]);
// only error if there's a single native module
// as multi modules can mean some are optional
if (!multiModule && !nativeModule) {
throw new Error(getMissingModuleHelpText(namespace));
}
if (multiModule) {
multiModuleRoot[nativeModuleNames[i]] = !!nativeModule;
}
const argToPrepend = [];
if (hasMultiAppSupport) {
argToPrepend.push(module.app.name);
}
if (hasCustomUrlOrRegionSupport && !disablePrependCustomUrlOrRegion) {
argToPrepend.push(module._customUrlOrRegion);
}
Object.assign(multiModuleRoot, nativeModuleWrapped(namespace, nativeModule, argToPrepend));
}
if (nativeEvents && nativeEvents.length) {
for (let i = 0, len = nativeEvents.length; i < len; i++) {
subscribeToNativeModuleEvent(nativeEvents[i]);
}
}
Object.freeze(multiModuleRoot);
NATIVE_MODULE_REGISTRY[key] = multiModuleRoot;
return NATIVE_MODULE_REGISTRY[key];
}
/**
* Subscribe to a native event for js side distribution by appName
* React Native events are hard set at compile - cant do dynamic event names
* so we use a single event send it to js and js then internally can prefix it
* and distribute dynamically.
*
* @param eventName
* @private
*/
function subscribeToNativeModuleEvent(eventName) {
if (!NATIVE_MODULE_EVENT_SUBSCRIPTIONS[eventName]) {
RNFBNativeEventEmitter.addListener(eventName, event => {
if (event.appName && event.databaseId) {
// Firestore requires both appName and databaseId to prefix
SharedEventEmitter.emit(`${event.appName}-${event.databaseId}-${eventName}`, event);
} else if (event.appName) {
// native event has an appName property - auto prefix and internally emit
SharedEventEmitter.emit(`${event.appName}-${eventName}`, event);
} else {
// standard event - no need to prefix
SharedEventEmitter.emit(eventName, event);
}
});
NATIVE_MODULE_EVENT_SUBSCRIPTIONS[eventName] = true;
}
}
/**
* Help text for integrating the native counter parts for each firebase module.
*
* @param namespace
* @returns {string}
*/
function getMissingModuleHelpText(namespace) {
const snippet = `firebase.${namespace}()`;
if (isIOS || isAndroid) {
return (
`You attempted to use a Firebase module that's not installed natively on your project by calling ${snippet}.` +
`\r\n\r\nEnsure you have installed the npm package '@react-native-firebase/${namespace}',` +
' have imported it in your project, and have rebuilt your native application.'
);
}
return (
`You attempted to use a Firebase module that's not installed on your project by calling ${snippet}.` +
`\r\n\r\nEnsure you have installed the npm package '@react-native-firebase/${namespace}' and` +
' have imported it in your project.'
);
}
/**
* Gets a wrapped native module instance for the provided firebase module.
* Will attempt to create a new instance if non previously created.
*
* @param module
* @returns {*}
*/
export function getNativeModule(module) {
const key = nativeModuleKey(module);
if (NATIVE_MODULE_REGISTRY[key]) {
return NATIVE_MODULE_REGISTRY[key];
}
return initialiseNativeModule(module);
}
/**
* Custom wrapped app module as it does not have it's own FirebaseModule based class.
*
* @returns {*}
*/
export function getAppModule() {
if (NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]) {
return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE];
}
const namespace = 'app';
const nativeModule = getReactNativeModule(APP_NATIVE_MODULE);
if (!nativeModule) {
throw new Error(getMissingModuleHelpText(namespace));
}
NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] = nativeModuleWrapped(namespace, nativeModule, []);
return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE];
}