2017-05-25 22:39:06 +00:00
import { Platform } from 'react-native' ;
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-05-25 22:39:06 +00:00
export function nativeSDKMissing ( sdkName ) {
2017-05-27 16:31:00 +00:00
console . warn ( ` Firebase ${ sdkName } native sdk has not been included in your ${ Platform . OS === 'ios' ? 'podfile' : 'build.gradle' } - ${ sdkName } methods have been disabled. ` ) ;
2017-05-25 22:39:06 +00:00
}
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-05-25 22:39:06 +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-05-25 22:39:06 +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 { * }
* /
2017-04-04 16:58:47 +00:00
export function tryJSONParse ( string : string | null ) : any {
2017-02-14 15:56:28 +00:00
try {
2017-04-04 16:58:47 +00:00
return string && JSON . parse ( string ) ;
2017-02-14 15:56:28 +00:00
} 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-05-25 22:39:06 +00:00
export function promisify ( fn : Function | string ,
NativeModule : Object ,
errorPrefix ? : string ) : ( args : 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-05-25 22:39:06 +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
* /
2017-05-25 22:39:06 +00:00
export function each ( array : Array < * > ,
chunkSize : number | Function ,
iterator : Function ,
cb ? : Function ) : void {
2017-02-14 15:56:28 +00:00
if ( typeof chunkSize === 'function' ) {
cb = iterator ;
iterator = chunkSize ;
chunkSize = DEFAULT _CHUNK _SIZE ;
}
2017-04-04 16:58:47 +00:00
if ( cb ) {
_delayChunk ( array , chunkSize , ( slice , start ) => {
for ( let ii = 0 , jj = slice . length ; ii < jj ; ii += 1 ) {
iterator ( slice [ ii ] , start + ii ) ;
}
} , cb ) ;
}
2017-02-14 15:56:28 +00:00
}
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 { * }
* /
2017-05-25 22:39:06 +00:00
export function map ( array : Array < * > ,
chunkSize : number | Function ,
iterator : Function ,
cb ? : Function ) : void {
2017-02-14 15:56:28 +00:00
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 ;
2017-04-04 16:58:47 +00:00
} , ( ) => cb && cb ( result ) ) ;
2017-02-14 15:56:28 +00:00
}
// 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 ;
}
2017-05-26 16:56:04 +00:00
2017-05-27 16:31:00 +00:00
/ * *
* Converts a code and message from a native event to a JS Error
* @ param code
* @ param message
* @ returns { Error }
* /
2017-05-26 16:56:04 +00:00
export function nativeToJSError ( code : string , message : string ) {
const error = new Error ( message ) ;
error . code = code ;
return error ;
}