2017-03-02 11:40:08 +00:00
|
|
|
/**
|
|
|
|
* @flow
|
|
|
|
* Database representation wrapper
|
|
|
|
*/
|
|
|
|
import { NativeModules, NativeEventEmitter } from 'react-native';
|
|
|
|
|
|
|
|
import { Base } from './../base';
|
2017-03-24 22:53:56 +00:00
|
|
|
import Snapshot from './snapshot';
|
|
|
|
import Reference from './reference';
|
|
|
|
import TransactionHandler from './transaction';
|
2017-05-25 22:39:06 +00:00
|
|
|
import { promisify, nativeSDKMissing } from './../../utils';
|
2017-03-02 11:40:08 +00:00
|
|
|
|
2017-03-02 13:09:41 +00:00
|
|
|
const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
|
2017-03-02 11:40:08 +00:00
|
|
|
const FirebaseDatabaseEvt = new NativeEventEmitter(FirebaseDatabase);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @class Database
|
|
|
|
*/
|
|
|
|
export default class Database extends Base {
|
|
|
|
constructor(firebase: Object, options: Object = {}) {
|
|
|
|
super(firebase, options);
|
2017-05-25 22:39:06 +00:00
|
|
|
if (FirebaseDatabase.nativeSDKMissing) {
|
|
|
|
return nativeSDKMissing('database');
|
|
|
|
}
|
|
|
|
|
2017-04-26 11:21:53 +00:00
|
|
|
this.references = {};
|
2017-03-02 11:40:08 +00:00
|
|
|
this.serverTimeOffset = 0;
|
|
|
|
this.persistenceEnabled = false;
|
|
|
|
this.namespace = 'firebase:database';
|
2017-03-24 22:53:56 +00:00
|
|
|
this.transaction = new TransactionHandler(firebase, this, FirebaseDatabaseEvt);
|
2017-03-02 11:40:08 +00:00
|
|
|
|
|
|
|
if (firebase.options.persistence === true) {
|
|
|
|
this._setPersistence(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.successListener = FirebaseDatabaseEvt.addListener(
|
|
|
|
'database_event',
|
|
|
|
event => this._handleDatabaseEvent(event)
|
|
|
|
);
|
|
|
|
|
|
|
|
this.errorListener = FirebaseDatabaseEvt.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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a new firebase reference instance
|
|
|
|
* @param path
|
|
|
|
* @returns {Reference}
|
|
|
|
*/
|
|
|
|
ref(path: string) {
|
|
|
|
return new Reference(this, path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @param modifiersString
|
|
|
|
* @param modifiers
|
|
|
|
* @param eventName
|
|
|
|
* @param cb
|
|
|
|
* @param errorCb
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2017-04-26 11:21:53 +00:00
|
|
|
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;
|
|
|
|
return promisify('on', FirebaseDatabase)(refId, path, query.getModifiers(), listenerId, eventName);
|
2017-03-02 11:40:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @param modifiersString
|
|
|
|
* @param eventName
|
|
|
|
* @param origCB
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2017-05-25 22:39:06 +00:00
|
|
|
off(refId: number,
|
|
|
|
// $FlowFixMe
|
|
|
|
listeners: Array<DatabaseListener>,
|
|
|
|
remainingListenersCount: number) {
|
2017-04-26 11:21:53 +00:00
|
|
|
this.log.debug('off() : ', refId, listeners);
|
2017-03-02 11:40:08 +00:00
|
|
|
|
2017-04-26 11:21:53 +00:00
|
|
|
// Delete the reference if there are no more listeners
|
|
|
|
if (remainingListenersCount === 0) delete this.references[refId];
|
2017-03-02 11:40:08 +00:00
|
|
|
|
2017-04-26 11:21:53 +00:00
|
|
|
if (listeners.length === 0) return Promise.resolve();
|
2017-03-02 11:40:08 +00:00
|
|
|
|
2017-04-26 11:21:53 +00:00
|
|
|
return promisify('off', FirebaseDatabase)(refId, listeners.map(listener => ({
|
|
|
|
listenerId: listener.listenerId,
|
|
|
|
eventName: listener.eventName,
|
|
|
|
})));
|
2017-03-02 11:40:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-26 11:21:53 +00:00
|
|
|
* Removes all references and their native listeners
|
2017-03-02 11:40:08 +00:00
|
|
|
* @returns {Promise.<*>}
|
|
|
|
*/
|
|
|
|
cleanup() {
|
|
|
|
const promises = [];
|
2017-04-26 11:21:53 +00:00
|
|
|
Object.keys(this.references).forEach((refId) => {
|
|
|
|
const ref = this.references[refId];
|
2017-05-30 11:46:28 +00:00
|
|
|
promises.push(this.off(Number(refId), Object.values(ref.refListeners), 0));
|
2017-03-02 11:40:08 +00:00
|
|
|
});
|
|
|
|
return Promise.all(promises);
|
|
|
|
}
|
|
|
|
|
|
|
|
goOnline() {
|
|
|
|
FirebaseDatabase.goOnline();
|
|
|
|
}
|
|
|
|
|
|
|
|
goOffline() {
|
|
|
|
FirebaseDatabase.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;
|
2017-03-10 18:12:46 +00:00
|
|
|
this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence.`);
|
|
|
|
return promisify('enablePersistence', FirebaseDatabase)(enable);
|
2017-03-02 11:40:08 +00:00
|
|
|
}
|
|
|
|
|
2017-03-10 18:12:46 +00:00
|
|
|
return Promise.reject({ status: 'Already enabled' });
|
2017-03-02 11:40:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_handleDatabaseEvent(event: Object) {
|
|
|
|
const body = event.body || {};
|
2017-04-26 11:21:53 +00:00
|
|
|
const { refId, listenerId, path, eventName, snapshot } = body;
|
|
|
|
this.log.debug('_handleDatabaseEvent: ', refId, listenerId, path, eventName, snapshot && snapshot.key);
|
2017-05-30 11:46:28 +00:00
|
|
|
if (this.references[refId] && this.references[refId].refListeners[listenerId]) {
|
|
|
|
const cb = this.references[refId].refListeners[listenerId].successCallback;
|
2017-04-26 11:21:53 +00:00
|
|
|
cb(new Snapshot(this.references[refId], snapshot));
|
2017-03-02 11:40:08 +00:00
|
|
|
} else {
|
2017-04-26 11:21:53 +00:00
|
|
|
FirebaseDatabase.off(refId, [{ listenerId, eventName }], () => {
|
|
|
|
this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', refId, listenerId, eventName);
|
2017-03-02 11:40:08 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-07 16:54:04 +00:00
|
|
|
/**
|
|
|
|
* Converts an native error object to a 'firebase like' error.
|
|
|
|
* @param error
|
|
|
|
* @returns {Error}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_toFirebaseError(error) {
|
|
|
|
const { path, message, modifiers, code, details } = error;
|
2017-03-08 13:21:21 +00:00
|
|
|
let firebaseMessage = `FirebaseError: ${message.toLowerCase().replace(/\s/g, '_')}`;
|
2017-03-07 16:54:04 +00:00
|
|
|
|
|
|
|
if (path) {
|
2017-03-08 13:21:21 +00:00
|
|
|
firebaseMessage = `${firebaseMessage} at /${path}\r\n`;
|
2017-03-07 16:54:04 +00:00
|
|
|
}
|
|
|
|
|
2017-04-04 16:58:20 +00:00
|
|
|
// $FlowFixMe
|
|
|
|
const firebaseError: FirebaseError = new Error(firebaseMessage);
|
2017-03-07 16:54:04 +00:00
|
|
|
|
|
|
|
firebaseError.code = code;
|
|
|
|
firebaseError.path = path;
|
|
|
|
firebaseError.details = details;
|
|
|
|
firebaseError.modifiers = modifiers;
|
|
|
|
|
|
|
|
return firebaseError;
|
|
|
|
}
|
|
|
|
|
2017-03-02 11:40:08 +00:00
|
|
|
/**
|
|
|
|
*
|
2017-03-07 16:54:04 +00:00
|
|
|
* @param error
|
2017-03-02 11:40:08 +00:00
|
|
|
* @private
|
|
|
|
*/
|
2017-03-07 16:54:04 +00:00
|
|
|
_handleDatabaseError(error: Object = {}) {
|
2017-04-26 11:21:53 +00:00
|
|
|
const { refId, listenerId, path } = error;
|
2017-03-07 16:54:04 +00:00
|
|
|
const firebaseError = this._toFirebaseError(error);
|
2017-03-02 11:40:08 +00:00
|
|
|
|
2017-04-26 11:21:53 +00:00
|
|
|
this.log.debug('_handleDatabaseError ->', refId, listenerId, path, 'database_error', error);
|
2017-03-02 11:40:08 +00:00
|
|
|
|
2017-05-30 11:46:28 +00:00
|
|
|
if (this.references[refId] && this.references[refId].refListeners[listenerId]) {
|
|
|
|
const failureCb = this.references[refId].refListeners[listenerId].failureCallback;
|
2017-04-26 11:21:53 +00:00
|
|
|
if (failureCb) failureCb(firebaseError);
|
|
|
|
}
|
2017-03-02 11:40:08 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-22 20:15:02 +00:00
|
|
|
|
|
|
|
export const statics = {
|
|
|
|
ServerValue: {
|
|
|
|
TIMESTAMP: FirebaseDatabase.serverValueTimestamp || { '.sv': 'timestamp' },
|
|
|
|
},
|
|
|
|
};
|