mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-04-24 04:24:52 +08:00
[v6] Implement Realtime Database (#2195)
* [database] recreate database branch based off of #2185 * [database] cleanup linting issues * [database] enable tests * [database] add to tests deps
This commit is contained in:
committed by
Elliot Hesp
parent
da9c32000d
commit
fe959bb842
198
packages/database/lib/DatabaseDataSnapshot.js
Normal file
198
packages/database/lib/DatabaseDataSnapshot.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/* eslint-disable no-console */
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { isString, isArray, isFunction, isObject } from '@react-native-firebase/common';
|
||||
import { deepGet } from '@react-native-firebase/common/lib/deeps';
|
||||
|
||||
export default class DatabaseDataSnapshot {
|
||||
constructor(reference, snapshot) {
|
||||
this._snapshot = snapshot;
|
||||
|
||||
if (reference.key !== snapshot.key) {
|
||||
// reference is a query?
|
||||
this._ref = reference.ref.child(snapshot.key);
|
||||
} else {
|
||||
this._ref = reference;
|
||||
}
|
||||
|
||||
// TODO #894
|
||||
// if (this._ref.path === '.info/connected') {
|
||||
// Handle 1/0 vs true/false values on ios/android
|
||||
// }
|
||||
}
|
||||
|
||||
get key() {
|
||||
return this._snapshot.key;
|
||||
}
|
||||
|
||||
get ref() {
|
||||
return this._ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new snapshot of the child location
|
||||
* @param path
|
||||
* @returns {DatabaseDataSnapshot}
|
||||
*/
|
||||
child(path) {
|
||||
if (!isString(path)) {
|
||||
throw new Error(`snapshot().child(*) 'path' must be a string value`);
|
||||
}
|
||||
|
||||
let value = deepGet(this._snapshot.value, path);
|
||||
|
||||
if (value === undefined) value = null;
|
||||
const childRef = this._ref.child(path);
|
||||
|
||||
return new DatabaseDataSnapshot(childRef, {
|
||||
value,
|
||||
key: childRef.key,
|
||||
exists: value !== null,
|
||||
childKeys: isObject(value) ? Object.keys(value) : [],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the value exists
|
||||
*
|
||||
* @returns {(function())|((path: PathLike, callback: (exists: boolean) => void) => void)|boolean|exists|(() => boolean)}
|
||||
*/
|
||||
exists() {
|
||||
return this._snapshot.exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports value and priority
|
||||
*
|
||||
* @returns {{'.priority': *, '.value': *}}
|
||||
*/
|
||||
exportVal() {
|
||||
let { value } = this._snapshot;
|
||||
|
||||
if (isObject(value) || isArray(value)) {
|
||||
value = JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
return {
|
||||
'.value': value,
|
||||
'.priority': this._snapshot.priority,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over keys in order from Firebase
|
||||
*
|
||||
* @param action
|
||||
* @return {boolean}
|
||||
*/
|
||||
forEach(action) {
|
||||
if (!isFunction(action)) {
|
||||
throw new Error(`snapshot.forEach(*) 'action' must be a function.`);
|
||||
}
|
||||
|
||||
// If the value is an array,
|
||||
if (isArray(this._snapshot.value)) {
|
||||
return this._snapshot.value.some((value, i) => {
|
||||
const snapshot = this.child(i.toString());
|
||||
return action(snapshot, i) === true;
|
||||
});
|
||||
}
|
||||
|
||||
if (!this._snapshot.childKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
for (let i = 0; i < this._snapshot.childKeys.length; i++) {
|
||||
const key = this._snapshot.childKeys[i];
|
||||
const snapshot = this.child(key);
|
||||
const actionReturn = action(snapshot, i);
|
||||
|
||||
if (actionReturn === true) {
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
getPriority() {
|
||||
return this._snapshot.priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the returned value for a nested child path
|
||||
*
|
||||
* @param path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasChild(path) {
|
||||
if (!isString(path)) {
|
||||
throw new Error(`snapshot.hasChild(*) 'path' must be a string value.`);
|
||||
}
|
||||
|
||||
return deepGet(this._snapshot.value, path) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the snapshot has any children
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasChildren() {
|
||||
return this.numChildren() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of children this snapshot has
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
numChildren() {
|
||||
const { value } = this._snapshot;
|
||||
if (isArray(value)) return value.length;
|
||||
if (!isObject(value)) return 0;
|
||||
return Object.keys(value).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the .toJSON implementation for snapshot
|
||||
* Same as snapshot.val()
|
||||
* @returns {any}
|
||||
*/
|
||||
toJSON() {
|
||||
return this.val();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized value of the snapshot returned from Firebase
|
||||
*
|
||||
* @returns {any}
|
||||
*/
|
||||
val() {
|
||||
const { value } = this._snapshot;
|
||||
|
||||
if (isObject(value) || isArray(value)) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
141
packages/database/lib/DatabaseOnDisconnect.js
Normal file
141
packages/database/lib/DatabaseOnDisconnect.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/* eslint-disable no-console */
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
isFunction,
|
||||
isNull,
|
||||
isNumber,
|
||||
isObject,
|
||||
isString,
|
||||
isUndefined,
|
||||
promiseWithOptionalCallback,
|
||||
} from '@react-native-firebase/common';
|
||||
|
||||
export default class DatabaseOnDisconnect {
|
||||
constructor(reference) {
|
||||
this._ref = reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#cancel
|
||||
*/
|
||||
cancel(onComplete) {
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().cancel(*) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._ref._database.native.onDisconnectCancel(this._ref.path),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#remove
|
||||
*/
|
||||
remove(onComplete) {
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().remove(*) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._ref._database.native.onDisconnectRemove(this._ref.path),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#set
|
||||
*/
|
||||
set(value, onComplete) {
|
||||
if (isUndefined(value)) {
|
||||
throw new Error(`firebase.database().ref().value(*) 'value' must be defined.`);
|
||||
}
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().set(_, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._ref._database.native.onDisconnectSet(this._ref.path, { value }),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#setwithpriority
|
||||
*/
|
||||
setWithPriority(value, priority, onComplete) {
|
||||
if (isUndefined(value)) {
|
||||
throw new Error(`firebase.database().ref().setWithPriority(*) 'value' must be defined.`);
|
||||
}
|
||||
|
||||
if (!isNumber(priority) && !isString(priority) && !isNull(priority)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().setWithPriority(_, *) 'priority' must be a number, string or null value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().setWithPriority(_, _, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._ref._database.native.onDisconnectSetWithPriority(this._ref.path, { value, priority }),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#update
|
||||
*/
|
||||
update(values, onComplete) {
|
||||
if (!isObject(values)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().update(*) 'values' must be an object.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!Object.keys(values).length) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().update(*) 'values' must be an object containing multiple values.`,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO validate keys; / . # $ |
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().onDisconnect().update(_, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._ref._database.native.onDisconnectUpdate(this._ref.path, { values }),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
}
|
||||
539
packages/database/lib/DatabaseQuery.js
Normal file
539
packages/database/lib/DatabaseQuery.js
Normal file
@@ -0,0 +1,539 @@
|
||||
/* eslint-disable no-console */
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
isBoolean,
|
||||
isFunction,
|
||||
isNull,
|
||||
isNumber,
|
||||
isObject,
|
||||
isString,
|
||||
isUndefined,
|
||||
pathIsEmpty,
|
||||
pathToUrlEncodedString,
|
||||
ReferenceBase,
|
||||
} from '@react-native-firebase/common';
|
||||
import DatabaseReference from './DatabaseReference';
|
||||
import DatabaseDataSnapshot from './DatabaseDataSnapshot';
|
||||
import DatabaseSyncTree from './DatabaseSyncTree';
|
||||
|
||||
const eventTypes = ['value', 'child_added', 'child_changed', 'child_moved', 'child_removed'];
|
||||
|
||||
// Internal listener count
|
||||
let listeners = 0;
|
||||
|
||||
export default class DatabaseQuery extends ReferenceBase {
|
||||
constructor(database, path, modifiers) {
|
||||
super(path);
|
||||
this._database = database;
|
||||
this._modifiers = modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#endat
|
||||
*/
|
||||
get ref() {
|
||||
// TODO require cycle warning?
|
||||
// Require cycle: ../packages/database/lib/DatabaseReference.js -> ../packages/database/lib/DatabaseQuery.js -> ../packages/database/lib/DatabaseReference.js
|
||||
return new DatabaseReference(this._database, this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param key
|
||||
* @return {DatabaseQuery}
|
||||
*/
|
||||
endAt(value, key) {
|
||||
if (!isNumber(value) && !isString(value) && !isBoolean(value) && !isNull(value)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().endAt(*) 'value' must be a number, string, boolean or null value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(key) && !isString(key)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().endAt(_, *) 'key' must be a string value if defined.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this._modifiers.hasEndAt()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().endAt() Ending point was already set (by another call to endAt or equalTo).`,
|
||||
);
|
||||
}
|
||||
|
||||
const modifiers = this._modifiers.endAt(value, key);
|
||||
modifiers.validateModifiers('firebase.database().ref().endAt()');
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param key
|
||||
* @return {DatabaseQuery}
|
||||
*/
|
||||
equalTo(value, key) {
|
||||
if (!isNumber(value) && !isString(value) && !isBoolean(value) && !isNull(value)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().equalTo(*) 'value' must be a number, string, boolean or null value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(key) && !isString(key)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().equalTo(_, *) 'key' must be a string value if defined.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this._modifiers.hasStartAt()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().equalTo() Starting point was already set (by another call to startAt or equalTo).`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this._modifiers.hasEndAt()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().equalTo() Ending point was already set (by another call to endAt or equalTo).`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.startAt(value, key).endAt(value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param other
|
||||
* @return {boolean}
|
||||
*/
|
||||
isEqual(other) {
|
||||
if (!(other instanceof DatabaseQuery)) {
|
||||
throw new Error(`firebase.database().ref().isEqual(*) 'other' must be an instance of Query.`);
|
||||
}
|
||||
|
||||
const sameApp = other._database.app === this._database.app;
|
||||
const sameDatabasePath = other.toString() === this.toString();
|
||||
const sameModifiers = other._modifiers.toString() === this._modifiers.toString();
|
||||
|
||||
return sameApp && sameDatabasePath && sameModifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param limit
|
||||
* @return {DatabaseQuery}
|
||||
*/
|
||||
limitToFirst(limit) {
|
||||
if (this._modifiers.isValidLimit(limit)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().limitToFirst(*) 'limit' must be a positive integer value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this._modifiers.hasLimit()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().limitToFirst(*) Limit was already set (by another call to limitToFirst, or limitToLast)`,
|
||||
);
|
||||
}
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, this._modifiers.limitToFirst(limit));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param limit
|
||||
* @return {DatabaseQuery}
|
||||
*/
|
||||
limitToLast(limit) {
|
||||
if (this._modifiers.isValidLimit(limit)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().limitToLast(*) 'limit' must be a positive integer value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this._modifiers.hasLimit()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().limitToLast(*) Limit was already set (by another call to limitToFirst, or limitToLast)`,
|
||||
);
|
||||
}
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, this._modifiers.limitToLast(limit));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param eventType
|
||||
* @param callback
|
||||
* @param context
|
||||
* @return {DatabaseQuery}
|
||||
*/
|
||||
off(eventType, callback, context) {
|
||||
//
|
||||
if (arguments.length === 0) {
|
||||
// Firebase Docs:
|
||||
// if no eventType or callback is specified, all callbacks for the Reference will be removed
|
||||
return DatabaseSyncTree.removeListenersForRegistrations(
|
||||
DatabaseSyncTree.getRegistrationsByPath(this.path),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(eventType) && !eventTypes.includes(eventType)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().off(*) 'eventType' must be one of ${eventTypes.join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(callback) && !isFunction(callback)) {
|
||||
throw new Error(`firebase.database().ref().off(_, *) 'callback' must be a function.`);
|
||||
}
|
||||
|
||||
if (!isUndefined(context) && !isObject(context)) {
|
||||
throw new Error(`firebase.database().ref().off(_, _, *) 'context' must be an object.`);
|
||||
}
|
||||
|
||||
// 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 && callback) {
|
||||
const registration = DatabaseSyncTree.getOneByPathEventListener(
|
||||
this.path,
|
||||
eventType,
|
||||
callback,
|
||||
);
|
||||
if (!registration) return [];
|
||||
|
||||
// remove the paired cancellation registration if any exist
|
||||
DatabaseSyncTree.removeListenersForRegistrations([`${registration}$cancelled`]);
|
||||
|
||||
// remove only the first registration to match firebase web sdk
|
||||
// call multiple times to remove multiple registrations
|
||||
return DatabaseSyncTree.removeListenerRegistrations(callback, [registration]);
|
||||
}
|
||||
|
||||
// Firebase Docs:
|
||||
// If a callback is not specified, all callbacks for the specified eventType will be removed.
|
||||
const registrations = DatabaseSyncTree.getRegistrationsByPathEvent(this.path, eventType);
|
||||
|
||||
DatabaseSyncTree.removeListenersForRegistrations(
|
||||
DatabaseSyncTree.getRegistrationsByPathEvent(this.path, `${eventType}$cancelled`),
|
||||
);
|
||||
|
||||
return DatabaseSyncTree.removeListenersForRegistrations(registrations);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param eventType
|
||||
* @param callback
|
||||
* @param cancelCallbackOrContext
|
||||
* @param context
|
||||
* @return {DatabaseQuery}
|
||||
*/
|
||||
on(eventType, callback, cancelCallbackOrContext, context) {
|
||||
if (!eventTypes.includes(eventType)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().on(*) 'eventType' must be one of ${eventTypes.join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFunction(callback)) {
|
||||
throw new Error(`firebase.database().ref().on(_, *) 'callback' must be a function.`);
|
||||
}
|
||||
|
||||
if (
|
||||
!isUndefined(cancelCallbackOrContext) &&
|
||||
(!isFunction(cancelCallbackOrContext) && !isObject(cancelCallbackOrContext))
|
||||
) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().on(_, _, *) 'cancelCallbackOrContext' must be a function or object.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(context) && !isObject(context)) {
|
||||
throw new Error(`firebase.database().ref().on(_, _, _, *) 'context' must be an object.`);
|
||||
}
|
||||
|
||||
const queryKey = this._generateQueryKey();
|
||||
const eventRegistrationKey = this._generateQueryEventKey(eventType);
|
||||
const registrationCancellationKey = `${eventRegistrationKey}$cancelled`;
|
||||
const _context =
|
||||
cancelCallbackOrContext && !isFunction(cancelCallbackOrContext)
|
||||
? cancelCallbackOrContext
|
||||
: context;
|
||||
|
||||
// Add a new SyncTree registration
|
||||
DatabaseSyncTree.addRegistration({
|
||||
eventType,
|
||||
ref: this, // Not this.ref?
|
||||
path: this.path,
|
||||
key: queryKey,
|
||||
appName: this._database.app.name,
|
||||
dbURL: this._database._customUrlOrRegion,
|
||||
eventRegistrationKey,
|
||||
listener: _context ? callback.bind(_context) : callback,
|
||||
});
|
||||
|
||||
if (cancelCallbackOrContext && 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
|
||||
|
||||
DatabaseSyncTree.addRegistration({
|
||||
ref: this,
|
||||
once: true,
|
||||
path: this.path,
|
||||
key: queryKey,
|
||||
appName: this._database.app.name,
|
||||
dbURL: this._database._customUrlOrRegion,
|
||||
eventType: `${eventType}$cancelled`,
|
||||
eventRegistrationKey: registrationCancellationKey,
|
||||
listener: _context ? cancelCallbackOrContext.bind(_context) : cancelCallbackOrContext,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO appName, DB already passed along?
|
||||
// TODO what needs going into the object?
|
||||
this._database.native.on({
|
||||
eventType,
|
||||
path: this.path,
|
||||
key: queryKey,
|
||||
appName: this._database.app.name,
|
||||
modifiers: this._modifiers.toArray(),
|
||||
hasCancellationCallback: isFunction(cancelCallbackOrContext),
|
||||
registration: {
|
||||
eventRegistrationKey,
|
||||
key: queryKey,
|
||||
registrationCancellationKey,
|
||||
},
|
||||
});
|
||||
|
||||
// increment number of listeners - just a short way of making
|
||||
// every registration unique per .on() call
|
||||
listeners += 1;
|
||||
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param eventType
|
||||
* @param successCallBack
|
||||
* @param failureCallbackOrContext
|
||||
* @param context
|
||||
*/
|
||||
once(eventType, successCallBack, failureCallbackOrContext, context) {
|
||||
if (!eventTypes.includes(eventType)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().once(*) 'eventType' must be one of ${eventTypes.join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(successCallBack) && !isFunction(successCallBack)) {
|
||||
throw new Error(`firebase.database().ref().once(_, *) 'successCallBack' must be a function.`);
|
||||
}
|
||||
|
||||
if (
|
||||
!isUndefined(failureCallbackOrContext) &&
|
||||
(!isObject(failureCallbackOrContext) && !isFunction(failureCallbackOrContext))
|
||||
) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().once(_, _, *) 'failureCallbackOrContext' must be a function or context.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(context) && !isObject(context)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().once(_, _, _, *) 'context' must be a context object.`,
|
||||
);
|
||||
}
|
||||
|
||||
const modifiers = this._modifiers.toArray();
|
||||
|
||||
return this._database.native
|
||||
.once(this.path, modifiers, eventType)
|
||||
.then(result => {
|
||||
let dataSnapshot;
|
||||
let previousChildName;
|
||||
|
||||
// Child based events return a previousChildName
|
||||
if (eventType === 'value') {
|
||||
dataSnapshot = new DatabaseDataSnapshot(this.ref, result);
|
||||
} else {
|
||||
dataSnapshot = new DatabaseDataSnapshot(this.ref, result.snapshot);
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
previousChildName = result.previousChildName;
|
||||
}
|
||||
|
||||
if (isFunction(successCallBack)) {
|
||||
if (isObject(failureCallbackOrContext)) {
|
||||
successCallBack.bind(failureCallbackOrContext)(dataSnapshot, previousChildName);
|
||||
} else if (isObject(context)) {
|
||||
successCallBack.bind(context)(dataSnapshot, previousChildName);
|
||||
} else {
|
||||
successCallBack(dataSnapshot, previousChildName);
|
||||
}
|
||||
}
|
||||
|
||||
return dataSnapshot;
|
||||
})
|
||||
.catch(error => {
|
||||
if (isFunction(failureCallbackOrContext)) failureCallbackOrContext(error);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbychild
|
||||
*/
|
||||
orderByChild(path) {
|
||||
if (!isString(path)) {
|
||||
throw new Error(`firebase.database().ref().orderByChild(*) 'path' must be a string value.`);
|
||||
}
|
||||
|
||||
if (pathIsEmpty(path)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().orderByChild(*) 'path' cannot be empty. Use orderByValue instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this._modifiers.hasOrderBy()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().orderByChild(*) You can't combine multiple orderBy calls.`,
|
||||
);
|
||||
}
|
||||
|
||||
const modifiers = this._modifiers.orderByChild(path);
|
||||
modifiers.validateModifiers('firebase.database().ref().orderByChild()');
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbykey
|
||||
*/
|
||||
orderByKey() {
|
||||
if (this._modifiers.hasOrderBy()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().orderByKey() You can't combine multiple orderBy calls.`,
|
||||
);
|
||||
}
|
||||
|
||||
const modifiers = this._modifiers.orderByKey();
|
||||
modifiers.validateModifiers('firebase.database().ref().orderByKey()');
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbypriority
|
||||
*/
|
||||
orderByPriority() {
|
||||
if (this._modifiers.hasOrderBy()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().orderByPriority() You can't combine multiple orderBy calls.`,
|
||||
);
|
||||
}
|
||||
|
||||
const modifiers = this._modifiers.orderByPriority();
|
||||
modifiers.validateModifiers('firebase.database().ref().orderByPriority()');
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbyvalue
|
||||
*/
|
||||
orderByValue() {
|
||||
if (this._modifiers.hasOrderBy()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().orderByValue() You can't combine multiple orderBy calls.`,
|
||||
);
|
||||
}
|
||||
|
||||
const modifiers = this._modifiers.orderByValue();
|
||||
modifiers.validateModifiers('firebase.database().ref().orderByValue()');
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, modifiers);
|
||||
}
|
||||
|
||||
startAt(value, key) {
|
||||
if (!isNumber(value) && !isString(value) && !isBoolean(value) && !isNull(value)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().startAt(*) 'value' must be a number, string, boolean or null value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(key) && !isString(key)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().startAt(_, *) 'key' must be a string value if defined.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this._modifiers.hasStartAt()) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().startAt() Starting point was already set (by another call to startAt or equalTo).`,
|
||||
);
|
||||
}
|
||||
|
||||
const modifiers = this._modifiers.startAt(value, key);
|
||||
modifiers.validateModifiers('firebase.database().ref().startAt()');
|
||||
|
||||
return new DatabaseQuery(this._database, this.path, modifiers);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this._database._customUrlOrRegion}${pathToUrlEncodedString(this.path)}`;
|
||||
}
|
||||
|
||||
keepSynced(bool) {
|
||||
if (!isBoolean(bool)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().keepSynced(*) 'bool' value must be a boolean value.`,
|
||||
);
|
||||
}
|
||||
|
||||
return this._database.native.keepSynced(
|
||||
this._generateQueryKey(),
|
||||
this.path,
|
||||
this._modifiers.toArray(),
|
||||
bool,
|
||||
);
|
||||
}
|
||||
|
||||
// Generates a unique string for a query
|
||||
// Ensures any queries called in various orders keep the same key
|
||||
_generateQueryKey() {
|
||||
return `$${this._database._customUrlOrRegion}$/${this.path}$${
|
||||
this._database.app.name
|
||||
}$${this._modifiers.toString()}`;
|
||||
}
|
||||
|
||||
// Generates a unique event registration key
|
||||
_generateQueryEventKey(eventType) {
|
||||
return `${this._generateQueryKey()}$${listeners}$${eventType}`;
|
||||
}
|
||||
}
|
||||
237
packages/database/lib/DatabaseQueryModifiers.js
Normal file
237
packages/database/lib/DatabaseQueryModifiers.js
Normal file
@@ -0,0 +1,237 @@
|
||||
/* eslint-disable no-console */
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { isNull, isNumber, isString } from '@react-native-firebase/common';
|
||||
|
||||
const CONSTANTS = {
|
||||
VIEW_FROM_LEFT: 'left',
|
||||
VIEW_FROM_RIGHT: 'right',
|
||||
};
|
||||
|
||||
export default class DatabaseQueryModifiers {
|
||||
constructor() {
|
||||
this._limit = undefined;
|
||||
this._orderBy = undefined;
|
||||
this._startAt = undefined;
|
||||
this._endAt = undefined;
|
||||
this._modifiers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* LIMIT
|
||||
*
|
||||
*/
|
||||
|
||||
hasLimit() {
|
||||
return this._limit !== undefined;
|
||||
}
|
||||
|
||||
isValidLimit(limit) {
|
||||
return !isNumber(limit) || Math.floor(limit) !== limit || limit <= 0;
|
||||
}
|
||||
|
||||
limitToFirst(limit) {
|
||||
const newLimit = {
|
||||
id: `limit-limitToFirst:${limit}`,
|
||||
name: 'limitToFirst',
|
||||
type: 'limit',
|
||||
value: limit,
|
||||
viewFrom: CONSTANTS.VIEW_FROM_LEFT,
|
||||
};
|
||||
|
||||
this._limit = newLimit;
|
||||
this._modifiers.push(newLimit);
|
||||
return this;
|
||||
}
|
||||
|
||||
limitToLast(limit) {
|
||||
const newLimit = {
|
||||
id: `limit-limitToLast:${limit}`,
|
||||
name: 'limitToLast',
|
||||
type: 'limit',
|
||||
value: limit,
|
||||
viewFrom: CONSTANTS.VIEW_FROM_RIGHT,
|
||||
};
|
||||
|
||||
this._limit = newLimit;
|
||||
this._modifiers.push(newLimit);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* ORDER
|
||||
*
|
||||
*/
|
||||
|
||||
hasOrderBy() {
|
||||
return this._orderBy !== undefined;
|
||||
}
|
||||
|
||||
orderByChild(path) {
|
||||
const newOrder = {
|
||||
id: `order-orderByChild:${path}`,
|
||||
type: 'orderBy',
|
||||
name: 'orderByChild',
|
||||
key: path,
|
||||
};
|
||||
|
||||
this._orderBy = newOrder;
|
||||
this._modifiers.push(newOrder);
|
||||
return this;
|
||||
}
|
||||
|
||||
orderByKey() {
|
||||
const newOrder = {
|
||||
id: `order-orderByKey`,
|
||||
type: 'orderBy',
|
||||
name: 'orderByKey',
|
||||
};
|
||||
|
||||
this._orderBy = newOrder;
|
||||
this._modifiers.push(newOrder);
|
||||
return this;
|
||||
}
|
||||
|
||||
isValidPriority(priority) {
|
||||
return isNumber(priority) || isString(priority) || isNull(priority);
|
||||
}
|
||||
|
||||
orderByPriority() {
|
||||
const newOrder = {
|
||||
id: `order-orderByPriority`,
|
||||
type: 'orderBy',
|
||||
name: 'orderByPriority',
|
||||
};
|
||||
|
||||
this._orderBy = newOrder;
|
||||
this._modifiers.push(newOrder);
|
||||
return this;
|
||||
}
|
||||
|
||||
orderByValue() {
|
||||
const newOrder = {
|
||||
id: `order-orderByValue`,
|
||||
type: 'orderBy',
|
||||
name: 'orderByValue',
|
||||
};
|
||||
|
||||
this._orderBy = newOrder;
|
||||
this._modifiers.push(newOrder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* FILTER
|
||||
*
|
||||
*/
|
||||
|
||||
hasStartAt() {
|
||||
return this._startAt !== undefined;
|
||||
}
|
||||
|
||||
hasEndAt() {
|
||||
return this._endAt !== undefined;
|
||||
}
|
||||
|
||||
startAt(value, key) {
|
||||
const newStart = {
|
||||
id: `filter-startAt:${value}:${key || ''}`,
|
||||
type: 'filter',
|
||||
name: 'startAt',
|
||||
value,
|
||||
valueType: value === null ? 'null' : typeof value,
|
||||
key,
|
||||
};
|
||||
|
||||
this._startAt = newStart;
|
||||
this._modifiers.push(newStart);
|
||||
return this;
|
||||
}
|
||||
|
||||
endAt(value, key) {
|
||||
const newStart = {
|
||||
id: `filter-endAt:${value}:${key || ''}`,
|
||||
type: 'filter',
|
||||
name: 'endAt',
|
||||
value,
|
||||
valueType: value === null ? 'null' : typeof value,
|
||||
key,
|
||||
};
|
||||
|
||||
this._endAt = newStart;
|
||||
this._modifiers.push(newStart);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Returns a modifier array
|
||||
toArray() {
|
||||
return this._modifiers;
|
||||
}
|
||||
|
||||
// Converts the modifier list to a string representation
|
||||
toString() {
|
||||
const sorted = this._modifiers.sort((a, b) => {
|
||||
if (a.id < b.id) return -1;
|
||||
if (a.id > b.id) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
let key = '{';
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
if (i !== 0) key += ',';
|
||||
key += sorted[i].id;
|
||||
}
|
||||
key += '}';
|
||||
return key;
|
||||
}
|
||||
|
||||
validateModifiers(prefix) {
|
||||
if (this._orderBy && this._orderBy.name === 'orderByKey') {
|
||||
if ((this._startAt && !!this._startAt.key) || (this._endAt && !!this._endAt.key)) {
|
||||
throw new Error(
|
||||
`${prefix} When ordering by key, you may only pass a value argument to startAt(), endAt(), or equalTo().`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._orderBy && this._orderBy.name === 'orderByKey') {
|
||||
if (
|
||||
(this._startAt && this._startAt.valueType !== 'string') ||
|
||||
(this._endAt && this._endAt.valueType !== 'string')
|
||||
) {
|
||||
throw new Error(
|
||||
`${prefix} When ordering by key, the value of startAt(), endAt(), or equalTo() must be a string.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._orderBy && this._orderBy.name === 'orderByPriority') {
|
||||
if (
|
||||
(this._startAt && !this.isValidPriority(this._startAt.value)) ||
|
||||
(this._endAt && !this.isValidPriority(this._endAt.value))
|
||||
) {
|
||||
throw new Error(
|
||||
`${prefix} When ordering by priority, the first value of startAt(), endAt(), or equalTo() must be a valid priority value (null, a number, or a string).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
269
packages/database/lib/DatabaseReference.js
Normal file
269
packages/database/lib/DatabaseReference.js
Normal file
@@ -0,0 +1,269 @@
|
||||
/* eslint-disable no-console */
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
isString,
|
||||
pathParent,
|
||||
pathChild,
|
||||
isValidPath,
|
||||
// generateDatabaseId,
|
||||
isNumber,
|
||||
isNull,
|
||||
isUndefined,
|
||||
isFunction,
|
||||
promiseWithOptionalCallback,
|
||||
isObject,
|
||||
isBoolean,
|
||||
} from '@react-native-firebase/common';
|
||||
|
||||
import DatabaseQuery from './DatabaseQuery';
|
||||
import DatabaseQueryModifiers from './DatabaseQueryModifiers';
|
||||
import DatabaseOnDisconnect from './DatabaseOnDisconnect';
|
||||
import DatabaseDataSnapshot from './DatabaseDataSnapshot';
|
||||
|
||||
const internalRefs = ['.info/connected', '.info/serverTimeOffset'];
|
||||
|
||||
export default class DatabaseReference extends DatabaseQuery {
|
||||
constructor(database, path) {
|
||||
// Validate the reference path
|
||||
if (!internalRefs.includes(path) && !isValidPath(path)) {
|
||||
throw new Error(
|
||||
`firebase.database() Paths must be non-empty strings and can't contain ".", "#", "$", "[", or "]"`,
|
||||
);
|
||||
}
|
||||
|
||||
super(database, path, new DatabaseQueryModifiers());
|
||||
this._database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#parent
|
||||
*/
|
||||
get parent() {
|
||||
const parentPath = pathParent(this.path);
|
||||
if (parentPath === null) return null;
|
||||
return new DatabaseReference(this._database, parentPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#root
|
||||
*/
|
||||
get root() {
|
||||
return new DatabaseReference(this._database, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#child
|
||||
* @param path
|
||||
*/
|
||||
child(path) {
|
||||
if (!isString(path)) {
|
||||
throw new Error(`firebase.database().ref().child(*) 'path' must be a string value.`);
|
||||
}
|
||||
return new DatabaseReference(this._database, pathChild(this.path, path));
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#set
|
||||
* @param value
|
||||
* @param onComplete
|
||||
*/
|
||||
set(value, onComplete) {
|
||||
if (isUndefined(value)) {
|
||||
throw new Error(`firebase.database().ref().set(*) 'value' must be defined.`);
|
||||
}
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().set(_, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(this._database.native.set(this.path, { value }), onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#update
|
||||
* @param values
|
||||
* @param onComplete
|
||||
*/
|
||||
update(values, onComplete) {
|
||||
if (!isObject(values)) {
|
||||
throw new Error(`firebase.database().ref().update(*) 'values' must be an object.`);
|
||||
}
|
||||
|
||||
if (!Object.keys(values).length) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().update(*) 'values' must be an object containing multiple values.`,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO validate keys
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().update(_, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._database.native.update(this.path, { values }),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#setwithpriority
|
||||
* @param newVal
|
||||
* @param newPriority
|
||||
* @param onComplete
|
||||
*/
|
||||
setWithPriority(newVal, newPriority, onComplete) {
|
||||
if (isUndefined(newVal)) {
|
||||
throw new Error(`firebase.database().ref().setWithPriority(*) 'newVal' must be defined.`);
|
||||
}
|
||||
|
||||
if (!isNumber(newPriority) && !isString(newPriority) && !isNull(newPriority)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().setWithPriority(_, *) 'newPriority' must be a number, string or null value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().setWithPriority(_, _, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._database.native.setWithPriority(this.path, {
|
||||
value: newVal,
|
||||
priority: newPriority,
|
||||
}),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#remove
|
||||
* @param onComplete
|
||||
*/
|
||||
remove(onComplete) {
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().remove(*) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(this._database.native.remove(this.path), onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
|
||||
* @param transactionUpdate
|
||||
* @param onComplete
|
||||
* @param applyLocally
|
||||
*/
|
||||
transaction(transactionUpdate, onComplete, applyLocally) {
|
||||
if (!isFunction(transactionUpdate)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().transaction(*) 'transactionUpdate' must be a function.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().transaction(_, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(applyLocally) && !isBoolean(applyLocally)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().transaction(_, _, *) 'applyLocally' must be a boolean value if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const onCompleteWrapper = (error, committed, snapshotData) => {
|
||||
if (isFunction(onComplete)) {
|
||||
if (error) {
|
||||
onComplete(error, committed, null);
|
||||
} else {
|
||||
onComplete(null, committed, new DatabaseDataSnapshot(this, snapshotData));
|
||||
}
|
||||
}
|
||||
|
||||
if (error) return reject(error);
|
||||
return resolve({
|
||||
committed,
|
||||
snapshot: new DatabaseDataSnapshot(this, snapshotData),
|
||||
});
|
||||
};
|
||||
|
||||
// start the transaction natively
|
||||
this._database._transaction.add(this, transactionUpdate, onCompleteWrapper, applyLocally);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#setpriority
|
||||
* @param priority
|
||||
* @param onComplete
|
||||
*/
|
||||
setPriority(priority, onComplete) {
|
||||
if (!isNumber(priority) && !isString(priority) && !isNull(priority)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().setPriority(*) 'priority' must be a number, string or null value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isUndefined(onComplete) && !isFunction(onComplete)) {
|
||||
throw new Error(
|
||||
`firebase.database().ref().setPriority(_, *) 'onComplete' must be a function if provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
return promiseWithOptionalCallback(
|
||||
this._database.native.setPriority(this.path, { priority }),
|
||||
onComplete,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#push
|
||||
* @param value
|
||||
* @param onComplete
|
||||
* @returns {DatabaseReference}
|
||||
*/
|
||||
// push(value, onComplete) {
|
||||
// TODO validate value?
|
||||
//
|
||||
// const id = generateDatabaseId(this._database._serverTime);
|
||||
// const pushRef = this.child(id);
|
||||
// const thennablePushRef = this.child(id);
|
||||
//
|
||||
// return thennablePushRef;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#ondisconnect
|
||||
*/
|
||||
onDisconnect() {
|
||||
return new DatabaseOnDisconnect(this);
|
||||
}
|
||||
}
|
||||
24
packages/database/lib/DatabaseStatics.js
Normal file
24
packages/database/lib/DatabaseStatics.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
export default {
|
||||
ServerValue: {
|
||||
TIMESTAMP: {
|
||||
'.sv': 'timestamp',
|
||||
},
|
||||
},
|
||||
};
|
||||
309
packages/database/lib/DatabaseSyncTree.js
Normal file
309
packages/database/lib/DatabaseSyncTree.js
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
import NativeError from '@react-native-firebase/app/lib/internal/NativeFirebaseError';
|
||||
import { isString } from '@react-native-firebase/common';
|
||||
import SharedEventEmitter from '@react-native-firebase/app/lib/internal/SharedEventEmitter';
|
||||
import DatabaseDataSnapshot from './DatabaseDataSnapshot';
|
||||
|
||||
class DatabaseSyncTree {
|
||||
constructor() {
|
||||
this._tree = {};
|
||||
this._reverseLookup = {};
|
||||
|
||||
SharedEventEmitter.addListener('database_sync_event', this._handleSyncEvent.bind(this));
|
||||
}
|
||||
|
||||
get native() {
|
||||
return NativeModules.RNFBDatabaseQueryModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incoming event from native
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleSyncEvent(event) {
|
||||
const { body } = event;
|
||||
if (body.error) {
|
||||
this._handleErrorEvent(body);
|
||||
} else {
|
||||
this._handleValueEvent(body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes native database query listener cancellation events to their js counterparts.
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleErrorEvent(event) {
|
||||
// console.log('SyncTree.ERROR >>>', event);
|
||||
const { eventRegistrationKey, registrationCancellationKey } = event.registration;
|
||||
|
||||
const registration = this.getRegistration(registrationCancellationKey);
|
||||
|
||||
if (registration) {
|
||||
// build a new js error - we additionally attach
|
||||
// the ref as a property for easier debugging
|
||||
const error = NativeError.fromEvent(event.error, 'database');
|
||||
|
||||
// forward on to users .on(successCallback, cancellationCallback <-- listener
|
||||
SharedEventEmitter.emit(registrationCancellationKey, error);
|
||||
|
||||
// remove the paired event registration - if we received a cancellation
|
||||
// event then it's guaranteed that they'll be no further value events
|
||||
this.removeRegistration(eventRegistrationKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes native database 'on' events to their js equivalent counterpart.
|
||||
* If t is no longer any listeners remaining for this event we internally
|
||||
* call the native unsub method to prevent further events coming through.
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleValueEvent(event) {
|
||||
// console.log('SyncTree.VALUE >>>', event);
|
||||
const { key, eventRegistrationKey } = event.registration;
|
||||
const registration = this.getRegistration(eventRegistrationKey);
|
||||
// console.log('SyncTree.registration >>>', registration);
|
||||
|
||||
if (!registration) {
|
||||
// registration previously revoked
|
||||
// notify native that the registration
|
||||
// no longer exists so it can remove
|
||||
// the native listeners
|
||||
return this.native.off(key, eventRegistrationKey);
|
||||
}
|
||||
|
||||
let snapshot;
|
||||
let previousChildName;
|
||||
|
||||
// Value events don't return a previousChildName
|
||||
if (event.eventType === 'value') {
|
||||
snapshot = new DatabaseDataSnapshot(registration.ref, event.data);
|
||||
} else {
|
||||
snapshot = new DatabaseDataSnapshot(registration.ref, event.data.snapshot);
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
previousChildName = event.data.previousChildName;
|
||||
}
|
||||
|
||||
// forward on to users .on(successCallback <-- listener
|
||||
return SharedEventEmitter.emit(eventRegistrationKey, snapshot, previousChildName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns registration information such as appName, ref, path and registration keys.
|
||||
*
|
||||
* @param registration
|
||||
* @return {null}
|
||||
*/
|
||||
getRegistration(registration: string) {
|
||||
return this._reverseLookup[registration]
|
||||
? Object.assign({}, this._reverseLookup[registration])
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all listeners for the specified registration keys.
|
||||
*
|
||||
* @param registrations
|
||||
* @return {number}
|
||||
*/
|
||||
removeListenersForRegistrations(registrations) {
|
||||
if (isString(registrations)) {
|
||||
this.removeRegistration(registrations);
|
||||
SharedEventEmitter.removeAllListeners(registrations);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Array.isArray(registrations)) return 0;
|
||||
for (let i = 0, len = registrations.length; i < len; i++) {
|
||||
this.removeRegistration(registrations[i]);
|
||||
SharedEventEmitter.removeAllListeners(registrations[i]);
|
||||
}
|
||||
|
||||
return registrations.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific listener from the specified registrations.
|
||||
*
|
||||
* @param listener
|
||||
* @param registrations
|
||||
* @return {Array} array of registrations removed
|
||||
*/
|
||||
removeListenerRegistrations(listener, registrations) {
|
||||
if (!Array.isArray(registrations)) return [];
|
||||
const removed = [];
|
||||
|
||||
for (let i = 0, len = registrations.length; i < len; i++) {
|
||||
const registration = registrations[i];
|
||||
const subscriptions = SharedEventEmitter._subscriber.getSubscriptionsForType(registration);
|
||||
|
||||
if (subscriptions) {
|
||||
for (let j = 0, l = subscriptions.length; j < l; j++) {
|
||||
const subscription = subscriptions[j];
|
||||
// The subscription may have been removed during this event loop.
|
||||
// its listener matches the listener in method parameters
|
||||
if (subscription && subscription.listener === listener) {
|
||||
subscription.remove();
|
||||
removed.push(registration);
|
||||
this.removeRegistration(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all registration keys for the specified path.
|
||||
*
|
||||
* @param path
|
||||
* @return {Array}
|
||||
*/
|
||||
getRegistrationsByPath(path) {
|
||||
const out = [];
|
||||
const eventKeys = Object.keys(this._tree[path] || {});
|
||||
|
||||
for (let i = 0, len = eventKeys.length; i < len; i++) {
|
||||
Array.prototype.push.apply(out, Object.keys(this._tree[path][eventKeys[i]]));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all registration keys for the specified path and eventType.
|
||||
*
|
||||
* @param path
|
||||
* @param eventType
|
||||
* @return {Array}
|
||||
*/
|
||||
getRegistrationsByPathEvent(path, eventType) {
|
||||
if (!this._tree[path]) return [];
|
||||
if (!this._tree[path][eventType]) return [];
|
||||
|
||||
return Object.keys(this._tree[path][eventType]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single registration key for the specified path, eventType, and listener
|
||||
*
|
||||
* @param path
|
||||
* @param eventType
|
||||
* @param listener
|
||||
* @return {Array}
|
||||
*/
|
||||
getOneByPathEventListener(path, eventType, listener) {
|
||||
if (!this._tree[path]) return null;
|
||||
if (!this._tree[path][eventType]) return null;
|
||||
|
||||
const registrationsForPathEvent = Object.entries(this._tree[path][eventType]);
|
||||
|
||||
for (let i = 0; i < registrationsForPathEvent.length; i++) {
|
||||
const registration = registrationsForPathEvent[i];
|
||||
if (registration[1] === listener) return registration[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new listener.
|
||||
*
|
||||
* @param registration
|
||||
*/
|
||||
addRegistration(registration) {
|
||||
const { eventRegistrationKey, eventType, listener, once, path } = registration;
|
||||
|
||||
if (!this._tree[path]) this._tree[path] = {};
|
||||
if (!this._tree[path][eventType]) this._tree[path][eventType] = {};
|
||||
|
||||
this._tree[path][eventType][eventRegistrationKey] = listener;
|
||||
this._reverseLookup[eventRegistrationKey] = registration;
|
||||
|
||||
if (once) {
|
||||
SharedEventEmitter.once(
|
||||
eventRegistrationKey,
|
||||
this._onOnceRemoveRegistration(eventRegistrationKey, listener),
|
||||
);
|
||||
} else {
|
||||
SharedEventEmitter.addListener(eventRegistrationKey, listener);
|
||||
}
|
||||
|
||||
return eventRegistrationKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a registration, if it's not a `once` registration then instructs native
|
||||
* to also remove the underlying database query listener.
|
||||
*
|
||||
* @param registration
|
||||
* @return {boolean}
|
||||
*/
|
||||
removeRegistration(registration) {
|
||||
if (!this._reverseLookup[registration]) return false;
|
||||
const { path, eventType, once } = this._reverseLookup[registration];
|
||||
|
||||
if (!this._tree[path]) {
|
||||
delete this._reverseLookup[registration];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._tree[path][eventType]) {
|
||||
delete this._reverseLookup[registration];
|
||||
return false;
|
||||
}
|
||||
|
||||
// we don't want `once` events to notify native as they're already
|
||||
// automatically unsubscribed on native when the first event is sent
|
||||
const registrationObj = this._reverseLookup[registration];
|
||||
if (registrationObj && !once) {
|
||||
this.native.off(registrationObj.key, registration);
|
||||
}
|
||||
|
||||
delete this._tree[path][eventType][registration];
|
||||
delete this._reverseLookup[registration];
|
||||
|
||||
return !!registrationObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a `once` listener with a new function that self de-registers.
|
||||
*
|
||||
* @param registration
|
||||
* @param listener
|
||||
* @return {function(...[*])}
|
||||
* @private
|
||||
*/
|
||||
_onOnceRemoveRegistration(registration, listener) {
|
||||
return (...args) => {
|
||||
this.removeRegistration(registration);
|
||||
listener(...args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new DatabaseSyncTree();
|
||||
176
packages/database/lib/DatabaseTransaction.js
Normal file
176
packages/database/lib/DatabaseTransaction.js
Normal file
@@ -0,0 +1,176 @@
|
||||
/* eslint-disable no-console */
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import NativeError from '@react-native-firebase/app/lib/internal/NativeFirebaseError';
|
||||
|
||||
let transactionId = 0;
|
||||
|
||||
/**
|
||||
* Uses the push id generator to create a transaction id
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
|
||||
const generateTransactionId = () => transactionId++;
|
||||
|
||||
export default class DatabaseTransaction {
|
||||
constructor(database) {
|
||||
this._database = database;
|
||||
this._emitter = database.emitter;
|
||||
this._transactions = {};
|
||||
|
||||
this._emitter.addListener(
|
||||
this._database.eventNameForApp('database_transaction_event'),
|
||||
this._onTransactionEvent.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param reference
|
||||
* @param transactionUpdater
|
||||
* @param onComplete
|
||||
* @param applyLocally
|
||||
*/
|
||||
add(reference, transactionUpdater, onComplete, applyLocally = false) {
|
||||
const id = generateTransactionId();
|
||||
|
||||
this._transactions[id] = {
|
||||
id,
|
||||
reference,
|
||||
transactionUpdater,
|
||||
onComplete,
|
||||
applyLocally,
|
||||
completed: false,
|
||||
started: true,
|
||||
};
|
||||
|
||||
this._database.native.transactionStart(reference.path, id, applyLocally);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a transaction by ID
|
||||
*
|
||||
* @param id
|
||||
* @return {*}
|
||||
* @private
|
||||
*/
|
||||
_getTransaction(id) {
|
||||
return this._transactions[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a transaction by ID on the next event loop
|
||||
*
|
||||
* @param id
|
||||
* @private
|
||||
*/
|
||||
_removeTransaction(id) {
|
||||
setImmediate(() => {
|
||||
delete this._transactions[id];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onTransactionEvent(event) {
|
||||
switch (event.body.type) {
|
||||
case 'update':
|
||||
return this._handleUpdate(event);
|
||||
case 'error':
|
||||
return this._handleError(event);
|
||||
case 'complete':
|
||||
return this._handleComplete(event);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleUpdate(event) {
|
||||
let newValue;
|
||||
|
||||
const { id, body } = event;
|
||||
const { value } = body;
|
||||
|
||||
try {
|
||||
const transaction = this._getTransaction(id);
|
||||
if (!transaction) return;
|
||||
newValue = transaction.transactionUpdater(value);
|
||||
} finally {
|
||||
let abort = false;
|
||||
|
||||
if (newValue === undefined) {
|
||||
abort = true;
|
||||
}
|
||||
|
||||
this._database.native.transactionTryCommit(id, {
|
||||
value: newValue,
|
||||
abort,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleError(event) {
|
||||
const transaction = this._getTransaction(event.id);
|
||||
|
||||
if (transaction && !transaction.completed) {
|
||||
transaction.completed = true;
|
||||
|
||||
try {
|
||||
// error, committed, snapshot
|
||||
const error = NativeError.fromEvent(event.body.error, 'database');
|
||||
transaction.onComplete(error, false, null);
|
||||
} finally {
|
||||
this._removeTransaction(event.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleComplete(event) {
|
||||
const transaction = this._getTransaction(event.id);
|
||||
|
||||
if (transaction && !transaction.completed) {
|
||||
transaction.completed = true;
|
||||
|
||||
try {
|
||||
// error, committed, snapshot
|
||||
transaction.onComplete(null, event.body.committed, Object.assign({}, event.body.snapshot));
|
||||
} finally {
|
||||
this._removeTransaction(event.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1204
packages/database/lib/index.d.ts
vendored
Normal file
1204
packages/database/lib/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
199
packages/database/lib/index.js
Normal file
199
packages/database/lib/index.js
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
createModuleNamespace,
|
||||
FirebaseModule,
|
||||
getFirebaseRoot,
|
||||
} from '@react-native-firebase/app/lib/internal';
|
||||
import { isString, isBoolean, isNumber } from '@react-native-firebase/common';
|
||||
|
||||
import version from './version';
|
||||
import DatabaseStatics from './DatabaseStatics';
|
||||
import DatabaseReference from './DatabaseReference';
|
||||
import DatabaseTransaction from './DatabaseTransaction';
|
||||
|
||||
const namespace = 'database';
|
||||
|
||||
const nativeModuleName = [
|
||||
'RNFBDatabaseModule',
|
||||
'RNFBDatabaseReferenceModule',
|
||||
'RNFBDatabaseQueryModule',
|
||||
'RNFBDatabaseOnDisconnectModule',
|
||||
'RNFBDatabaseTransactionModule',
|
||||
];
|
||||
|
||||
class FirebaseDatabaseModule extends FirebaseModule {
|
||||
constructor(app, config, databaseUrl) {
|
||||
super(app, config, databaseUrl);
|
||||
this._serverTimeOffset = 0;
|
||||
this._customUrlOrRegion = databaseUrl || this.app.options.databaseURL;
|
||||
this._transaction = new DatabaseTransaction(this);
|
||||
setTimeout(() => {
|
||||
this._syncServerTimeOffset();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the server time offset in sync with the server time
|
||||
* @private
|
||||
*/
|
||||
_syncServerTimeOffset() {
|
||||
this.ref('.info/serverTimeOffset').on('value', snapshot => {
|
||||
this._serverTimeOffset = snapshot.val();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current server time, used to generate data such as database keys
|
||||
* @returns {Date}
|
||||
* @private
|
||||
*/
|
||||
get _serverTime() {
|
||||
return new Date(Date.now() + this._serverTimeOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Reference instance from a given path. Defaults to the root reference.
|
||||
* @param path
|
||||
* @returns {DatabaseReference}
|
||||
*/
|
||||
ref(path = '/') {
|
||||
if (!isString(path)) {
|
||||
throw new Error(`firebase.app().database().ref(*) 'path' must be a string value.`);
|
||||
}
|
||||
|
||||
return new DatabaseReference(this, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Reference from a database URL.
|
||||
* Note domain must be the same.
|
||||
* Any query parameters are stripped as per the web SDK.
|
||||
* @param url
|
||||
* @returns {DatabaseReference}
|
||||
*/
|
||||
refFromURL(url) {
|
||||
if (!isString(url) || !url.startsWith('https://')) {
|
||||
throw new Error(
|
||||
`firebase.app().database().refFromURL(*) 'url' must be a valid database URL.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!url.includes(this._customUrlOrRegion)) {
|
||||
throw new Error(
|
||||
`firebase.app().database().refFromURL(*) 'url' must be the same domain as the current instance (${
|
||||
this._customUrlOrRegion
|
||||
}). To use a different database domain, create a new Firebase instance.`,
|
||||
);
|
||||
}
|
||||
|
||||
let path = url.replace(this._customUrlOrRegion, '');
|
||||
if (path.includes('?')) path = path.slice(0, path.indexOf('?'));
|
||||
|
||||
return new DatabaseReference(this, path || '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* goOnline
|
||||
*/
|
||||
goOnline() {
|
||||
return this.native.goOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* goOffline
|
||||
*/
|
||||
goOffline() {
|
||||
return this.native.goOffline();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
setPersistenceEnabled(enabled) {
|
||||
if (!isBoolean(enabled)) {
|
||||
throw new Error(
|
||||
`firebase.app().database().setPersistenceEnabled(*) 'enabled' must be a boolean value.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.native.setPersistenceEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
setLoggingEnabled(enabled) {
|
||||
if (!isBoolean(enabled)) {
|
||||
throw new Error(
|
||||
`firebase.app().database().setLoggingEnabled(*) 'enabled' must be a boolean value.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.native.setLoggingEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bytes
|
||||
*/
|
||||
setPersistenceCacheSizeBytes(bytes) {
|
||||
if (!isNumber(bytes)) {
|
||||
throw new Error(
|
||||
`firebase.app().database().setPersistenceCacheSizeBytes(*) 'bytes' must be a number value.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (bytes < 1000000) {
|
||||
throw new Error(
|
||||
`firebase.app().database().setPersistenceCacheSizeBytes(*) 'bytes' must be greater than 1000000 (1MB).`,
|
||||
);
|
||||
}
|
||||
|
||||
if (bytes > 100000000) {
|
||||
throw new Error(
|
||||
`firebase.app().database().setPersistenceCacheSizeBytes(*) 'bytes' must be less than 100000000 (10MB).`,
|
||||
);
|
||||
}
|
||||
|
||||
this.native.setPersistenceCacheSizeBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
// import { SDK_VERSION } from '@react-native-firebase/database';
|
||||
export const SDK_VERSION = version;
|
||||
|
||||
// import database from '@react-native-firebase/database';
|
||||
// database().X(...);
|
||||
export default createModuleNamespace({
|
||||
statics: DatabaseStatics,
|
||||
version,
|
||||
namespace,
|
||||
nativeModuleName,
|
||||
nativeEvents: ['database_transaction_event', 'database_sync_event'],
|
||||
hasMultiAppSupport: true,
|
||||
hasCustomUrlOrRegionSupport: true,
|
||||
ModuleClass: FirebaseDatabaseModule,
|
||||
});
|
||||
|
||||
// import database, { firebase } from '@react-native-firebase/database';
|
||||
// database().X(...);
|
||||
// firebase.database().X(...);
|
||||
export const firebase = getFirebaseRoot();
|
||||
68
packages/database/lib/index.js.flow
Normal file
68
packages/database/lib/index.js.flow
Normal file
@@ -0,0 +1,68 @@
|
||||
/* eslint-disable import/no-duplicates */
|
||||
/*
|
||||
* Copyright (c) 2016-present Invertase Limited & Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this library except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import type { ReactNativeFirebaseModule } from '@react-native-firebase/app-types/index.js.flow';
|
||||
|
||||
export interface Statics {}
|
||||
|
||||
export interface Module extends ReactNativeFirebaseModule {}
|
||||
|
||||
declare module '@react-native-firebase/database' {
|
||||
import type {
|
||||
ReactNativeFirebaseNamespace,
|
||||
ReactNativeFirebaseModuleAndStatics,
|
||||
} from '@react-native-firebase/app-types/index.js.flow';
|
||||
/**
|
||||
* @example
|
||||
* ```js
|
||||
* import { firebase } from '@react-native-firebase/database';
|
||||
* firebase.database().X(...);
|
||||
* ```
|
||||
*/
|
||||
declare export var firebase: {} & ReactNativeFirebaseNamespace;
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```js
|
||||
* import database from '@react-native-firebase/database';
|
||||
* database().X(...);
|
||||
* ```
|
||||
*/
|
||||
declare export default ReactNativeFirebaseModuleAndStatics<Module, Statics>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach namespace to `firebase.` and `FirebaseApp.`.
|
||||
*/
|
||||
declare module '@react-native-firebase/app-types' {
|
||||
import type { ReactNativeFirebaseModuleAndStatics } from '@react-native-firebase/app-types/index.js.flow';
|
||||
|
||||
declare interface ReactNativeFirebaseNamespace {
|
||||
/**
|
||||
* Database
|
||||
*/
|
||||
database: ReactNativeFirebaseModuleAndStatics<Module, Statics>;
|
||||
}
|
||||
|
||||
declare interface FirebaseApp {
|
||||
/**
|
||||
* Database
|
||||
*/
|
||||
database(): Module;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user