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
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
import binaryToBase64 from 'react-native/Libraries/Utilities/binaryToBase64';
|
||||
import promiseDefer from './promiseDefer';
|
||||
import { promiseDefer } from './promise';
|
||||
|
||||
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
|
||||
27
packages/common/lib/deeps.js
Normal file
27
packages/common/lib/deeps.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { isObject } from './validate';
|
||||
|
||||
/**
|
||||
* Deep get a value from an object.
|
||||
* @website https://github.com/Salakar/deeps
|
||||
* @param object
|
||||
* @param path
|
||||
* @param joiner
|
||||
* @returns {*}
|
||||
*/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function deepGet(object, path, joiner = '/') {
|
||||
if (!isObject(object) && !Array.isArray(object)) return undefined;
|
||||
const keys = path.split(joiner);
|
||||
|
||||
let i = 0;
|
||||
let tmp = object;
|
||||
const len = keys.length;
|
||||
|
||||
while (i < len) {
|
||||
const key = keys[i++];
|
||||
if (!tmp || !Object.hasOwnProperty.call(tmp, key)) return undefined;
|
||||
tmp = tmp[key];
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
72
packages/common/lib/id.js
Normal file
72
packages/common/lib/id.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
const AUTO_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
// timestamp of last push, used to prevent local collisions if you push twice in one ms.
|
||||
let lastPushTime = 0;
|
||||
|
||||
// we generate 72-bits of randomness which get turned into 12 characters and appended to the
|
||||
// timestamp to prevent collisions with other clients. We store the last characters we
|
||||
// generated because in the event of a collision, we'll use those same characters except
|
||||
// "incremented" by one.
|
||||
const lastRandChars = [];
|
||||
|
||||
/**
|
||||
* Generate a firebase id - for use with ref().push(val, cb) - e.g. -KXMr7k2tXUFQqiaZRY4'
|
||||
* @param serverTimeOffset - pass in server time offset from native side
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generateDatabaseId(serverTimeOffset = 0): string {
|
||||
const timeStampChars = new Array(8);
|
||||
let now = new Date().getTime() + serverTimeOffset;
|
||||
const duplicateTime = now === lastPushTime;
|
||||
|
||||
lastPushTime = now;
|
||||
|
||||
for (let i = 7; i >= 0; i -= 1) {
|
||||
timeStampChars[i] = PUSH_CHARS.charAt(now % 64);
|
||||
now = Math.floor(now / 64);
|
||||
}
|
||||
|
||||
if (now !== 0) {
|
||||
throw new Error('We should have converted the entire timestamp.');
|
||||
}
|
||||
|
||||
let id = timeStampChars.join('');
|
||||
|
||||
if (!duplicateTime) {
|
||||
for (let i = 0; i < 12; i += 1) {
|
||||
lastRandChars[i] = Math.floor(Math.random() * 64);
|
||||
}
|
||||
} else {
|
||||
// if the timestamp hasn't changed since last push,
|
||||
// use the same random number, but increment it by 1.
|
||||
let i;
|
||||
for (i = 11; i >= 0 && lastRandChars[i] === 63; i -= 1) {
|
||||
lastRandChars[i] = 0;
|
||||
}
|
||||
|
||||
lastRandChars[i] += 1;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
id += PUSH_CHARS.charAt(lastRandChars[i]);
|
||||
}
|
||||
|
||||
if (id.length !== 20) throw new Error('Length should be 20.');
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a firestore auto id for use with collection/document .add()
|
||||
* @return {string}
|
||||
*/
|
||||
export function generateFirestoreId(): string {
|
||||
let autoId = '';
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
autoId += AUTO_ID_CHARS.charAt(Math.floor(Math.random() * AUTO_ID_CHARS.length));
|
||||
}
|
||||
return autoId;
|
||||
}
|
||||
@@ -18,10 +18,11 @@ import { Platform } from 'react-native';
|
||||
import { isString } from './validate';
|
||||
import Base64 from './Base64';
|
||||
|
||||
export * from './id';
|
||||
export * from './path';
|
||||
export * from './validate';
|
||||
export * from './promise';
|
||||
export Base64 from './Base64';
|
||||
export promiseDefer from './promiseDefer';
|
||||
export ReferenceBase from './ReferenceBase';
|
||||
|
||||
export function getDataUrlParts(dataUrlString) {
|
||||
@@ -74,3 +75,19 @@ export function stripTrailingSlash(string) {
|
||||
export const isIOS = Platform.OS === 'ios';
|
||||
|
||||
export const isAndroid = Platform.OS === 'android';
|
||||
|
||||
export function tryJSONParse(string) {
|
||||
try {
|
||||
return string && JSON.parse(string);
|
||||
} catch (jsonError) {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
export function tryJSONStringify(data: mixed): string | null {
|
||||
try {
|
||||
return JSON.stringify(data);
|
||||
} catch (jsonError) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,7 @@ export function pathParent(path) {
|
||||
* Joins a parent and a child path
|
||||
*/
|
||||
export function pathChild(path, childPath) {
|
||||
const canonicalChildPath = childPath
|
||||
.split('/')
|
||||
.filter($ => $.length > 0)
|
||||
.join('/');
|
||||
const canonicalChildPath = pathPieces(childPath).join('/');
|
||||
|
||||
if (path.length === 0) {
|
||||
return canonicalChildPath;
|
||||
@@ -58,3 +55,59 @@ export function pathLastComponent(path) {
|
||||
|
||||
return path.slice(index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all none empty pieces of the path
|
||||
* @param path
|
||||
* @returns {*}
|
||||
*/
|
||||
export function pathPieces(path) {
|
||||
return path.split('/').filter($ => $.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given path is empty
|
||||
* @param path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function pathIsEmpty(path) {
|
||||
return !pathPieces(path).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given path to a URL encoded string
|
||||
* @param path
|
||||
* @returns {string|string}
|
||||
*/
|
||||
export function pathToUrlEncodedString(path) {
|
||||
const pieces = pathPieces(path);
|
||||
let pathString = '';
|
||||
for (let i = 0; i < pieces.length; i++) {
|
||||
pathString += `/${encodeURIComponent(String(pieces[i]))}`;
|
||||
}
|
||||
return pathString || '/';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
export const INVALID_PATH_REGEX = /[[\].#$\u0000-\u001F\u007F]/;
|
||||
|
||||
/**
|
||||
* Ensures a given path is a valid Firebase path
|
||||
* @param path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isValidPath(path) {
|
||||
return typeof path === 'string' && path.length !== 0 && !INVALID_PATH_REGEX.test(path);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-control-regex,no-useless-escape
|
||||
export const INVALID_KEY_REGEX = /[\[\].#$\/\u0000-\u001F\u007F]/;
|
||||
|
||||
/**
|
||||
* Ensures a given key is a valid Firebase key
|
||||
* @param key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isValidKey(key) {
|
||||
return typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX.test(path);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
*
|
||||
*/
|
||||
|
||||
export default function promiseDefer() {
|
||||
import { isFunction } from './validate';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function promiseDefer() {
|
||||
const deferred = {
|
||||
resolve: null,
|
||||
reject: null,
|
||||
@@ -28,3 +33,26 @@ export default function promiseDefer() {
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param promise
|
||||
* @param callback
|
||||
*/
|
||||
export function promiseWithOptionalCallback(promise, callback) {
|
||||
if (!isFunction(callback)) return promise;
|
||||
|
||||
return promise
|
||||
.then(result => {
|
||||
if (callback && callback.length === 1) {
|
||||
callback(null);
|
||||
} else if (callback) {
|
||||
callback(null, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.catch(error => {
|
||||
if (callback) callback(error);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
41
packages/common/lib/serialize.js
Normal file
41
packages/common/lib/serialize.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { tryJSONParse, tryJSONStringify } from './index';
|
||||
import { isObject } from './validate';
|
||||
|
||||
export function serializeType(value) {
|
||||
if (isObject(value)) {
|
||||
return {
|
||||
type: 'object',
|
||||
value: serializeObject(value),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: typeof value,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function serializeObject(object) {
|
||||
if (!isObject(object)) return object;
|
||||
|
||||
// json stringify then parse it calls toString on Objects / Classes
|
||||
// that support it i.e new Date() becomes a ISO string.
|
||||
return tryJSONParse(tryJSONStringify(object));
|
||||
}
|
||||
Reference in New Issue
Block a user