react-native-firebase/lib/modules/database/reference.js

446 lines
11 KiB
JavaScript
Raw Normal View History

2017-03-02 11:40:08 +00:00
/**
* @flow
*/
import { NativeModules } from 'react-native';
import Query from './query.js';
import Snapshot from './snapshot';
import Disconnect from './disconnect';
import { ReferenceBase } from './../base';
import { promisify, isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils';
2017-03-02 13:09:41 +00:00
const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
// Unique Reference ID for native events
let refId = 1;
2017-03-02 11:40:08 +00:00
/**
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference
2017-03-02 11:40:08 +00:00
* @class Reference
*/
export default class Reference extends ReferenceBase {
refId: number;
listeners: { [listenerId: number]: DatabaseListener };
database: FirebaseDatabase;
2017-03-02 11:40:08 +00:00
query: Query;
constructor(database: FirebaseDatabase, path: string, existingModifiers?: Array<DatabaseModifier>) {
super(database.firebase, path);
this.refId = refId++;
this.listeners = {};
this.database = database;
2017-03-02 11:40:08 +00:00
this.namespace = 'firebase:db:ref';
this.query = new Query(this, path, existingModifiers);
this.log.debug('Created new Reference', this.refId, this.path);
2017-03-02 11:40:08 +00:00
}
/**
*
* @param bool
* @returns {*}
*/
keepSynced(bool: boolean) {
const path = this.path;
2017-03-02 11:40:08 +00:00
return promisify('keepSynced', FirebaseDatabase)(path, bool);
}
/**
*
* @param value
* @returns {*}
*/
set(value: any) {
const path = this.path;
2017-03-02 11:40:08 +00:00
const _value = this._serializeAnyType(value);
return promisify('set', FirebaseDatabase)(path, _value);
}
/**
*
* @param val
* @returns {*}
*/
update(val: Object) {
const path = this.path;
2017-03-02 11:40:08 +00:00
const value = this._serializeObject(val);
return promisify('update', FirebaseDatabase)(path, value);
}
/**
*
* @returns {*}
*/
remove() {
return promisify('remove', FirebaseDatabase)(this.path);
2017-03-02 11:40:08 +00:00
}
/**
*
* @param value
* @param onComplete
* @returns {*}
*/
push(value: any, onComplete: Function) {
if (value === null || value === undefined) {
return new Reference(this.database, `${this.path}/${generatePushID(this.database.serverTimeOffset)}`);
2017-03-02 11:40:08 +00:00
}
const path = this.path;
2017-03-02 11:40:08 +00:00
const _value = this._serializeAnyType(value);
return promisify('push', FirebaseDatabase)(path, _value)
.then(({ ref }) => {
const newRef = new Reference(this.database, ref);
2017-03-02 11:40:08 +00:00
if (isFunction(onComplete)) return onComplete(null, newRef);
return newRef;
}).catch((e) => {
if (isFunction(onComplete)) return onComplete(e, null);
return e;
});
}
/**
*
* @param eventName
* @param successCallback
* @param failureCallback
* @param context TODO
* @returns {*}
*/
on(eventName: string, successCallback: () => any, failureCallback: () => any) {
if (!isFunction(successCallback)) throw new Error('The specified callback must be a function');
if (failureCallback && !isFunction(failureCallback)) throw new Error('The specified error callback must be a function');
this.log.debug('adding reference.on', this.refId, eventName);
const listener = {
listenerId: Object.keys(this.listeners).length + 1,
eventName,
successCallback,
failureCallback,
};
this.listeners[listener.listenerId] = listener;
this.database.on(this, listener);
return successCallback;
2017-03-02 11:40:08 +00:00
}
/**
*
* @param eventName
* @param successCallback
* @param failureCallback
* @param context TODO
* @returns {Promise.<TResult>}
*/
once(eventName: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: FirebaseError) => void) {
return promisify('once', FirebaseDatabase)(this.refId, this.path, this.query.getModifiers(), eventName)
2017-03-02 11:40:08 +00:00
.then(({ snapshot }) => new Snapshot(this, snapshot))
.then((snapshot) => {
if (isFunction(successCallback)) successCallback(snapshot);
2017-03-02 11:40:08 +00:00
return snapshot;
})
.catch((error) => {
const firebaseError = this.database._toFirebaseError(error);
2017-03-07 17:35:48 +00:00
if (isFunction(failureCallback)) return failureCallback(firebaseError);
return Promise.reject(firebaseError);
2017-03-02 11:40:08 +00:00
});
}
2017-03-07 17:35:48 +00:00
/**
*
* @param eventName
2017-03-07 17:35:48 +00:00
* @param origCB
* @returns {*}
*/
off(eventName?: string = '', origCB?: () => any) {
this.log.debug('ref.off(): ', this.refId, eventName);
let listenersToRemove;
if (eventName && origCB) {
listenersToRemove = Object.values(this.listeners).filter((listener) => {
return listener.eventName === eventName && listener.successCallback === origCB;
});
// Only remove a single listener as per the web spec
if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]];
} else if (eventName) {
listenersToRemove = Object.values(this.listeners).filter((listener) => {
return listener.eventName === eventName;
});
} else if (origCB) {
listenersToRemove = Object.values(this.listeners).filter((listener) => {
return listener.successCallback === origCB;
});
} else {
listenersToRemove = Object.values(this.listeners);
}
// Remove the listeners from the reference to prevent memory leaks
listenersToRemove.forEach((listener) => {
delete this.listeners[listener.listenerId];
});
return this.database.off(this.refId, listenersToRemove, Object.keys(this.listeners).length);
}
/**
* Atomically modifies the data at this location.
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
* @param transactionUpdate
* @param onComplete
* @param applyLocally
*/
2017-04-19 10:30:08 +00:00
transaction(transactionUpdate: Function, onComplete, applyLocally: boolean = false) {
if (!isFunction(transactionUpdate)) return Promise.reject(new Error('Missing transactionUpdate function argument.'));
return new Promise((resolve, reject) => {
const onCompleteWrapper = (error, committed, snapshotData) => {
if (error) {
2017-04-04 16:58:20 +00:00
if (typeof onComplete === 'function') {
onComplete(error, committed, null);
}
return reject(error);
}
const snapshot = new Snapshot(this, snapshotData);
2017-04-04 16:58:20 +00:00
if (typeof onComplete === 'function') {
onComplete(null, committed, snapshot);
}
return resolve({ committed, snapshot });
};
this.database.transaction.add(this, transactionUpdate, onCompleteWrapper, applyLocally);
});
2017-03-02 11:40:08 +00:00
}
/**
* MODIFIERS
*/
/**
*
* @returns {Reference}
*/
orderByKey(): Reference {
return this.orderBy('orderByKey');
}
/**
*
* @returns {Reference}
*/
orderByPriority(): Reference {
return this.orderBy('orderByPriority');
}
/**
*
* @returns {Reference}
*/
orderByValue(): Reference {
return this.orderBy('orderByValue');
}
/**
*
* @param key
* @returns {Reference}
*/
orderByChild(key: string): Reference {
return this.orderBy('orderByChild', key);
}
/**
*
* @param name
* @param key
* @returns {Reference}
*/
orderBy(name: string, key?: string): Reference {
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.orderBy(name, key);
2017-03-02 11:40:08 +00:00
return newRef;
}
/**
* LIMITS
*/
/**
*
* @param limit
* @returns {Reference}
*/
limitToLast(limit: number): Reference {
return this.limit('limitToLast', limit);
}
/**
*
* @param limit
* @returns {Reference}
*/
limitToFirst(limit: number): Reference {
return this.limit('limitToFirst', limit);
}
/**
*
* @param name
* @param limit
* @returns {Reference}
*/
limit(name: string, limit: number): Reference {
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.limit(name, limit);
2017-03-02 11:40:08 +00:00
return newRef;
}
/**
* FILTERS
*/
/**
*
* @param value
* @param key
* @returns {Reference}
*/
equalTo(value: any, key?: string): Reference {
return this.filter('equalTo', value, key);
}
/**
*
* @param value
* @param key
* @returns {Reference}
*/
endAt(value: any, key?: string): Reference {
return this.filter('endAt', value, key);
}
/**
*
* @param value
* @param key
* @returns {Reference}
*/
startAt(value: any, key?: string): Reference {
return this.filter('startAt', value, key);
}
/**
*
* @param name
* @param value
* @param key
* @returns {Reference}
*/
filter(name: string, value: any, key?: string): Reference {
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.filter(name, value, key);
2017-03-02 11:40:08 +00:00
return newRef;
}
/**
*
* @returns {Disconnect}
*/
2017-03-02 11:40:08 +00:00
onDisconnect() {
return new Disconnect(this.path);
}
/**
* Get a specified child
* @param path
* @returns {Reference}
*/
2017-03-02 11:40:08 +00:00
child(path: string) {
return new Reference(this.database, `${this.path}/${path}`);
2017-03-02 11:40:08 +00:00
}
/**
* Return the ref as a path string
* @returns {string}
*/
2017-03-02 11:40:08 +00:00
toString(): string {
return this.path;
2017-03-02 11:40:08 +00:00
}
2017-04-22 16:59:04 +00:00
/**
* Returns whether another Reference represent the same location and are from the
* same instance of firebase.app.App - multiple firebase apps not currently supported.
* @param {Reference} otherRef - Other reference to compare to this one
* @return {Boolean} Whether otherReference is equal to this one
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#isEqual}
*/
isEqual(otherRef: Reference): boolean {
return !!otherRef && otherRef.constructor === Reference && otherRef.key === this.key;
}
2017-03-02 11:40:08 +00:00
/**
* GETTERS
*/
/**
* Returns the parent ref of the current ref i.e. a ref of /foo/bar would return a new ref to '/foo'
* @returns {*}
*/
get parent(): Reference|null {
if (this.path === '/') return null;
return new Reference(this.database, this.path.substring(0, this.path.lastIndexOf('/')));
2017-03-02 11:40:08 +00:00
}
/**
* A reference to itself
* @type {!Reference}
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#ref}
*/
get ref(): Reference {
return this;
}
2017-03-02 11:40:08 +00:00
/**
* Returns a ref to the root of db - '/'
* @returns {Reference}
*/
get root(): Reference {
return new Reference(this.database, '/');
2017-03-02 11:40:08 +00:00
}
/**
* INTERNALS
*/
/**
*
* @param obj
* @returns {Object}
* @private
*/
_serializeObject(obj: Object) {
if (!isObject(obj)) return obj;
// json stringify then parse it calls toString on Objects / Classes
// that support it i.e new Date() becomes a ISO string.
return tryJSONParse(tryJSONStringify(obj));
}
/**
*
* @param value
* @returns {*}
* @private
*/
_serializeAnyType(value: any) {
if (isObject(value)) {
return {
type: 'object',
value: this._serializeObject(value),
};
}
return {
type: typeof value,
value,
};
}
}