2017-04-04 16:58:20 +00:00
|
|
|
/**
|
|
|
|
* @flow
|
|
|
|
*/
|
2017-02-14 15:56:28 +00:00
|
|
|
// modeled after base64 web-safe chars, but ordered by ASCII
|
|
|
|
const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
|
|
|
|
const hasOwnProperty = Object.hasOwnProperty;
|
|
|
|
const DEFAULT_CHUNK_SIZE = 50;
|
|
|
|
|
|
|
|
// internal promise handler
|
|
|
|
const _handler = (resolve, reject, errorPrefix, err, resp) => {
|
|
|
|
// resolve / reject after events etc
|
|
|
|
setImmediate(() => {
|
|
|
|
if (err) {
|
2017-04-04 16:58:20 +00:00
|
|
|
// $FlowFixMe
|
|
|
|
const firebaseError: FirebaseError = new Error(err.message);
|
2017-03-07 17:35:48 +00:00
|
|
|
|
|
|
|
if (isObject(err)) {
|
|
|
|
Object.keys(err).forEach(key => Object.defineProperty(firebaseError, key, { value: err[key] }));
|
|
|
|
if (errorPrefix) {
|
|
|
|
firebaseError.code = toWebSDKErrorCode(err.code || '', errorPrefix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reject(firebaseError);
|
2017-02-14 15:56:28 +00:00
|
|
|
}
|
|
|
|
return resolve(resp);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-04-04 16:58:20 +00:00
|
|
|
export function toWebSDKErrorCode(code: any, prefix: string): string {
|
2017-02-14 15:56:28 +00:00
|
|
|
if (!code || typeof code !== 'string') return '';
|
|
|
|
return code.toLowerCase().replace('error_', prefix).replace(/_/g, '-');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deep get a value from an object.
|
|
|
|
* @website https://github.com/Salakar/deeps
|
|
|
|
* @param object
|
|
|
|
* @param path
|
|
|
|
* @param joiner
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2017-04-04 16:58:20 +00:00
|
|
|
export function deepGet(
|
|
|
|
object: Object,
|
|
|
|
path: string,
|
|
|
|
joiner?: string = '/'
|
|
|
|
): any {
|
2017-02-14 15:56:28 +00:00
|
|
|
const keys = path.split(joiner);
|
|
|
|
|
|
|
|
let i = 0;
|
|
|
|
let tmp = object;
|
|
|
|
const len = keys.length;
|
|
|
|
|
|
|
|
while (i < len) {
|
2017-03-02 13:09:41 +00:00
|
|
|
const key = keys[i++];
|
2017-02-14 15:56:28 +00:00
|
|
|
if (!tmp || !hasOwnProperty.call(tmp, key)) return null;
|
|
|
|
tmp = tmp[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deep check if a key exists.
|
|
|
|
* @website https://github.com/Salakar/deeps
|
|
|
|
* @param object
|
|
|
|
* @param path
|
|
|
|
* @param joiner
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2017-04-04 16:58:20 +00:00
|
|
|
export function deepExists(
|
|
|
|
object: Object,
|
|
|
|
path: string,
|
|
|
|
joiner?: string = '/'
|
|
|
|
): boolean {
|
2017-02-14 15:56:28 +00:00
|
|
|
const keys = path.split(joiner);
|
|
|
|
|
|
|
|
let i = 0;
|
|
|
|
let tmp = object;
|
|
|
|
const len = keys.length;
|
|
|
|
|
|
|
|
while (i < len) {
|
2017-03-02 13:09:41 +00:00
|
|
|
const key = keys[i++];
|
2017-02-14 15:56:28 +00:00
|
|
|
if (!tmp || !hasOwnProperty.call(tmp, key)) return false;
|
|
|
|
tmp = tmp[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmp !== undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Simple is object check.
|
|
|
|
* @param item
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2017-04-04 16:58:20 +00:00
|
|
|
export function isObject(item: any): boolean {
|
2017-02-14 15:56:28 +00:00
|
|
|
return (item && typeof item === 'object' && !Array.isArray(item) && item !== null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Simple is function check
|
|
|
|
* @param item
|
|
|
|
* @returns {*|boolean}
|
|
|
|
*/
|
2017-04-04 16:58:20 +00:00
|
|
|
export function isFunction(item?: any): boolean {
|
|
|
|
return Boolean(item && typeof item === 'function');
|
2017-02-14 15:56:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param string
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
export function tryJSONParse(string) {
|
|
|
|
try {
|
|
|
|
return JSON.parse(string);
|
|
|
|
} catch (jsonError) {
|
|
|
|
return string;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param data
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2017-04-04 16:58:20 +00:00
|
|
|
export function tryJSONStringify(data: any): string | null {
|
2017-02-14 15:56:28 +00:00
|
|
|
try {
|
|
|
|
return JSON.stringify(data);
|
|
|
|
} catch (jsonError) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// noinspection Eslint
|
|
|
|
export const windowOrGlobal = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes an objects keys it's values
|
|
|
|
* @param object
|
|
|
|
* @returns {{}}
|
|
|
|
*/
|
|
|
|
export function reverseKeyValues(object: Object): Object {
|
|
|
|
const output = {};
|
|
|
|
for (const key in object) {
|
|
|
|
output[object[key]] = key;
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* No operation func
|
|
|
|
*/
|
|
|
|
export function noop(): void {
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wraps a native module method to support promises.
|
|
|
|
* @param fn
|
|
|
|
* @param NativeModule
|
|
|
|
* @param errorPrefix
|
|
|
|
*/
|
2017-04-04 16:58:20 +00:00
|
|
|
export function promisify(
|
|
|
|
fn: Function | string,
|
|
|
|
NativeModule: Object,
|
|
|
|
errorPrefix?: string
|
|
|
|
): (any) => Promise<> {
|
2017-02-14 15:56:28 +00:00
|
|
|
return (...args) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const _fn = typeof fn === 'function' ? fn : NativeModule[fn];
|
|
|
|
if (!_fn || typeof _fn !== 'function') return reject(new Error('Missing function for promisify.'));
|
|
|
|
return _fn.apply(NativeModule, [...args, _handler.bind(_handler, resolve, reject, errorPrefix)]);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delays chunks based on sizes per event loop.
|
|
|
|
* @param collection
|
|
|
|
* @param chunkSize
|
|
|
|
* @param operation
|
|
|
|
* @param callback
|
|
|
|
* @private
|
|
|
|
*/
|
2017-04-04 16:58:20 +00:00
|
|
|
function _delayChunk(
|
|
|
|
collection: Array<*>,
|
|
|
|
chunkSize: number,
|
|
|
|
operation: Function,
|
|
|
|
callback: Function
|
|
|
|
): void {
|
2017-02-14 15:56:28 +00:00
|
|
|
const length = collection.length;
|
|
|
|
const iterations = Math.ceil(length / chunkSize);
|
|
|
|
|
|
|
|
// noinspection ES6ConvertVarToLetConst
|
|
|
|
let thisIteration = 0;
|
|
|
|
|
|
|
|
setImmediate(function next() {
|
|
|
|
const start = thisIteration * chunkSize;
|
|
|
|
const _end = start + chunkSize;
|
|
|
|
const end = _end >= length ? length : _end;
|
|
|
|
const result = operation(collection.slice(start, end), start, end);
|
|
|
|
|
|
|
|
if (thisIteration++ > iterations) {
|
|
|
|
callback(null, result);
|
|
|
|
} else {
|
|
|
|
setImmediate(next);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Async each with optional chunk size limit
|
|
|
|
* @param array
|
|
|
|
* @param chunkSize
|
|
|
|
* @param iterator
|
|
|
|
* @param cb
|
|
|
|
*/
|
|
|
|
export function each(array: Array, chunkSize?: number, iterator: Function, cb: Function): void {
|
|
|
|
if (typeof chunkSize === 'function') {
|
|
|
|
cb = iterator;
|
|
|
|
iterator = chunkSize;
|
|
|
|
chunkSize = DEFAULT_CHUNK_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
_delayChunk(array, chunkSize, (slice, start) => {
|
|
|
|
for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
|
|
|
|
iterator(slice[ii], start + ii);
|
|
|
|
}
|
|
|
|
}, cb);
|
|
|
|
}
|
|
|
|
|
2017-04-04 16:58:20 +00:00
|
|
|
export function typeOf(value: any): string {
|
2017-03-02 13:09:41 +00:00
|
|
|
if (value === null) return 'null';
|
2017-02-14 15:56:28 +00:00
|
|
|
if (Array.isArray(value)) return 'array';
|
2017-03-02 13:09:41 +00:00
|
|
|
return typeof value;
|
2017-02-14 15:56:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Async map with optional chunk size limit
|
|
|
|
* @param array
|
|
|
|
* @param chunkSize
|
|
|
|
* @param iterator
|
|
|
|
* @param cb
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
export function map(array: Array, chunkSize?: number, iterator: Function, cb: Function): void {
|
|
|
|
if (typeof chunkSize === 'function') {
|
|
|
|
cb = iterator;
|
|
|
|
iterator = chunkSize;
|
|
|
|
chunkSize = DEFAULT_CHUNK_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = [];
|
|
|
|
_delayChunk(array, chunkSize, (slice, start) => {
|
|
|
|
for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
|
|
|
|
result.push(iterator(slice[ii], start + ii, array));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}, () => cb(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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 generatePushID(serverTimeOffset?: number = 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;
|
|
|
|
}
|