UNPKG

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
"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;