/** * @flow * Database representation wrapper */ import { NativeModules } from 'react-native'; import ModuleBase from './../../utils/ModuleBase'; import Snapshot from './snapshot'; import Reference from './reference'; import TransactionHandler from './transaction'; import { promisify } from './../../utils'; /** * @class Database */ // TODO refactor native and js - legacy code here using old fb methods export default class Database extends ModuleBase { constructor(firebaseApp: Object, options: Object = {}) { super(firebaseApp, options, 'Database', true); this.references = {}; this.serverTimeOffset = 0; this.persistenceEnabled = false; this.transaction = new TransactionHandler(this); if (options.persistence === true) { this._setPersistence(true); } this.successListener = this._eventEmitter.addListener( 'database_event', event => this._handleDatabaseEvent(event), ); this.errorListener = this._eventEmitter.addListener( 'database_error', err => this._handleDatabaseError(err), ); this.offsetRef = this.ref('.info/serverTimeOffset'); this.offsetRef.on('value', (snapshot) => { this.serverTimeOffset = snapshot.val() || this.serverTimeOffset; }); this.log.debug('Created new Database instance', this.options); } /** * TODO wtf is this doing here ;p * @returns {*} * @param ref * @param listener */ on(ref: Reference, listener: DatabaseListener) { const { refId, path, query } = ref; const { listenerId, eventName } = listener; this.log.debug('on() : ', ref.refId, listenerId, eventName); this.references[refId] = ref; // todo pointless promise, doesn't need to return anything return promisify('on', this._native)(refId, path, query.getModifiers(), listenerId, eventName); } /** * * @returns {*} * @param refId * @param listeners * @param remainingListenersCount */ off(refId: number, listeners: Array, remainingListenersCount: number) { this.log.debug('off() : ', refId, listeners); // Delete the reference if there are no more listeners if (remainingListenersCount === 0) delete this.references[refId]; if (listeners.length === 0) return Promise.resolve(); return promisify('off', this._native)(refId, listeners.map(listener => ({ listenerId: listener.listenerId, eventName: listener.eventName, }))); } /** * Removes all references and their native listeners * @returns {Promise.<*>} */ cleanup() { const promises = []; Object.keys(this.references).forEach((refId) => { const ref = this.references[refId]; promises.push(this.off(Number(refId), Object.values(ref.refListeners), 0)); }); return Promise.all(promises); } // goOnline() { // this._native.goOnline(); // } // // goOffline() { // this._native.goOffline(); // } /** * INTERNALS */ // _getServerTime() { // return new Date().getTime() + this.serverTimeOffset; // } // /** // * Enabled / disable database persistence // * @param enable // * @returns {*} // * @private // */ // _setPersistence(enable: boolean = true) { // if (this.persistenceEnabled !== enable) { // this.persistenceEnabled = enable; // this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence.`); // return promisify('enablePersistence', this._native)(enable); // } // // return Promise.reject({ status: 'Already enabled' }); // } /** * * @param event * @private */ _handleDatabaseEvent(event: Object) { const body = event.body || {}; const { refId, listenerId, path, eventName, snapshot, previousChildName } = body; this.log.debug('_handleDatabaseEvent: ', refId, listenerId, path, eventName, snapshot && snapshot.key); if (this.references[refId] && this.references[refId].refListeners[listenerId]) { const cb = this.references[refId].refListeners[listenerId].successCallback; cb(new Snapshot(this.references[refId], snapshot), previousChildName); } else { this._native.off(refId, [{ listenerId, eventName }], () => { this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', refId, listenerId, eventName); }); } } /** * Converts an native error object to a 'firebase like' error. * @param error * @returns {Error} * @private */ _toFirebaseError(error) { const { path, message, modifiers, code, details } = error; let firebaseMessage = `FirebaseError: ${message.toLowerCase().replace(/\s/g, '_')}`; if (path) { firebaseMessage = `${firebaseMessage} at /${path}\r\n`; } // $FlowFixMe const firebaseError: FirebaseError = new Error(firebaseMessage); firebaseError.code = code; firebaseError.path = path; firebaseError.details = details; firebaseError.modifiers = modifiers; return firebaseError; } /** * * @param error * @private */ _handleDatabaseError(error: Object = {}) { const { refId, listenerId, path } = error; const firebaseError = this._toFirebaseError(error); this.log.debug('_handleDatabaseError ->', refId, listenerId, path, 'database_error', error); if (this.references[refId] && this.references[refId].refListeners[listenerId]) { const failureCb = this.references[refId].refListeners[listenerId].failureCallback; if (failureCb) failureCb(firebaseError); } } } export const statics = { ServerValue: NativeModules.FirebaseDatabase ? { TIMESTAMP: NativeModules.FirebaseDatabase.serverValueTimestamp || { '.sv': 'timestamp' }, } : {}, };