/** * @providesModule Firebase * @flow */ import { NativeModules, NativeEventEmitter } from 'react-native'; import INTERNALS from '../../utils/internals'; import FirebaseApp from './firebase-app'; import { isObject, isString } from '../../utils'; // module imports import AdMob, { statics as AdMobStatics } from '../admob'; import Auth, { statics as AuthStatics } from '../auth'; import Analytics, { statics as AnalyticsStatics } from '../analytics'; import Config, { statics as ConfigStatics } from '../config'; import Crash, { statics as CrashStatics } from '../crash'; import Database, { statics as DatabaseStatics } from '../database'; import Firestore, { statics as FirestoreStatics } from '../firestore'; import Links, { statics as LinksStatics } from '../links'; import Messaging, { statics as MessagingStatics } from '../messaging'; import Performance, { statics as PerformanceStatics } from '../perf'; import Storage, { statics as StorageStatics } from '../storage'; import Utils, { statics as UtilsStatics } from '../utils'; import type { AdMobModule, AnalyticsModule, AuthModule, ConfigModule, CrashModule, DatabaseModule, FirebaseModule, FirebaseModuleAndStatics, FirebaseOptions, FirebaseStatics, FirestoreModule, LinksModule, MessagingModule, PerformanceModule, StorageModule, UtilsModule, } from '../../types'; const FirebaseCoreModule = NativeModules.RNFirebase; class FirebaseCore { _nativeEmitters: { [string]: NativeEventEmitter }; _nativeSubscriptions: { [string]: boolean }; admob: AdMobModule; analytics: AnalyticsModule; auth: AuthModule; config: ConfigModule; crash: CrashModule; database: DatabaseModule; firestore: FirestoreModule; links: LinksModule; messaging: MessagingModule; perf: PerformanceModule; storage: StorageModule; utils: UtilsModule; constructor() { this._nativeEmitters = {}; this._nativeSubscriptions = {}; if (!FirebaseCoreModule) { throw (new Error(INTERNALS.STRINGS.ERROR_MISSING_CORE)); } this._initializeNativeApps(); // modules this.admob = this._appNamespaceOrStatics(AdMobStatics, AdMob); this.analytics = this._appNamespaceOrStatics(AnalyticsStatics, Analytics); this.auth = this._appNamespaceOrStatics(AuthStatics, Auth); this.config = this._appNamespaceOrStatics(ConfigStatics, Config); this.crash = this._appNamespaceOrStatics(CrashStatics, Crash); this.database = this._appNamespaceOrStatics(DatabaseStatics, Database); this.firestore = this._appNamespaceOrStatics(FirestoreStatics, Firestore); this.links = this._appNamespaceOrStatics(LinksStatics, Links); this.messaging = this._appNamespaceOrStatics(MessagingStatics, Messaging); this.perf = this._appNamespaceOrStatics(PerformanceStatics, Performance); this.storage = this._appNamespaceOrStatics(StorageStatics, Storage); this.utils = this._appNamespaceOrStatics(UtilsStatics, Utils); } /** * Bootstraps all native app instances that were discovered on boot * @private */ _initializeNativeApps() { for (let i = 0, len = FirebaseCoreModule.apps.length; i < len; i++) { const app = FirebaseCoreModule.apps[i]; const options = Object.assign({}, app); delete options.name; INTERNALS.APPS[app.name] = new FirebaseApp(app.name, options); INTERNALS.APPS[app.name]._initializeApp(true); } } /** * Web SDK initializeApp * * @param options * @param name * @return {*} */ initializeApp(options: FirebaseOptions, name: string): FirebaseApp { if (name && !isString(name)) { throw new Error(INTERNALS.STRINGS.ERROR_INIT_STRING_NAME); } const _name = (name || INTERNALS.STRINGS.DEFAULT_APP_NAME).toUpperCase(); // return an existing app if found // todo in v4 remove deprecation and throw an error if (INTERNALS.APPS[_name]) { console.warn(INTERNALS.STRINGS.WARN_INITIALIZE_DEPRECATION); return INTERNALS.APPS[_name]; } // only validate if app doesn't already exist // to allow apps already initialized natively // to still go through init without erroring (backwards compatibility) if (!isObject(options)) { throw new Error(INTERNALS.STRINGS.ERROR_INIT_OBJECT); } if (!options.apiKey) { throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('apiKey')); } if (!options.appId) { throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('appId')); } if (!options.databaseURL) { throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('databaseURL')); } if (!options.messagingSenderId) { throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('messagingSenderId')); } if (!options.projectId) { throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('projectId')); } if (!options.storageBucket) { throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('storageBucket')); } INTERNALS.APPS[_name] = new FirebaseApp(_name, options); // only initialize if certain props are available if (options.databaseURL && options.apiKey) { INTERNALS.APPS[_name]._initializeApp(); } return INTERNALS.APPS[_name]; } /** * Retrieves a Firebase app instance. * * When called with no arguments, the default app is returned. * When an app name is provided, the app corresponding to that name is returned. * * @param name * @return {*} */ app(name?: string): FirebaseApp { const _name = name ? name.toUpperCase() : INTERNALS.STRINGS.DEFAULT_APP_NAME; const app = INTERNALS.APPS[_name]; if (!app) throw new Error(INTERNALS.STRINGS.ERROR_APP_NOT_INIT(_name)); return app; } /** * A (read-only) array of all initialized apps. * @return {Array} */ get apps(): Array { return Object.values(INTERNALS.APPS); } /* * INTERNALS */ /** * 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 * @param nativeEmitter * @private */ _subscribeForDistribution(eventName: string, nativeEmitter: NativeEventEmitter) { if (!this._nativeSubscriptions[eventName]) { nativeEmitter.addListener(eventName, (event) => { if (event.appName) { // native event has an appName property - auto prefix and internally emit INTERNALS.SharedEventEmitter.emit(`${event.appName}-${eventName}`, event); } else { // standard event - no need to prefix INTERNALS.SharedEventEmitter.emit(eventName, event); } }); this._nativeSubscriptions[eventName] = true; } } /** * * @param statics * @param InstanceClass * @return {function(FirebaseApp=)} * @private */ _appNamespaceOrStatics(statics: S, InstanceClass: Class): FirebaseModuleAndStatics { const namespace = InstanceClass._NAMESPACE; const getNamespace = (app?: FirebaseApp) => { let _app = app; // throw an error if it's not a valid app instance if (_app && !(_app instanceof FirebaseApp)) throw new Error(INTERNALS.STRINGS.ERROR_NOT_APP(namespace)); // default to the 'DEFAULT' app if no arg provided - will throw an error // if default app not initialized else if (!_app) _app = this.app(INTERNALS.STRINGS.DEFAULT_APP_NAME); return INTERNALS.APPS[_app._name][namespace](_app); }; return Object.assign(getNamespace, statics, { nativeModuleExists: !!NativeModules[InstanceClass._NATIVE_MODULE], }); } /** * * @param name * @param nativeModule * @return {*} * @private */ _getOrSetNativeEmitter(name: string, nativeModule: Object): NativeEventEmitter { if (this._nativeEmitters[name]) { return this._nativeEmitters[name]; } return this._nativeEmitters[name] = new NativeEventEmitter(nativeModule); } } export default new FirebaseCore();