react-native-firebase-compiled
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
796 lines (637 loc) • 23.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _Query = _interopRequireDefault(require("./Query"));
var _DataSnapshot = _interopRequireDefault(require("./DataSnapshot"));
var _OnDisconnect = _interopRequireDefault(require("./OnDisconnect"));
var _log = require("../../utils/log");
var _native = require("../../utils/native");
var _ReferenceBase = _interopRequireDefault(require("../../utils/ReferenceBase"));
var _utils = require("../../utils");
var _SyncTree = _interopRequireDefault(require("../../utils/SyncTree"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
// track all event registrations by path
let listeners = 0;
/**
* Enum for event types
* @readonly
* @enum {String}
*/
const ReferenceEventTypes = {
value: 'value',
child_added: 'child_added',
child_removed: 'child_removed',
child_changed: 'child_changed',
child_moved: 'child_moved'
};
/**
* @typedef {String} ReferenceLocation - Path to location in the database, relative
* to the root reference. Consists of a path where segments are separated by a
* forward slash (/) and ends in a ReferenceKey - except the root location, which
* has no ReferenceKey.
*
* @example
* // root reference location: '/'
* // non-root reference: '/path/to/referenceKey'
*/
/**
* @typedef {String} ReferenceKey - Identifier for each location that is unique to that
* location, within the scope of its parent. The last part of a ReferenceLocation.
*/
/**
* Represents a specific location in your Database that can be used for
* reading or writing data.
*
* You can reference the root using firebase.database().ref() or a child location
* by calling firebase.database().ref("child/path").
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference
* @class Reference
* @extends ReferenceBase
*/
class Reference extends _ReferenceBase.default {
constructor(database, path, existingModifiers) {
super(path);
_defineProperty(this, "_database", void 0);
_defineProperty(this, "_promise", void 0);
_defineProperty(this, "_query", void 0);
_defineProperty(this, "_refListeners", void 0);
this._promise = null;
this._refListeners = {};
this._database = database;
this._query = new _Query.default(this, existingModifiers);
(0, _log.getLogger)(database).debug('Created new Reference', this._getRefKey());
}
/**
* By calling `keepSynced(true)` on a location, the data for that location will
* automatically be downloaded and kept in sync, even when no listeners are
* attached for that location. Additionally, while a location is kept synced,
* it will not be evicted from the persistent disk cache.
*
* @link https://firebase.google.com/docs/reference/android/com/google/firebase/database/Query.html#keepSynced(boolean)
* @param bool
* @returns {*}
*/
keepSynced(bool) {
return (0, _native.getNativeModule)(this._database).keepSynced(this._getRefKey(), this.path, this._query.getModifiers(), bool);
}
/**
* Writes data to this Database location.
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#set
* @param value
* @param onComplete
* @returns {Promise}
*/
set(value, onComplete) {
return (0, _utils.promiseOrCallback)((0, _native.getNativeModule)(this._database).set(this.path, this._serializeAnyType(value)), onComplete);
}
/**
* Sets a priority for the data at this Database location.
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#setPriority
* @param priority
* @param onComplete
* @returns {Promise}
*/
setPriority(priority, onComplete) {
const _priority = this._serializeAnyType(priority);
return (0, _utils.promiseOrCallback)((0, _native.getNativeModule)(this._database).setPriority(this.path, _priority), onComplete);
}
/**
* Writes data the Database location. Like set() but also specifies the priority for that data.
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#setWithPriority
* @param value
* @param priority
* @param onComplete
* @returns {Promise}
*/
setWithPriority(value, priority, onComplete) {
const _value = this._serializeAnyType(value);
const _priority = this._serializeAnyType(priority);
return (0, _utils.promiseOrCallback)((0, _native.getNativeModule)(this._database).setWithPriority(this.path, _value, _priority), onComplete);
}
/**
* Writes multiple values to the Database at once.
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#update
* @param val
* @param onComplete
* @returns {Promise}
*/
update(val, onComplete) {
const value = this._serializeObject(val);
return (0, _utils.promiseOrCallback)((0, _native.getNativeModule)(this._database).update(this.path, value), onComplete);
}
/**
* Removes the data at this Database location.
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#remove
* @param onComplete
* @return {Promise}
*/
remove(onComplete) {
return (0, _utils.promiseOrCallback)((0, _native.getNativeModule)(this._database).remove(this.path), onComplete);
}
/**
* Atomically modifies the data at this location.
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
* @param transactionUpdate
* @param onComplete
* @param applyLocally
*/
transaction(transactionUpdate, onComplete, applyLocally = false) {
if (!(0, _utils.isFunction)(transactionUpdate)) {
return Promise.reject(new Error('Missing transactionUpdate function argument.'));
}
return new Promise((resolve, reject) => {
const onCompleteWrapper = (error, committed, snapshotData) => {
if ((0, _utils.isFunction)(onComplete)) {
if (error) {
onComplete(error, committed, null);
} else {
onComplete(null, committed, new _DataSnapshot.default(this, snapshotData));
}
}
if (error) return reject(error);
return resolve({
committed,
snapshot: new _DataSnapshot.default(this, snapshotData)
});
}; // start the transaction natively
this._database._transactionHandler.add(this, transactionUpdate, onCompleteWrapper, applyLocally);
});
}
/**
*
* @param eventName
* @param successCallback
* @param cancelOrContext
* @param context
* @returns {Promise.<any>}
*/
once(eventName = 'value', successCallback, cancelOrContext, context) {
return (0, _native.getNativeModule)(this._database).once(this._getRefKey(), this.path, this._query.getModifiers(), eventName).then(({
snapshot
}) => {
const _snapshot = new _DataSnapshot.default(this, snapshot);
if ((0, _utils.isFunction)(successCallback)) {
if ((0, _utils.isObject)(cancelOrContext)) successCallback.bind(cancelOrContext)(_snapshot);
if (context && (0, _utils.isObject)(context)) successCallback.bind(context)(_snapshot);
successCallback(_snapshot);
}
return _snapshot;
}).catch(error => {
if ((0, _utils.isFunction)(cancelOrContext)) return cancelOrContext(error);
throw error;
});
}
/**
*
* @param value
* @param onComplete
* @returns {*}
*/
push(value, onComplete) {
if (value === null || value === undefined) {
return new Reference(this._database, `${this.path}/${(0, _utils.generatePushID)(this._database._serverTimeOffset)}`);
}
const newRef = new Reference(this._database, `${this.path}/${(0, _utils.generatePushID)(this._database._serverTimeOffset)}`);
const promise = newRef.set(value); // if callback provided then internally call the set promise with value
if ((0, _utils.isFunction)(onComplete)) {
return promise // $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
.then(() => onComplete(null, newRef)) // $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
.catch(error => onComplete(error, null));
} // otherwise attach promise to 'thenable' reference and return the
// new reference
newRef._setThenable(promise);
return newRef;
}
/**
* MODIFIERS
*/
/**
*
* @returns {Reference}
*/
orderByKey() {
return this.orderBy('orderByKey');
}
/**
*
* @returns {Reference}
*/
orderByPriority() {
return this.orderBy('orderByPriority');
}
/**
*
* @returns {Reference}
*/
orderByValue() {
return this.orderBy('orderByValue');
}
/**
*
* @param key
* @returns {Reference}
*/
orderByChild(key) {
return this.orderBy('orderByChild', key);
}
/**
*
* @param name
* @param key
* @returns {Reference}
*/
orderBy(name, key) {
const newRef = new Reference(this._database, this.path, this._query.getModifiers());
newRef._query.orderBy(name, key);
return newRef;
}
/**
* LIMITS
*/
/**
*
* @param limit
* @returns {Reference}
*/
limitToLast(limit) {
return this.limit('limitToLast', limit);
}
/**
*
* @param limit
* @returns {Reference}
*/
limitToFirst(limit) {
return this.limit('limitToFirst', limit);
}
/**
*
* @param name
* @param limit
* @returns {Reference}
*/
limit(name, limit) {
const newRef = new Reference(this._database, this.path, this._query.getModifiers());
newRef._query.limit(name, limit);
return newRef;
}
/**
* FILTERS
*/
/**
*
* @param value
* @param key
* @returns {Reference}
*/
equalTo(value, key) {
return this.filter('equalTo', value, key);
}
/**
*
* @param value
* @param key
* @returns {Reference}
*/
endAt(value, key) {
return this.filter('endAt', value, key);
}
/**
*
* @param value
* @param key
* @returns {Reference}
*/
startAt(value, key) {
return this.filter('startAt', value, key);
}
/**
*
* @param name
* @param value
* @param key
* @returns {Reference}
*/
filter(name, value, key) {
const newRef = new Reference(this._database, this.path, this._query.getModifiers());
newRef._query.filter(name, value, key);
return newRef;
}
/**
*
* @returns {OnDisconnect}
*/
onDisconnect() {
return new _OnDisconnect.default(this);
}
/**
* Creates a Reference to a child of the current Reference, using a relative path.
* No validation is performed on the path to ensure it has a valid format.
* @param {String} path relative to current ref's location
* @returns {!Reference} A new Reference to the path provided, relative to the current
* Reference
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#child}
*/
child(path) {
return new Reference(this._database, `${this.path}/${path}`);
}
/**
* Return the ref as a path string
* @returns {string}
*/
toString() {
return `${this._database.databaseUrl}/${this.path}`;
}
/**
* 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) {
return !!otherRef && otherRef.constructor === Reference && otherRef.key === this.key && this._query.queryIdentifier() === otherRef._query.queryIdentifier();
}
/**
* GETTERS
*/
/**
* The parent location of a Reference, or null for the root Reference.
* @type {Reference}
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#parent}
*/
get parent() {
if (this.path === '/') return null;
return new Reference(this._database, this.path.substring(0, this.path.lastIndexOf('/')));
}
/**
* A reference to itself
* @type {!Reference}
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#ref}
*/
get ref() {
return this;
}
/**
* Reference to the root of the database: '/'
* @type {!Reference}
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#root}
*/
get root() {
return new Reference(this._database, '/');
}
/**
* Access then method of promise if set
* @return {*}
*/
then(fnResolve, fnReject) {
if ((0, _utils.isFunction)(fnResolve) && this._promise && this._promise.then) {
return this._promise.then.bind(this._promise)(result => {
this._promise = null;
return fnResolve(result);
}, possibleErr => {
this._promise = null;
if ((0, _utils.isFunction)(fnReject)) {
return fnReject(possibleErr);
}
throw possibleErr;
});
}
throw new Error("Cannot read property 'then' of undefined.");
}
/**
* Access catch method of promise if set
* @return {*}
*/
catch(fnReject) {
if ((0, _utils.isFunction)(fnReject) && this._promise && this._promise.catch) {
return this._promise.catch.bind(this._promise)(possibleErr => {
this._promise = null;
return fnReject(possibleErr);
});
}
throw new Error("Cannot read property 'catch' of undefined.");
}
/**
* INTERNALS
*/
/**
* Generate a unique registration key.
*
* @return {string}
*/
_getRegistrationKey(eventType) {
return `$${this._database.databaseUrl}$/${this.path}$${this._query.queryIdentifier()}$${listeners}$${eventType}`;
}
/**
* Generate a string that uniquely identifies this
* combination of path and query modifiers
*
* @return {string}
* @private
*/
_getRefKey() {
return `$${this._database.databaseUrl}$/${this.path}$${this._query.queryIdentifier()}`;
}
/**
* Set the promise this 'thenable' reference relates to
* @param promise
* @private
*/
_setThenable(promise) {
this._promise = promise;
}
/**
*
* @param obj
* @returns {Object}
* @private
*/
_serializeObject(obj) {
if (!(0, _utils.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 (0, _utils.tryJSONParse)((0, _utils.tryJSONStringify)(obj));
}
/**
*
* @param value
* @returns {*}
* @private
*/
_serializeAnyType(value) {
if ((0, _utils.isObject)(value)) {
return {
type: 'object',
value: this._serializeObject(value)
};
}
return {
type: typeof value,
value
};
}
/**
* Register a listener for data changes at the current ref's location.
* The primary method of reading data from a Database.
*
* Listeners can be unbound using {@link off}.
*
* Event Types:
*
* - value: {@link callback}.
* - child_added: {@link callback}
* - child_removed: {@link callback}
* - child_changed: {@link callback}
* - child_moved: {@link callback}
*
* @param {ReferenceEventType} eventType - Type of event to attach a callback for.
* @param {ReferenceEventCallback} callback - Function that will be called
* when the event occurs with the new data.
* @param {cancelCallbackOrContext=} cancelCallbackOrContext - Optional callback that is called
* if the event subscription fails. {@link cancelCallbackOrContext}
* @param {*=} context - Optional object to bind the callbacks to when calling them.
* @returns {ReferenceEventCallback} callback function, unmodified (unbound), for
* convenience if you want to pass an inline function to on() and store it later for
* removing using off().
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#on}
*/
on(eventType, callback, cancelCallbackOrContext, context) {
if (!eventType) {
throw new Error('Query.on failed: Function called with 0 arguments. Expects at least 2.');
}
if (!(0, _utils.isString)(eventType) || !ReferenceEventTypes[eventType]) {
throw new Error(`Query.on failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
}
if (!callback) {
throw new Error('Query.on failed: Function called with 1 argument. Expects at least 2.');
}
if (!(0, _utils.isFunction)(callback)) {
throw new Error('Query.on failed: Second argument must be a valid function.');
}
if (cancelCallbackOrContext && !(0, _utils.isFunction)(cancelCallbackOrContext) && !(0, _utils.isObject)(context) && !(0, _utils.isObject)(cancelCallbackOrContext)) {
throw new Error('Query.on failed: Function called with 3 arguments, but third optional argument `cancelCallbackOrContext` was not a function.');
}
if (cancelCallbackOrContext && !(0, _utils.isFunction)(cancelCallbackOrContext) && context) {
throw new Error('Query.on failed: Function called with 4 arguments, but third optional argument `cancelCallbackOrContext` was not a function.');
}
const eventRegistrationKey = this._getRegistrationKey(eventType);
const registrationCancellationKey = `${eventRegistrationKey}$cancelled`;
const _context = cancelCallbackOrContext && !(0, _utils.isFunction)(cancelCallbackOrContext) ? cancelCallbackOrContext : context;
const registrationObj = {
eventType,
ref: this,
path: this.path,
key: this._getRefKey(),
appName: this._database.app.name,
dbURL: this._database.databaseUrl,
eventRegistrationKey
};
_SyncTree.default.addRegistration(_objectSpread({}, registrationObj, {
listener: _context ? callback.bind(_context) : callback
}));
if (cancelCallbackOrContext && (0, _utils.isFunction)(cancelCallbackOrContext)) {
// cancellations have their own separate registration
// as these are one off events, and they're not guaranteed
// to occur either, only happens on failure to register on native
_SyncTree.default.addRegistration({
ref: this,
once: true,
path: this.path,
key: this._getRefKey(),
appName: this._database.app.name,
dbURL: this._database.databaseUrl,
eventType: `${eventType}$cancelled`,
eventRegistrationKey: registrationCancellationKey,
listener: _context ? cancelCallbackOrContext.bind(_context) : cancelCallbackOrContext
});
} // initialise the native listener if not already listening
(0, _native.getNativeModule)(this._database).on({
eventType,
path: this.path,
key: this._getRefKey(),
appName: this._database.app.name,
modifiers: this._query.getModifiers(),
hasCancellationCallback: (0, _utils.isFunction)(cancelCallbackOrContext),
registration: {
eventRegistrationKey,
key: registrationObj.key,
registrationCancellationKey
}
}); // increment number of listeners - just s short way of making
// every registration unique per .on() call
listeners += 1; // return original unbound successCallback for
// the purposes of calling .off(eventType, callback) at a later date
return callback;
}
/**
* Detaches a callback previously attached with on().
*
* Detach a callback previously attached with on(). Note that if on() was called
* multiple times with the same eventType and callback, the callback will be called
* multiple times for each event, and off() must be called multiple times to
* remove the callback. Calling off() on a parent listener will not automatically
* remove listeners registered on child nodes, off() must also be called on any
* child listeners to remove the callback.
*
* If a callback is not specified, all callbacks for the specified eventType will be removed.
* Similarly, if no eventType or callback is specified, all callbacks for the Reference will be removed.
* @param eventType
* @param originalCallback
*/
off(eventType = '', originalCallback) {
if (!arguments.length) {
// Firebase Docs:
// if no eventType or callback is specified, all callbacks for the Reference will be removed.
return _SyncTree.default.removeListenersForRegistrations(_SyncTree.default.getRegistrationsByPath(this.path));
}
/*
* VALIDATE ARGS
*/
if (eventType && (!(0, _utils.isString)(eventType) || !ReferenceEventTypes[eventType])) {
throw new Error(`Query.off failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
}
if (originalCallback && !(0, _utils.isFunction)(originalCallback)) {
throw new Error('Query.off failed: Function called with 2 arguments, but second optional argument was not a function.');
} // Firebase Docs:
// Note that if on() was called
// multiple times with the same eventType and callback, the callback will be called
// multiple times for each event, and off() must be called multiple times to
// remove the callback.
// Remove only a single registration
if (eventType && originalCallback) {
const registration = _SyncTree.default.getOneByPathEventListener(this.path, eventType, originalCallback);
if (!registration) return []; // remove the paired cancellation registration if any exist
_SyncTree.default.removeListenersForRegistrations([`${registration}$cancelled`]); // remove only the first registration to match firebase web sdk
// call multiple times to remove multiple registrations
return _SyncTree.default.removeListenerRegistrations(originalCallback, [registration]);
} // Firebase Docs:
// If a callback is not specified, all callbacks for the specified eventType will be removed.
const registrations = _SyncTree.default.getRegistrationsByPathEvent(this.path, eventType);
_SyncTree.default.removeListenersForRegistrations(_SyncTree.default.getRegistrationsByPathEvent(this.path, `${eventType}$cancelled`));
return _SyncTree.default.removeListenersForRegistrations(registrations);
}
} // eslint-disable-next-line no-unused-vars
// class ThenableReference<+R> extends Reference {
// then<U>(
// onFulfill?: (value: R) => Promise<U> | U,
// onReject?: (error: any) => Promise<U> | U
// ): Promise<U>;
// catch<U>(onReject?: (error: any) => Promise<U> | U): Promise<R | U>;
// }
exports.default = Reference;