From dded21ff7c56cc6d3ca445bdbff9c273a7c9df6e Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 23 Mar 2018 13:40:07 +0000 Subject: [PATCH 01/40] start of new testing infra --- tests-new/.buckconfig | 6 + tests-new/.flowconfig | 97 ++ tests-new/.gitignore | 40 + tests-new/.watchmanconfig | 1 + tests-new/README.md | 67 + tests-new/android/app/BUCK | 66 + tests-new/android/app/build.gradle | 86 ++ tests-new/android/app/keystore.jks | Bin 0 -> 2057 bytes tests-new/android/app/proguard-rules.pro | 63 + .../java/com/example/DetoxTest.java | 24 + .../android/app/src/main/AndroidManifest.xml | 31 + .../main/java/com/example/MainActivity.java | 15 + .../java/com/example/MainApplication.java | 40 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 8 + tests-new/android/build.gradle | 20 + tests-new/android/gradle.properties | 21 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52266 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + tests-new/android/gradlew | 164 +++ tests-new/android/gradlew.bat | 90 ++ tests-new/android/keystores/BUCK | 8 + .../keystores/debug.keystore.properties | 4 + tests-new/android/settings.gradle | 6 + tests-new/app.js | 55 + tests-new/e2e/example.spec.js | 19 + tests-new/e2e/init.js | 10 + tests-new/e2e/mocha.opts | 3 + tests-new/e2eExplicitRequire/example.spec.js | 26 + tests-new/e2eExplicitRequire/init.js | 14 + tests-new/e2eExplicitRequire/mocha.opts | 3 + tests-new/index.android.js | 1 + tests-new/index.js | 1 + .../ios/example.xcodeproj/project.pbxproj | 1077 +++++++++++++++++ .../xcschemes/example Release.xcscheme | 129 ++ .../xcshareddata/xcschemes/example.xcscheme | 129 ++ tests-new/ios/example/AppDelegate.h | 16 + tests-new/ios/example/AppDelegate.m | 37 + .../ios/example/Base.lproj/LaunchScreen.xib | 42 + .../AppIcon.appiconset/Contents.json | 48 + tests-new/ios/example/Info.plist | 47 + tests-new/ios/example/main.m | 18 + tests-new/ios/exampleTests/Info.plist | 24 + tests-new/ios/exampleTests/exampleTests.m | 70 ++ tests-new/package.json | 47 + tests-new/rn-cli.config.js | 12 + 50 files changed, 2694 insertions(+) create mode 100755 tests-new/.buckconfig create mode 100755 tests-new/.flowconfig create mode 100755 tests-new/.gitignore create mode 100755 tests-new/.watchmanconfig create mode 100755 tests-new/README.md create mode 100755 tests-new/android/app/BUCK create mode 100755 tests-new/android/app/build.gradle create mode 100755 tests-new/android/app/keystore.jks create mode 100755 tests-new/android/app/proguard-rules.pro create mode 100755 tests-new/android/app/src/androidTest/java/com/example/DetoxTest.java create mode 100755 tests-new/android/app/src/main/AndroidManifest.xml create mode 100755 tests-new/android/app/src/main/java/com/example/MainActivity.java create mode 100755 tests-new/android/app/src/main/java/com/example/MainApplication.java create mode 100755 tests-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/values/strings.xml create mode 100755 tests-new/android/app/src/main/res/values/styles.xml create mode 100755 tests-new/android/build.gradle create mode 100755 tests-new/android/gradle.properties create mode 100755 tests-new/android/gradle/wrapper/gradle-wrapper.jar create mode 100755 tests-new/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 tests-new/android/gradlew create mode 100755 tests-new/android/gradlew.bat create mode 100755 tests-new/android/keystores/BUCK create mode 100755 tests-new/android/keystores/debug.keystore.properties create mode 100755 tests-new/android/settings.gradle create mode 100755 tests-new/app.js create mode 100755 tests-new/e2e/example.spec.js create mode 100755 tests-new/e2e/init.js create mode 100755 tests-new/e2e/mocha.opts create mode 100755 tests-new/e2eExplicitRequire/example.spec.js create mode 100755 tests-new/e2eExplicitRequire/init.js create mode 100755 tests-new/e2eExplicitRequire/mocha.opts create mode 100755 tests-new/index.android.js create mode 100755 tests-new/index.js create mode 100755 tests-new/ios/example.xcodeproj/project.pbxproj create mode 100755 tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme create mode 100755 tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme create mode 100755 tests-new/ios/example/AppDelegate.h create mode 100755 tests-new/ios/example/AppDelegate.m create mode 100755 tests-new/ios/example/Base.lproj/LaunchScreen.xib create mode 100755 tests-new/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100755 tests-new/ios/example/Info.plist create mode 100755 tests-new/ios/example/main.m create mode 100755 tests-new/ios/exampleTests/Info.plist create mode 100755 tests-new/ios/exampleTests/exampleTests.m create mode 100755 tests-new/package.json create mode 100755 tests-new/rn-cli.config.js diff --git a/tests-new/.buckconfig b/tests-new/.buckconfig new file mode 100755 index 00000000..934256cb --- /dev/null +++ b/tests-new/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/tests-new/.flowconfig b/tests-new/.flowconfig new file mode 100755 index 00000000..45fd3ccb --- /dev/null +++ b/tests-new/.flowconfig @@ -0,0 +1,97 @@ +[ignore] + +# We fork some components by platform. +.*/*.web.js +.*/*.android.js + +# Some modules have their own node_modules with overlap +.*/node_modules/node-haste/.* + +# Ugh +.*/node_modules/babel.* +.*/node_modules/babylon.* +.*/node_modules/invariant.* + +# Ignore react and fbjs where there are overlaps, but don't ignore +# anything that react-native relies on +.*/node_modules/fbjs/lib/Map.js +.*/node_modules/fbjs/lib/ErrorUtils.js + +# Flow has a built-in definition for the 'react' module which we prefer to use +# over the currently-untyped source +.*/node_modules/react/react.js +.*/node_modules/react/lib/React.js +.*/node_modules/react/lib/ReactDOM.js + +.*/__mocks__/.* +.*/__tests__/.* + +.*/commoner/test/source/widget/share.js + +# Ignore commoner tests +.*/node_modules/commoner/test/.* + +# See https://github.com/facebook/flow/issues/442 +.*/react-tools/node_modules/commoner/lib/reader.js + +# Ignore jest +.*/node_modules/jest-cli/.* + +# Ignore Website +.*/website/.* + +# Ignore generators +.*/local-cli/generator.* + +# Ignore BUCK generated folders +.*\.buckd/ + +# Ignore RNPM +.*/local-cli/rnpm/.* + +.*/node_modules/is-my-json-valid/test/.*\.json +.*/node_modules/iconv-lite/encodings/tables/.*\.json +.*/node_modules/y18n/test/.*\.json +.*/node_modules/spdx-license-ids/spdx-license-ids.json +.*/node_modules/spdx-exceptions/index.json +.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json +.*/node_modules/resolve/lib/core.json +.*/node_modules/jsonparse/samplejson/.*\.json +.*/node_modules/json5/test/.*\.json +.*/node_modules/ua-parser-js/test/.*\.json +.*/node_modules/builtin-modules/builtin-modules.json +.*/node_modules/binary-extensions/binary-extensions.json +.*/node_modules/url-regex/tlds.json +.*/node_modules/joi/.*\.json +.*/node_modules/isemail/.*\.json +.*/node_modules/tr46/.*\.json + + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow +flow/ + +[options] +module.system=haste + +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + +munge_underscores=true + +module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + +[version] +^0.25.0 diff --git a/tests-new/.gitignore b/tests-new/.gitignore new file mode 100755 index 00000000..42c9490e --- /dev/null +++ b/tests-new/.gitignore @@ -0,0 +1,40 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.idea +.gradle +local.properties + +# node.js +# +node_modules/ +npm-debug.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore diff --git a/tests-new/.watchmanconfig b/tests-new/.watchmanconfig new file mode 100755 index 00000000..9e26dfee --- /dev/null +++ b/tests-new/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests-new/README.md b/tests-new/README.md new file mode 100755 index 00000000..1b84cd90 --- /dev/null +++ b/tests-new/README.md @@ -0,0 +1,67 @@ +> detox + +# React Native Demo Project + +## Requirements + +* Make sure you have Xcode installed (tested with Xcode 8.1-8.2). +* make sure you have node installed (`brew install node`, node 7.6.0 and up is required for native async-await support, otherwise you'll have to babel the tests). +* Make sure you have react-native dependencies installed: + * react-native-cli is installed (`npm install -g react-native-cli`) + * watchman is installed (`brew install watchman`) + +### Step 1: Npm install + +* Make sure you're in folder `examples/demo-react-native`. +* Run `npm install`. + +## To test Release build of your app + +### Step 2: Build + +* Build the demo project + + +```sh +detox build --configuration ios.sim.release +``` + +### Step 3: Test + +* Run tests on the demo project + + +```sh +detox test --configuration ios.sim.release +``` + +This action will open a new simulator and run the tests on it. + +## To test Debug build of your app + +### Step 2: Build + +* Build the demo project + + +```sh +detox build --configuration ios.sim.debug +``` + +### Step 3: Test + +* start react-native packager + + +```sh +npm run start +``` + +* Run tests on the demo project + + +```sh +detox test --configuration ios.sim.debug +``` + +This action will open a new simulator and run the tests on it. diff --git a/tests-new/android/app/BUCK b/tests-new/android/app/BUCK new file mode 100755 index 00000000..d73aebd8 --- /dev/null +++ b/tests-new/android/app/BUCK @@ -0,0 +1,66 @@ +import re + +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = 'all-libs', + exported_deps = lib_deps +) + +android_library( + name = 'app-code', + srcs = glob([ + 'src/main/java/**/*.java', + ]), + deps = [ + ':all-libs', + ':build_config', + ':res', + ], +) + +android_build_config( + name = 'build_config', + package = 'com.example', +) + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.example', +) + +android_binary( + name = 'app', + package_type = 'debug', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//android/keystores:debug', + deps = [ + ':app-code', + ], +) diff --git a/tests-new/android/app/build.gradle b/tests-new/android/app/build.gradle new file mode 100755 index 00000000..98beaca9 --- /dev/null +++ b/tests-new/android/app/build.gradle @@ -0,0 +1,86 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +apply from: "../../node_modules/react-native/react.gradle" + +android { + compileSdkVersion 27 + buildToolsVersion '27.0.2' + + defaultConfig { + applicationId "com.detox.rn.example" + minSdkVersion 18 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" + } + + testBuildType System.getProperty('testBuildType', 'debug') + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + missingDimensionStrategy "minReactNative", "minReactNative46" + } + splits { + abi { + reset() + enable false + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86" + } + } + signingConfigs { + release { + storeFile file("keystore.jks") + storePassword "12345678" + keyAlias "key0" + keyPassword "12345678" + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + signingConfig signingConfigs.release + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a": 1, "x86": 2] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } + + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/NOTICE' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE.txt' + } +} + +dependencies { + implementation "com.android.support:appcompat-v7:27.0.2" + implementation "com.facebook.react:react-native:+" // From node_modules + + androidTestImplementation(project(path: ":detox")) + androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test:rules:1.0.1' +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/tests-new/android/app/keystore.jks b/tests-new/android/app/keystore.jks new file mode 100755 index 0000000000000000000000000000000000000000..6be9060611fbc37fa30d2cfef167ddb6903661e6 GIT binary patch literal 2057 zcmV+k2=@2>?f&fm0006200031000311Z!n^FaQ7nU8Gr$(f|Mj0Wg9D{V)y&3M&Qy z1OX}n5di@O00e>r>4SM=UxkU`nR%&<7;yHJ4kyrJ`$Mu1edvQP3VO=Y6BxK}T1H@@ z5mkj87Yh+t(57*(LQmLEK?O%iqHO%{_t;nBr5U zw}bpw_ti`>$%FWBuUGJE5ynEw%X41rg6FCDA6DjCND?OQ1 zCMt!Exl_Gc{HE}2Ux|(O(H0lg7~aU~Js#zKmmh-)7Pfoln+xBQ&}m{hh}+^DaBFA@qdhjLvTJxYBK0sxi4zr7IR& z_lTstLnARJyjrQa4C}MqpHr}qp&{bPbTHKON%rx7lSKM7GE2Md7@n@VN@v>C)nj)dMQ^Wtj05`IQ9;m`QOa91;MdIfQAB)A1W!od*5 zpYzi7<*RoE2{h7qM7rShBDd^|GE`R!Ny+kTf65Pcwv%9)cH3vG~Xas`3Dcp}oFdOQK+654A zfn!_L0-q-3W0yg!wki~g7?vB*nc@NXw-w%k16kkn+lCIkhM5nWe=jGBlNuP>Q5qKt ziRx@@*16t0){4G)dzUPcXoR1omF(yRhqhX9YBg(ddUp=erUkQ|>;Rs6qYab|n0xa< z{gn1^zW=u4?GW;5%AUdjSvP*prwVLCE@H8C3OoDlRf0 zxCqZy$E5zD09Lp;kK70pNYI%RCa+#;6cIR8$_Z2-8Cy_!n7O>l4_o_EDKJ$!Q+sVi zbgSgd$YOwKlTq)h*$}Yi^)B@Wg7uGWG(!Jd{WV0DYNML2VKF#M7I5okgUxZ_%OW|( zAE%3(PW+$B_sDl3#6YGqKpqbU!Q&0)dyp$0Nqx3FwwI2Jqwu}63&q)yNF>M8Fbw7r zS@5*H@rbR&)eSOV0Vx1uF^Dj{+F#^oQ(>ut?!^{$@eRG@0A{PAE~i}tUhUE3K!VPT zP2TMEb;x#xk@f4YOpx=x?vQ)CV0Srb*Sh3AsD0}8MUjM}mCp`lGb`d{ z56Pg%j$}Zq6a<4L*^Y2o2DQd{_&2>pyd`7wqG>Dq3aGwI<{eMGVP;Q%W@8}#T_Wy} zwE8xBzvh?xC`&oEVv*q#5C)E>JXHVy00966SS~d%IRF3x!!UvZzc7LUr=SA@0RjR9 za=$yzFbxI?Duzgg_YDC73k3i$5HSug3Ytf&n5h4F(A+hDe6@4FLfG z1potr0S^E$f&mHwf&l>lm+613uazt4UMqek9n-AFtN05Lx1#XHulXhJDd0v{bdgu6 z!Pns)zCjVih=QvbI3KLEY|0q#hc3LgXWx-9bBkp1)Crq16tdqi;ip0C*7s>|PG9p^l3(i_fA21yT163Uk1QrAoT=Ki2FC#-m*A!ovR{B3dGUy9) zFbxI?Duzgg_YDC73k3iJf&l>lkNLBEsyIIsg*Y&sD?V$* zF`u0@%K%B%MX7nt!HFef04g5~Z7WhmrsWJ6>0}KQPM4nXCo06iHl4>*pF_wMSWq_} zMQl}+)fks1QRx2_ANU%Cb6kwaJnFC1X@Kx>kkC)K;2bK=L*%Wijy8>x^FQ+h3H6B3 n@Kkdhu~3^J$$*!+GIf1>A9b=S&WyI9iDjwko@kf;T%}hsq(igk literal 0 HcmV?d00001 diff --git a/tests-new/android/app/proguard-rules.pro b/tests-new/android/app/proguard-rules.pro new file mode 100755 index 00000000..9852871b --- /dev/null +++ b/tests-new/android/app/proguard-rules.pro @@ -0,0 +1,63 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Disabling obfuscation is useful if you collect stack traces from production crashes +# (unless you are using a system that supports de-obfuscate the stack traces). +-dontobfuscate + +# React Native + +# Keep our interfaces so they can be used by other ProGuard rules. +# See http://sourceforge.net/p/proguard/bugs/466/ +-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip +-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters + +# Do not strip any method/class that is annotated with @DoNotStrip +-keep @com.facebook.proguard.annotations.DoNotStrip class * +-keepclassmembers class * { + @com.facebook.proguard.annotations.DoNotStrip *; +} + +-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { + void set*(***); + *** get*(); +} + +-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } +-keep class * extends com.facebook.react.bridge.NativeModule { *; } +-keepclassmembers,includedescriptorclasses class * { native ; } +-keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } + +-dontwarn com.facebook.react.** + +# okhttp + +-keepattributes Signature +-keepattributes *Annotation* +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +# okio + +-keep class sun.misc.Unsafe { *; } +-dontwarn java.nio.file.* +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn okio.** diff --git a/tests-new/android/app/src/androidTest/java/com/example/DetoxTest.java b/tests-new/android/app/src/androidTest/java/com/example/DetoxTest.java new file mode 100755 index 00000000..e63fdfd4 --- /dev/null +++ b/tests-new/android/app/src/androidTest/java/com/example/DetoxTest.java @@ -0,0 +1,24 @@ +package com.example; + +import android.support.test.filters.LargeTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import com.wix.detox.Detox; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class DetoxTest { + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); + + @Test + public void runDetoxTests() throws InterruptedException { + Detox.runTests(mActivityRule); + } +} diff --git a/tests-new/android/app/src/main/AndroidManifest.xml b/tests-new/android/app/src/main/AndroidManifest.xml new file mode 100755 index 00000000..677f0759 --- /dev/null +++ b/tests-new/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests-new/android/app/src/main/java/com/example/MainActivity.java b/tests-new/android/app/src/main/java/com/example/MainActivity.java new file mode 100755 index 00000000..e84b7255 --- /dev/null +++ b/tests-new/android/app/src/main/java/com/example/MainActivity.java @@ -0,0 +1,15 @@ +package com.example; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "example"; + } +} diff --git a/tests-new/android/app/src/main/java/com/example/MainApplication.java b/tests-new/android/app/src/main/java/com/example/MainApplication.java new file mode 100755 index 00000000..b404a437 --- /dev/null +++ b/tests-new/android/app/src/main/java/com/example/MainApplication.java @@ -0,0 +1,40 @@ +package com.example; + +import android.app.Application; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage() + ); + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } + +} diff --git a/tests-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tests-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..cde69bcccec65160d92116f20ffce4fce0b5245c GIT binary patch literal 3418 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 literal 0 HcmV?d00001 diff --git a/tests-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..bfa42f0e7b91d006d22352c9ff2f134e504e3c1d GIT binary patch literal 4842 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 literal 0 HcmV?d00001 diff --git a/tests-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..324e72cdd7480cb983fa1bcc7ce686e51ef87fe7 GIT binary patch literal 7718 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 literal 0 HcmV?d00001 diff --git a/tests-new/android/app/src/main/res/values/strings.xml b/tests-new/android/app/src/main/res/values/strings.xml new file mode 100755 index 00000000..d75426c8 --- /dev/null +++ b/tests-new/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + example + diff --git a/tests-new/android/app/src/main/res/values/styles.xml b/tests-new/android/app/src/main/res/values/styles.xml new file mode 100755 index 00000000..319eb0ca --- /dev/null +++ b/tests-new/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/tests-new/android/build.gradle b/tests-new/android/build.gradle new file mode 100755 index 00000000..8f3a5f42 --- /dev/null +++ b/tests-new/android/build.gradle @@ -0,0 +1,20 @@ +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + jcenter() + google() + maven { + url "$projectDir/../../node_modules/react-native/android" + } + } +} diff --git a/tests-new/android/gradle.properties b/tests-new/android/gradle.properties new file mode 100755 index 00000000..b3052b36 --- /dev/null +++ b/tests-new/android/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.useDeprecatedNdk=true +android.enableAapt2=false diff --git a/tests-new/android/gradle/wrapper/gradle-wrapper.jar b/tests-new/android/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000000000000000000000000000000000000..b5166dad4d90021f6a0b45268c0755719f1d5cd4 GIT binary patch literal 52266 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 z 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 literal 0 HcmV?d00001 diff --git a/tests-new/android/gradle/wrapper/gradle-wrapper.properties b/tests-new/android/gradle/wrapper/gradle-wrapper.properties new file mode 100755 index 00000000..b8dc2d4a --- /dev/null +++ b/tests-new/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Sep 09 20:32:40 IDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/tests-new/android/gradlew b/tests-new/android/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/tests-new/android/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/tests-new/android/gradlew.bat b/tests-new/android/gradlew.bat new file mode 100755 index 00000000..8a0b282a --- /dev/null +++ b/tests-new/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tests-new/android/keystores/BUCK b/tests-new/android/keystores/BUCK new file mode 100755 index 00000000..15da20e6 --- /dev/null +++ b/tests-new/android/keystores/BUCK @@ -0,0 +1,8 @@ +keystore( + name = 'debug', + store = 'debug.keystore', + properties = 'debug.keystore.properties', + visibility = [ + 'PUBLIC', + ], +) diff --git a/tests-new/android/keystores/debug.keystore.properties b/tests-new/android/keystores/debug.keystore.properties new file mode 100755 index 00000000..121bfb49 --- /dev/null +++ b/tests-new/android/keystores/debug.keystore.properties @@ -0,0 +1,4 @@ +key.store=debug.keystore +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/tests-new/android/settings.gradle b/tests-new/android/settings.gradle new file mode 100755 index 00000000..64bd8d53 --- /dev/null +++ b/tests-new/android/settings.gradle @@ -0,0 +1,6 @@ +rootProject.name = 'DetoxRNExample' + +include ':app' + +include ':detox' +project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') \ No newline at end of file diff --git a/tests-new/app.js b/tests-new/app.js new file mode 100755 index 00000000..471bc82d --- /dev/null +++ b/tests-new/app.js @@ -0,0 +1,55 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * @flow + */ + +import React, { Component } from 'react'; +import { + AppRegistry, + StyleSheet, + Text, + View, + TouchableOpacity +} from 'react-native'; + +class example extends Component { + constructor(props) { + super(props); + this.state = { + greeting: undefined + }; + } + render() { + if (this.state.greeting) return this.renderAfterButton(); + return ( + + + Welcome + + + Say Hello + + + Say World + + + ); + } + renderAfterButton() { + return ( + + + {this.state.greeting}!!! + + + ); + } + onButtonPress(greeting) { + this.setState({ + greeting: greeting + }); + } +} + +AppRegistry.registerComponent('example', () => example); diff --git a/tests-new/e2e/example.spec.js b/tests-new/e2e/example.spec.js new file mode 100755 index 00000000..caa08866 --- /dev/null +++ b/tests-new/e2e/example.spec.js @@ -0,0 +1,19 @@ +describe('Example', () => { + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('should have welcome screen', async () => { + await expect(element(by.id('welcome'))).toBeVisible(); + }); + + it('should show hello screen after tap', async () => { + await element(by.id('hello_button')).tap(); + await expect(element(by.text('Hello!!!'))).toBeVisible(); + }); + + it('should show world screen after tap', async () => { + await element(by.id('world_button')).tap(); + await expect(element(by.text('World!!!'))).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/tests-new/e2e/init.js b/tests-new/e2e/init.js new file mode 100755 index 00000000..5326ee52 --- /dev/null +++ b/tests-new/e2e/init.js @@ -0,0 +1,10 @@ +const detox = require('detox'); +const config = require('../package.json').detox; + +before(async () => { + await detox.init(config); +}); + +after(async () => { + await detox.cleanup(); +}); diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts new file mode 100755 index 00000000..99b1114b --- /dev/null +++ b/tests-new/e2e/mocha.opts @@ -0,0 +1,3 @@ +--recursive +--timeout 120000 +--bail \ No newline at end of file diff --git a/tests-new/e2eExplicitRequire/example.spec.js b/tests-new/e2eExplicitRequire/example.spec.js new file mode 100755 index 00000000..c822777f --- /dev/null +++ b/tests-new/e2eExplicitRequire/example.spec.js @@ -0,0 +1,26 @@ +const {device, expect, element, by, waitFor} = require('detox'); + +describe('Example', () => { + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('should have welcome screen', async () => { + await expect(element(by.id('welcome'))).toBeVisible(); + }); + + it('should show hello screen after tap', async () => { + await element(by.id('hello_button')).tap(); + await expect(element(by.text('Hello!!!'))).toBeVisible(); + }); + + it('should show world screen after tap', async () => { + await element(by.id('world_button')).tap(); + await expect(element(by.text('World!!!'))).toBeVisible(); + }); + + it('waitFor should be exported', async () => { + await waitFor(element(by.id('welcome'))).toExist().withTimeout(2000); + await expect(element(by.id('welcome'))).toExist(); + }); +}); diff --git a/tests-new/e2eExplicitRequire/init.js b/tests-new/e2eExplicitRequire/init.js new file mode 100755 index 00000000..9ff09dc1 --- /dev/null +++ b/tests-new/e2eExplicitRequire/init.js @@ -0,0 +1,14 @@ +const detox = require('detox'); +const config = require('../package.json').detox; + +/* +Example showing how to use Detox with required objects rather than globally exported. +e.g `const {device, expect, element, by, waitFor} = require('detox');` + */ +before(async () => { + await detox.init(config, {initGlobals: false}); +}); + +after(async () => { + await detox.cleanup(); +}); diff --git a/tests-new/e2eExplicitRequire/mocha.opts b/tests-new/e2eExplicitRequire/mocha.opts new file mode 100755 index 00000000..99b1114b --- /dev/null +++ b/tests-new/e2eExplicitRequire/mocha.opts @@ -0,0 +1,3 @@ +--recursive +--timeout 120000 +--bail \ No newline at end of file diff --git a/tests-new/index.android.js b/tests-new/index.android.js new file mode 100755 index 00000000..3a208360 --- /dev/null +++ b/tests-new/index.android.js @@ -0,0 +1 @@ +require('./app'); \ No newline at end of file diff --git a/tests-new/index.js b/tests-new/index.js new file mode 100755 index 00000000..3a208360 --- /dev/null +++ b/tests-new/index.js @@ -0,0 +1 @@ +require('./app'); \ No newline at end of file diff --git a/tests-new/ios/example.xcodeproj/project.pbxproj b/tests-new/ios/example.xcodeproj/project.pbxproj new file mode 100755 index 00000000..efee9ce1 --- /dev/null +++ b/tests-new/ios/example.xcodeproj/project.pbxproj @@ -0,0 +1,1077 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 46EE18632047465300FAAB0E /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 46EE18602047463E00FAAB0E /* libRCTAnimation.a */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTActionSheet; + }; + 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; + 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTImage; + }; + 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511DB1A9E6C8500147676; + remoteInfo = RCTNetwork; + }; + 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTVibration; + }; + 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTSettings; + }; + 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; + 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = React; + }; + 392FDE1D1E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A283A1D9B042B00D4039D; + remoteInfo = "RCTImage-tvOS"; + }; + 392FDE211E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28471D9B043800D4039D; + remoteInfo = "RCTLinking-tvOS"; + }; + 392FDE251E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28541D9B044C00D4039D; + remoteInfo = "RCTNetwork-tvOS"; + }; + 392FDE291E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28611D9B046600D4039D; + remoteInfo = "RCTSettings-tvOS"; + }; + 392FDE2D1E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A287B1D9B048500D4039D; + remoteInfo = "RCTText-tvOS"; + }; + 392FDE321E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28881D9B049200D4039D; + remoteInfo = "RCTWebSocket-tvOS"; + }; + 392FDE3C1E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28131D9B038B00D4039D; + remoteInfo = "React-tvOS"; + }; + 392FDE3E1E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3C059A1DE3340900C268FA; + remoteInfo = yoga; + }; + 392FDE401E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3C06751DE3340C00C268FA; + remoteInfo = "yoga-tvOS"; + }; + 392FDE421E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD9251DE5FBEC00167DC4; + remoteInfo = cxxreact; + }; + 392FDE441E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD9321DE5FBEE00167DC4; + remoteInfo = "cxxreact-tvOS"; + }; + 392FDE461E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD90B1DE5FBD600167DC4; + remoteInfo = jschelpers; + }; + 392FDE481E2E5F680058768A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD9181DE5FBD800167DC4; + remoteInfo = "jschelpers-tvOS"; + }; + 46EE1805204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3DBE0D001F3B181A0099AA32; + remoteInfo = fishhook; + }; + 46EE1807204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3DBE0D0D1F3B181C0099AA32; + remoteInfo = "fishhook-tvOS"; + }; + 46EE1819204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EBF21BDC1FC498900052F4D5; + remoteInfo = jsinspector; + }; + 46EE181B204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EBF21BFA1FC4989A0052F4D5; + remoteInfo = "jsinspector-tvOS"; + }; + 46EE181D204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 139D7ECE1E25DB7D00323FB7; + remoteInfo = "third-party"; + }; + 46EE181F204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D383D3C1EBD27B6005632C8; + remoteInfo = "third-party-tvOS"; + }; + 46EE1821204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 139D7E881E25C6D100323FB7; + remoteInfo = "double-conversion"; + }; + 46EE1823204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D383D621EBD27B9005632C8; + remoteInfo = "double-conversion-tvOS"; + }; + 46EE1825204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9936F3131F5F2E4B0010BF04; + remoteInfo = privatedata; + }; + 46EE1827204743A500FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04; + remoteInfo = "privatedata-tvOS"; + }; + 46EE185F2047463E00FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 46EE185A2047463E00FAAB0E /* RCTAnimation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTAnimation; + }; + 46EE18612047463E00FAAB0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 46EE185A2047463E00FAAB0E /* RCTAnimation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28201D9B03D100D4039D; + remoteInfo = "RCTAnimation-tvOS"; + }; + 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTLinking; + }; + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + CC0F353E1D461097008BB94F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* exampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = exampleTests.m; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = example/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = example/AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = example/main.m; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 46EE185A2047463E00FAAB0E /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 46EE18632047465300FAAB0E /* libRCTAnimation.a in Frameworks */, + 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00C302A81ABCB8CE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302B61ABCB90400DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302BC1ABCB91800DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, + 392FDE1E1E2E5F680058768A /* libRCTImage-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302D41ABCB9D200DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, + 392FDE261E2E5F680058768A /* libRCTNetwork-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302E01ABCB9EE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, + ); + name = Products; + sourceTree = ""; + }; + 00E356EF1AD99517003FC87E /* exampleTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* exampleTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = exampleTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 139105B71AF99BAD00B5F7CC /* Products */ = { + isa = PBXGroup; + children = ( + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, + 392FDE2A1E2E5F680058768A /* libRCTSettings-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 139FDEE71B06529A00C62182 /* Products */ = { + isa = PBXGroup; + children = ( + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, + 392FDE331E2E5F680058768A /* libRCTWebSocket-tvOS.a */, + 46EE1806204743A500FAAB0E /* libfishhook.a */, + 46EE1808204743A500FAAB0E /* libfishhook-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* example */ = { + isa = PBXGroup; + children = ( + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = example; + sourceTree = ""; + }; + 146834001AC3E56700842450 /* Products */ = { + isa = PBXGroup; + children = ( + 146834041AC3E56700842450 /* libReact.a */, + 392FDE3D1E2E5F680058768A /* libReact.a */, + 392FDE3F1E2E5F680058768A /* libyoga.a */, + 392FDE411E2E5F680058768A /* libyoga.a */, + 392FDE431E2E5F680058768A /* libcxxreact.a */, + 392FDE451E2E5F680058768A /* libcxxreact.a */, + 392FDE471E2E5F680058768A /* libjschelpers.a */, + 392FDE491E2E5F680058768A /* libjschelpers.a */, + 46EE181A204743A500FAAB0E /* libjsinspector.a */, + 46EE181C204743A500FAAB0E /* libjsinspector-tvOS.a */, + 46EE181E204743A500FAAB0E /* libthird-party.a */, + 46EE1820204743A500FAAB0E /* libthird-party.a */, + 46EE1822204743A500FAAB0E /* libdouble-conversion.a */, + 46EE1824204743A500FAAB0E /* libdouble-conversion.a */, + 46EE1826204743A500FAAB0E /* libprivatedata.a */, + 46EE1828204743A500FAAB0E /* libprivatedata-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 46EE185B2047463E00FAAB0E /* Products */ = { + isa = PBXGroup; + children = ( + 46EE18602047463E00FAAB0E /* libRCTAnimation.a */, + 46EE18622047463E00FAAB0E /* libRCTAnimation.a */, + ); + name = Products; + sourceTree = ""; + }; + 78C398B11ACF4ADC00677621 /* Products */ = { + isa = PBXGroup; + children = ( + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, + 392FDE221E2E5F680058768A /* libRCTLinking-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 46EE185A2047463E00FAAB0E /* RCTAnimation.xcodeproj */, + 146833FF1AC3E56700842450 /* React.xcodeproj */, + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + 392FDE2E1E2E5F680058768A /* libRCTText-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* example */, + CCFA7DE41D11D22600E15EDF /* Frameworks */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* exampleTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + CCFA7DE41D11D22600E15EDF /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + CC0F353E1D461097008BB94F /* Embed Frameworks */, + 7D57265F10EEF7CD92D7973F /* Copy Detox Framework */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = example; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = Facebook; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "example" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; + ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + }, + { + ProductGroup = 46EE185B2047463E00FAAB0E /* Products */; + ProjectRef = 46EE185A2047463E00FAAB0E /* RCTAnimation.xcodeproj */; + }, + { + ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; + ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + }, + { + ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; + ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + }, + { + ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; + ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + }, + { + ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; + ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + }, + { + ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; + ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + }, + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; + ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + }, + { + ProductGroup = 139FDEE71B06529A00C62182 /* Products */; + ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + }, + { + ProductGroup = 146834001AC3E56700842450 /* Products */; + ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* example */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTActionSheet.a; + remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTImage.a; + remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetwork.a; + remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTVibration.a; + remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTSettings.a; + remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 146834041AC3E56700842450 /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE1E1E2E5F680058768A /* libRCTImage-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTImage-tvOS.a"; + remoteRef = 392FDE1D1E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE221E2E5F680058768A /* libRCTLinking-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTLinking-tvOS.a"; + remoteRef = 392FDE211E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE261E2E5F680058768A /* libRCTNetwork-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTNetwork-tvOS.a"; + remoteRef = 392FDE251E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE2A1E2E5F680058768A /* libRCTSettings-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTSettings-tvOS.a"; + remoteRef = 392FDE291E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE2E1E2E5F680058768A /* libRCTText-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTText-tvOS.a"; + remoteRef = 392FDE2D1E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE331E2E5F680058768A /* libRCTWebSocket-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTWebSocket-tvOS.a"; + remoteRef = 392FDE321E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE3D1E2E5F680058768A /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 392FDE3C1E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE3F1E2E5F680058768A /* libyoga.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libyoga.a; + remoteRef = 392FDE3E1E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE411E2E5F680058768A /* libyoga.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libyoga.a; + remoteRef = 392FDE401E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE431E2E5F680058768A /* libcxxreact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libcxxreact.a; + remoteRef = 392FDE421E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE451E2E5F680058768A /* libcxxreact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libcxxreact.a; + remoteRef = 392FDE441E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE471E2E5F680058768A /* libjschelpers.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjschelpers.a; + remoteRef = 392FDE461E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 392FDE491E2E5F680058768A /* libjschelpers.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjschelpers.a; + remoteRef = 392FDE481E2E5F680058768A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE1806204743A500FAAB0E /* libfishhook.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libfishhook.a; + remoteRef = 46EE1805204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE1808204743A500FAAB0E /* libfishhook-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libfishhook-tvOS.a"; + remoteRef = 46EE1807204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE181A204743A500FAAB0E /* libjsinspector.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjsinspector.a; + remoteRef = 46EE1819204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE181C204743A500FAAB0E /* libjsinspector-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libjsinspector-tvOS.a"; + remoteRef = 46EE181B204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE181E204743A500FAAB0E /* libthird-party.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libthird-party.a"; + remoteRef = 46EE181D204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE1820204743A500FAAB0E /* libthird-party.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libthird-party.a"; + remoteRef = 46EE181F204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE1822204743A500FAAB0E /* libdouble-conversion.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libdouble-conversion.a"; + remoteRef = 46EE1821204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE1824204743A500FAAB0E /* libdouble-conversion.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libdouble-conversion.a"; + remoteRef = 46EE1823204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE1826204743A500FAAB0E /* libprivatedata.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libprivatedata.a; + remoteRef = 46EE1825204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE1828204743A500FAAB0E /* libprivatedata-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libprivatedata-tvOS.a"; + remoteRef = 46EE1827204743A500FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE18602047463E00FAAB0E /* libRCTAnimation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAnimation.a; + remoteRef = 46EE185F2047463E00FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 46EE18622047463E00FAAB0E /* libRCTAnimation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAnimation.a; + remoteRef = 46EE18612047463E00FAAB0E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTLinking.a; + remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; + }; + 7D57265F10EEF7CD92D7973F /* Copy Detox Framework */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Detox Framework"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "if [ -n \"$DEPLOY_DETOX_FRAMEWORK\" ]; then\nmkdir -p \"${BUILT_PRODUCTS_DIR}\"/\"${FRAMEWORKS_FOLDER_PATH}\"\ncp -r \"${PROJECT_DIR}\"/../node_modules/detox/Detox.framework \"${BUILT_PRODUCTS_DIR}\"/\"${FRAMEWORKS_FOLDER_PATH}\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = example; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEAD_CODE_STRIPPING = NO; + INFOPLIST_FILE = example/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.wix.demo.react.native; + PRODUCT_NAME = example; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = example/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.wix.demo.react.native; + PRODUCT_NAME = example; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme b/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme new file mode 100755 index 00000000..944802f2 --- /dev/null +++ b/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme b/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme new file mode 100755 index 00000000..effa046b --- /dev/null +++ b/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests-new/ios/example/AppDelegate.h b/tests-new/ios/example/AppDelegate.h new file mode 100755 index 00000000..a9654d5e --- /dev/null +++ b/tests-new/ios/example/AppDelegate.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface AppDelegate : UIResponder + +@property (nonatomic, strong) UIWindow *window; + +@end diff --git a/tests-new/ios/example/AppDelegate.m b/tests-new/ios/example/AppDelegate.m new file mode 100755 index 00000000..71925b85 --- /dev/null +++ b/tests-new/ios/example/AppDelegate.m @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "AppDelegate.h" +#import +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + NSURL *jsCodeLocation; + jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; + + + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation + moduleName:@"example" + initialProperties:nil + launchOptions:launchOptions]; + rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + + return YES; +} + +@end diff --git a/tests-new/ios/example/Base.lproj/LaunchScreen.xib b/tests-new/ios/example/Base.lproj/LaunchScreen.xib new file mode 100755 index 00000000..9e04807a --- /dev/null +++ b/tests-new/ios/example/Base.lproj/LaunchScreen.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests-new/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json b/tests-new/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 00000000..52e440d7 --- /dev/null +++ b/tests-new/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,48 @@ +{ + "images": [ + { + "idiom": "iphone", + "size": "20x20", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "20x20", + "scale": "3x" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "3x" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "3x" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/tests-new/ios/example/Info.plist b/tests-new/ios/example/Info.plist new file mode 100755 index 00000000..682480a6 --- /dev/null +++ b/tests-new/ios/example/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSLocationWhenInUseUsageDescription + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/tests-new/ios/example/main.m b/tests-new/ios/example/main.m new file mode 100755 index 00000000..3d767fcb --- /dev/null +++ b/tests-new/ios/example/main.m @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/tests-new/ios/exampleTests/Info.plist b/tests-new/ios/exampleTests/Info.plist new file mode 100755 index 00000000..ba72822e --- /dev/null +++ b/tests-new/ios/exampleTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/tests-new/ios/exampleTests/exampleTests.m b/tests-new/ios/exampleTests/exampleTests.m new file mode 100755 index 00000000..3fab38df --- /dev/null +++ b/tests-new/ios/exampleTests/exampleTests.m @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import +#import + +#define TIMEOUT_SECONDS 600 +#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" + +@interface exampleTests : XCTestCase + +@end + +@implementation exampleTests + +- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test +{ + if (test(view)) { + return YES; + } + for (UIView *subview in [view subviews]) { + if ([self findSubviewInView:subview matching:test]) { + return YES; + } + } + return NO; +} + +- (void)testRendersWelcomeScreen +{ + UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; + BOOL foundElement = NO; + + __block NSString *redboxError = nil; + RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { + if (level >= RCTLogLevelError) { + redboxError = message; + } + }); + + while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + + foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { + if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { + return YES; + } + return NO; + }]; + } + + RCTSetLogFunction(RCTDefaultLogFunction); + + XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); + XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); +} + + +@end diff --git a/tests-new/package.json b/tests-new/package.json new file mode 100755 index 00000000..e96d0b19 --- /dev/null +++ b/tests-new/package.json @@ -0,0 +1,47 @@ +{ + "name": "example", + "version": "7.2.0", + "private": true, + "scripts": { + "start": "react-native start" + }, + "dependencies": { + "react": "16.0.0-beta.5", + "react-native": "0.49.3" + }, + "devDependencies": { + "detox": "^7.2.0", + "mocha": "^4.0.1" + }, + "detox": { + "test-runner": "mocha", + "specs": "e2e", + "runner-config": "e2e/mocha.opts", + "configurations": { + "ios.sim.release": { + "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/example.app", + "build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -project ios/example.xcodeproj -scheme example -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -quiet", + "type": "ios.simulator", + "name": "iPhone 7 Plus" + }, + "ios.sim.debug": { + "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app", + "build": "xcodebuild -project ios/example.xcodeproj -scheme example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", + "type": "ios.simulator", + "name": "iPhone 7 Plus" + }, + "android.emu.debug": { + "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk", + "build": "pushd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && popd", + "type": "android.emulator", + "name": "Nexus_5X_API_24_-_GPlay" + }, + "android.emu.release": { + "binaryPath": "android/app/build/outputs/apk/release/app-release.apk", + "build": "pushd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && popd", + "type": "android.emulator", + "name": "Nexus_5X_API_24_-_GPlay" + } + } + } +} diff --git a/tests-new/rn-cli.config.js b/tests-new/rn-cli.config.js new file mode 100755 index 00000000..bc94d8d4 --- /dev/null +++ b/tests-new/rn-cli.config.js @@ -0,0 +1,12 @@ +let metroBundler; +try { + metroBundler = require('metro'); +} catch (ex) { + metroBundler = require('metro-bundler'); +} + +module.exports = { + getBlacklistRE: function() { + return metroBundler.createBlacklist([/test\/.*/]); + } +}; \ No newline at end of file From c3abaa1cc87b3e1c3c5a3b3a8e15ac6db6a64696 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 23 Mar 2018 13:41:50 +0000 Subject: [PATCH 02/40] git hook derps --- tests-new/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests-new/README.md b/tests-new/README.md index 1b84cd90..14f3aaec 100755 --- a/tests-new/README.md +++ b/tests-new/README.md @@ -21,7 +21,6 @@ * Build the demo project - ```sh detox build --configuration ios.sim.release ``` @@ -30,7 +29,6 @@ detox build --configuration ios.sim.release * Run tests on the demo project - ```sh detox test --configuration ios.sim.release ``` @@ -43,7 +41,6 @@ This action will open a new simulator and run the tests on it. * Build the demo project - ```sh detox build --configuration ios.sim.debug ``` @@ -52,14 +49,12 @@ detox build --configuration ios.sim.debug * start react-native packager - ```sh npm run start ``` * Run tests on the demo project - ```sh detox test --configuration ios.sim.debug ``` From 6b8556ef7cd6dae99eb3b36f3549fc731a946921 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 23 Mar 2018 14:26:11 +0000 Subject: [PATCH 03/40] [wip] new testing infra --- .gitignore | 4 + tests-new/.babelrc | 16 + tests-new/.editorconfig | 10 + tests-new/.eslintrc | 39 + tests-new/README.md | 9 +- tests-new/android/app/build.gradle | 18 +- tests-new/android/app/google-services.json | 42 + tests-new/android/build.gradle | 29 + tests-new/android/gradle.properties | 1 + tests-new/android/settings.gradle | 4 +- tests-new/e2eExplicitRequire/example.spec.js | 26 - tests-new/e2eExplicitRequire/init.js | 14 - tests-new/e2eExplicitRequire/mocha.opts | 3 - tests-new/firebase/firebase-app.js | 175 +++ tests-new/firebase/firebase.js | 228 +++ tests-new/firebase/flow.js | 50 + tests-new/firebase/index.d.ts | 1368 +++++++++++++++++ tests-new/firebase/index.js | 80 + tests-new/firebase/internals.js | 239 +++ .../firebase/modules/admob/AdMobComponent.js | 100 ++ tests-new/firebase/modules/admob/AdRequest.js | 58 + tests-new/firebase/modules/admob/Banner.js | 14 + .../firebase/modules/admob/EventTypes.js | 23 + .../firebase/modules/admob/Interstitial.js | 119 ++ .../firebase/modules/admob/NativeExpress.js | 14 + .../firebase/modules/admob/RewardedVideo.js | 118 ++ .../firebase/modules/admob/VideoOptions.js | 16 + tests-new/firebase/modules/admob/index.js | 121 ++ tests-new/firebase/modules/analytics/index.js | 167 ++ .../modules/auth/ConfirmationResult.js | 37 + .../modules/auth/PhoneAuthListener.js | 347 +++++ tests-new/firebase/modules/auth/User.js | 334 ++++ tests-new/firebase/modules/auth/index.js | 526 +++++++ .../modules/auth/phone/ConfirmationResult.js | 37 + .../modules/auth/phone/PhoneAuthListener.js | 347 +++++ .../auth/providers/EmailAuthProvider.js | 27 + .../auth/providers/FacebookAuthProvider.js | 27 + .../auth/providers/GithubAuthProvider.js | 27 + .../auth/providers/GoogleAuthProvider.js | 27 + .../modules/auth/providers/OAuthProvider.js | 27 + .../auth/providers/PhoneAuthProvider.js | 27 + .../auth/providers/TwitterAuthProvider.js | 27 + tests-new/firebase/modules/auth/types.js | 75 + tests-new/firebase/modules/base.js | 21 + tests-new/firebase/modules/config/index.js | 185 +++ tests-new/firebase/modules/core/app.js | 196 +++ tests-new/firebase/modules/core/firebase.js | 226 +++ tests-new/firebase/modules/crash/index.js | 87 ++ .../firebase/modules/database/DataSnapshot.js | 146 ++ .../firebase/modules/database/OnDisconnect.js | 68 + tests-new/firebase/modules/database/Query.js | 108 ++ .../firebase/modules/database/Reference.js | 894 +++++++++++ tests-new/firebase/modules/database/index.js | 128 ++ .../firebase/modules/database/transaction.js | 164 ++ .../modules/fabric/crashlytics/index.js | 83 + .../modules/firestore/CollectionReference.js | 109 ++ .../modules/firestore/DocumentChange.js | 41 + .../modules/firestore/DocumentReference.js | 260 ++++ .../modules/firestore/DocumentSnapshot.js | 68 + .../firebase/modules/firestore/FieldPath.js | 22 + .../firebase/modules/firestore/FieldValue.js | 17 + .../firebase/modules/firestore/GeoPoint.js | 29 + tests-new/firebase/modules/firestore/Path.js | 50 + tests-new/firebase/modules/firestore/Query.js | 459 ++++++ .../modules/firestore/QuerySnapshot.js | 78 + .../firebase/modules/firestore/Transaction.js | 151 ++ .../modules/firestore/TransactionHandler.js | 241 +++ .../firebase/modules/firestore/WriteBatch.js | 76 + tests-new/firebase/modules/firestore/index.js | 247 +++ tests-new/firebase/modules/firestore/types.js | 54 + .../firebase/modules/firestore/utils/index.js | 74 + .../modules/firestore/utils/serialize.js | 162 ++ .../firebase/modules/instanceid/index.js | 32 + .../modules/invites/AndroidInvitation.js | 69 + .../firebase/modules/invites/Invitation.js | 110 ++ tests-new/firebase/modules/invites/index.js | 78 + tests-new/firebase/modules/invites/types.js | 21 + .../modules/links/AnalyticsParameters.js | 79 + .../modules/links/AndroidParameters.js | 60 + .../firebase/modules/links/DynamicLink.js | 79 + .../firebase/modules/links/IOSParameters.js | 114 ++ .../modules/links/ITunesParameters.js | 55 + .../modules/links/NavigationParameters.js | 31 + .../modules/links/SocialParameters.js | 55 + tests-new/firebase/modules/links/index.js | 97 ++ tests-new/firebase/modules/links/types.js | 53 + .../modules/messaging/RemoteMessage.js | 155 ++ tests-new/firebase/modules/messaging/index.js | 181 +++ tests-new/firebase/modules/messaging/types.js | 38 + .../modules/notifications/AndroidAction.js | 150 ++ .../modules/notifications/AndroidChannel.js | 198 +++ .../notifications/AndroidChannelGroup.js | 42 + .../notifications/AndroidNotification.js | 676 ++++++++ .../notifications/AndroidNotifications.js | 94 ++ .../notifications/AndroidRemoteInput.js | 123 ++ .../modules/notifications/IOSNotification.js | 160 ++ .../modules/notifications/Notification.js | 169 ++ .../firebase/modules/notifications/index.js | 318 ++++ .../firebase/modules/notifications/types.js | 216 +++ tests-new/firebase/modules/perf/Trace.js | 28 + tests-new/firebase/modules/perf/index.js | 42 + tests-new/firebase/modules/storage/index.js | 164 ++ .../firebase/modules/storage/reference.js | 106 ++ tests-new/firebase/modules/storage/task.js | 215 +++ tests-new/firebase/modules/utils/index.js | 115 ++ tests-new/firebase/types/index.js | 232 +++ tests-new/firebase/utils/ModuleBase.js | 47 + tests-new/firebase/utils/ReferenceBase.js | 29 + tests-new/firebase/utils/SyncTree.js | 334 ++++ tests-new/firebase/utils/apps.js | 203 +++ .../utils/emitter/EmitterSubscription.js | 61 + .../firebase/utils/emitter/EventEmitter.js | 220 +++ .../utils/emitter/EventSubscription.js | 42 + .../utils/emitter/EventSubscriptionVendor.js | 100 ++ tests-new/firebase/utils/events.js | 73 + tests-new/firebase/utils/index.js | 426 +++++ tests-new/firebase/utils/internals.js | 245 +++ tests-new/firebase/utils/log.js | 47 + tests-new/firebase/utils/native.js | 74 + tests-new/package.json | 29 +- tests/android/app/build.gradle | 70 - 121 files changed, 15670 insertions(+), 126 deletions(-) create mode 100644 tests-new/.babelrc create mode 100644 tests-new/.editorconfig create mode 100644 tests-new/.eslintrc create mode 100644 tests-new/android/app/google-services.json delete mode 100755 tests-new/e2eExplicitRequire/example.spec.js delete mode 100755 tests-new/e2eExplicitRequire/init.js delete mode 100755 tests-new/e2eExplicitRequire/mocha.opts create mode 100644 tests-new/firebase/firebase-app.js create mode 100644 tests-new/firebase/firebase.js create mode 100644 tests-new/firebase/flow.js create mode 100644 tests-new/firebase/index.d.ts create mode 100644 tests-new/firebase/index.js create mode 100644 tests-new/firebase/internals.js create mode 100644 tests-new/firebase/modules/admob/AdMobComponent.js create mode 100644 tests-new/firebase/modules/admob/AdRequest.js create mode 100644 tests-new/firebase/modules/admob/Banner.js create mode 100644 tests-new/firebase/modules/admob/EventTypes.js create mode 100644 tests-new/firebase/modules/admob/Interstitial.js create mode 100644 tests-new/firebase/modules/admob/NativeExpress.js create mode 100644 tests-new/firebase/modules/admob/RewardedVideo.js create mode 100644 tests-new/firebase/modules/admob/VideoOptions.js create mode 100644 tests-new/firebase/modules/admob/index.js create mode 100644 tests-new/firebase/modules/analytics/index.js create mode 100644 tests-new/firebase/modules/auth/ConfirmationResult.js create mode 100644 tests-new/firebase/modules/auth/PhoneAuthListener.js create mode 100644 tests-new/firebase/modules/auth/User.js create mode 100644 tests-new/firebase/modules/auth/index.js create mode 100644 tests-new/firebase/modules/auth/phone/ConfirmationResult.js create mode 100644 tests-new/firebase/modules/auth/phone/PhoneAuthListener.js create mode 100644 tests-new/firebase/modules/auth/providers/EmailAuthProvider.js create mode 100644 tests-new/firebase/modules/auth/providers/FacebookAuthProvider.js create mode 100644 tests-new/firebase/modules/auth/providers/GithubAuthProvider.js create mode 100644 tests-new/firebase/modules/auth/providers/GoogleAuthProvider.js create mode 100644 tests-new/firebase/modules/auth/providers/OAuthProvider.js create mode 100644 tests-new/firebase/modules/auth/providers/PhoneAuthProvider.js create mode 100644 tests-new/firebase/modules/auth/providers/TwitterAuthProvider.js create mode 100644 tests-new/firebase/modules/auth/types.js create mode 100644 tests-new/firebase/modules/base.js create mode 100644 tests-new/firebase/modules/config/index.js create mode 100644 tests-new/firebase/modules/core/app.js create mode 100644 tests-new/firebase/modules/core/firebase.js create mode 100644 tests-new/firebase/modules/crash/index.js create mode 100644 tests-new/firebase/modules/database/DataSnapshot.js create mode 100644 tests-new/firebase/modules/database/OnDisconnect.js create mode 100644 tests-new/firebase/modules/database/Query.js create mode 100644 tests-new/firebase/modules/database/Reference.js create mode 100644 tests-new/firebase/modules/database/index.js create mode 100644 tests-new/firebase/modules/database/transaction.js create mode 100644 tests-new/firebase/modules/fabric/crashlytics/index.js create mode 100644 tests-new/firebase/modules/firestore/CollectionReference.js create mode 100644 tests-new/firebase/modules/firestore/DocumentChange.js create mode 100644 tests-new/firebase/modules/firestore/DocumentReference.js create mode 100644 tests-new/firebase/modules/firestore/DocumentSnapshot.js create mode 100644 tests-new/firebase/modules/firestore/FieldPath.js create mode 100644 tests-new/firebase/modules/firestore/FieldValue.js create mode 100644 tests-new/firebase/modules/firestore/GeoPoint.js create mode 100644 tests-new/firebase/modules/firestore/Path.js create mode 100644 tests-new/firebase/modules/firestore/Query.js create mode 100644 tests-new/firebase/modules/firestore/QuerySnapshot.js create mode 100644 tests-new/firebase/modules/firestore/Transaction.js create mode 100644 tests-new/firebase/modules/firestore/TransactionHandler.js create mode 100644 tests-new/firebase/modules/firestore/WriteBatch.js create mode 100644 tests-new/firebase/modules/firestore/index.js create mode 100644 tests-new/firebase/modules/firestore/types.js create mode 100644 tests-new/firebase/modules/firestore/utils/index.js create mode 100644 tests-new/firebase/modules/firestore/utils/serialize.js create mode 100644 tests-new/firebase/modules/instanceid/index.js create mode 100644 tests-new/firebase/modules/invites/AndroidInvitation.js create mode 100644 tests-new/firebase/modules/invites/Invitation.js create mode 100644 tests-new/firebase/modules/invites/index.js create mode 100644 tests-new/firebase/modules/invites/types.js create mode 100644 tests-new/firebase/modules/links/AnalyticsParameters.js create mode 100644 tests-new/firebase/modules/links/AndroidParameters.js create mode 100644 tests-new/firebase/modules/links/DynamicLink.js create mode 100644 tests-new/firebase/modules/links/IOSParameters.js create mode 100644 tests-new/firebase/modules/links/ITunesParameters.js create mode 100644 tests-new/firebase/modules/links/NavigationParameters.js create mode 100644 tests-new/firebase/modules/links/SocialParameters.js create mode 100644 tests-new/firebase/modules/links/index.js create mode 100644 tests-new/firebase/modules/links/types.js create mode 100644 tests-new/firebase/modules/messaging/RemoteMessage.js create mode 100644 tests-new/firebase/modules/messaging/index.js create mode 100644 tests-new/firebase/modules/messaging/types.js create mode 100644 tests-new/firebase/modules/notifications/AndroidAction.js create mode 100644 tests-new/firebase/modules/notifications/AndroidChannel.js create mode 100644 tests-new/firebase/modules/notifications/AndroidChannelGroup.js create mode 100644 tests-new/firebase/modules/notifications/AndroidNotification.js create mode 100644 tests-new/firebase/modules/notifications/AndroidNotifications.js create mode 100644 tests-new/firebase/modules/notifications/AndroidRemoteInput.js create mode 100644 tests-new/firebase/modules/notifications/IOSNotification.js create mode 100644 tests-new/firebase/modules/notifications/Notification.js create mode 100644 tests-new/firebase/modules/notifications/index.js create mode 100644 tests-new/firebase/modules/notifications/types.js create mode 100644 tests-new/firebase/modules/perf/Trace.js create mode 100644 tests-new/firebase/modules/perf/index.js create mode 100644 tests-new/firebase/modules/storage/index.js create mode 100644 tests-new/firebase/modules/storage/reference.js create mode 100644 tests-new/firebase/modules/storage/task.js create mode 100644 tests-new/firebase/modules/utils/index.js create mode 100644 tests-new/firebase/types/index.js create mode 100644 tests-new/firebase/utils/ModuleBase.js create mode 100644 tests-new/firebase/utils/ReferenceBase.js create mode 100644 tests-new/firebase/utils/SyncTree.js create mode 100644 tests-new/firebase/utils/apps.js create mode 100644 tests-new/firebase/utils/emitter/EmitterSubscription.js create mode 100644 tests-new/firebase/utils/emitter/EventEmitter.js create mode 100644 tests-new/firebase/utils/emitter/EventSubscription.js create mode 100644 tests-new/firebase/utils/emitter/EventSubscriptionVendor.js create mode 100644 tests-new/firebase/utils/events.js create mode 100644 tests-new/firebase/utils/index.js create mode 100644 tests-new/firebase/utils/internals.js create mode 100644 tests-new/firebase/utils/log.js create mode 100644 tests-new/firebase/utils/native.js diff --git a/.gitignore b/.gitignore index 7800a2cf..59f29596 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,10 @@ tests/build tests/android/app/build tests/ios/Pods tests/firebase +tests-new/build +tests-new/android/app/build +tests-new/ios/Pods +tests-new/firebase .gradle local.properties *.iml diff --git a/tests-new/.babelrc b/tests-new/.babelrc new file mode 100644 index 00000000..0c5dcafa --- /dev/null +++ b/tests-new/.babelrc @@ -0,0 +1,16 @@ +{ + "presets": [ + "react-native" + ], + "env": { + "development": { + "plugins": [ + ["istanbul", { + "include": [ + "**/firebase/**.js" + ] + }] + ] + } + } +} diff --git a/tests-new/.editorconfig b/tests-new/.editorconfig new file mode 100644 index 00000000..0f099897 --- /dev/null +++ b/tests-new/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/tests-new/.eslintrc b/tests-new/.eslintrc new file mode 100644 index 00000000..daf94a0b --- /dev/null +++ b/tests-new/.eslintrc @@ -0,0 +1,39 @@ +{ + "extends": [ + "airbnb", + "prettier", + "prettier/flowtype", + "prettier/react" + ], + "parser": "babel-eslint", + "plugins": [ + "flowtype", + "prettier" + ], + "env": { + "es6": true, + "jasmine": true + }, + "rules": { + "prettier/prettier": ["error", { + "trailingComma": "es5", + "singleQuote": true + }], + + "react/forbid-prop-types": "warn", + "react/jsx-filename-extension": [ + "off", { "extensions": [".js", ".jsx"] } + ], + + "class-methods-use-this": 0, + "no-console": 0, + "no-plusplus": 0, + "no-undef": 0, + "no-underscore-dangle": "off", + "no-use-before-define": 0 + }, + "globals": { + "__DEV__": true, + "window": true + } +} diff --git a/tests-new/README.md b/tests-new/README.md index 14f3aaec..08c06e27 100755 --- a/tests-new/README.md +++ b/tests-new/README.md @@ -1,18 +1,17 @@ -> detox - -# React Native Demo Project +# React Native Firebase - Testing Project ## Requirements * Make sure you have Xcode installed (tested with Xcode 8.1-8.2). -* make sure you have node installed (`brew install node`, node 7.6.0 and up is required for native async-await support, otherwise you'll have to babel the tests). +* make sure you have node installed (`brew install node`, node 7.6.0 and up is required. * Make sure you have react-native dependencies installed: * react-native-cli is installed (`npm install -g react-native-cli`) * watchman is installed (`brew install watchman`) + * [appleSimUtils](https://github.com/wix/AppleSimulatorUtils) + * detox-cli `npm install -g detox-cli` ### Step 1: Npm install -* Make sure you're in folder `examples/demo-react-native`. * Run `npm install`. ## To test Release build of your app diff --git a/tests-new/android/app/build.gradle b/tests-new/android/app/build.gradle index 98beaca9..3a257572 100755 --- a/tests-new/android/app/build.gradle +++ b/tests-new/android/app/build.gradle @@ -1,9 +1,18 @@ apply plugin: "com.android.application" +apply plugin: "com.google.firebase.firebase-perf" +apply plugin: 'io.fabric' import com.android.build.OutputFile +project.ext.react = [ + entryFile: "index.js" +] + apply from: "../../node_modules/react-native/react.gradle" +def enableSeparateBuildPerCPUArchitecture = false +def enableProguardInReleaseBuilds = false + android { compileSdkVersion 27 buildToolsVersion '27.0.2' @@ -21,11 +30,12 @@ android { testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" missingDimensionStrategy "minReactNative", "minReactNative46" + multiDexEnabled true } splits { abi { reset() - enable false + enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86" } @@ -68,10 +78,14 @@ android { } } +project.ext.firebaseVersion = '11.8.0' + dependencies { implementation "com.android.support:appcompat-v7:27.0.2" implementation "com.facebook.react:react-native:+" // From node_modules + compile project(':react-native-firebase') + androidTestImplementation(project(path: ":detox")) androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' @@ -84,3 +98,5 @@ task copyDownloadableDepsToLibs(type: Copy) { from configurations.compile into 'libs' } + +apply plugin: 'com.google.gms.google-services' diff --git a/tests-new/android/app/google-services.json b/tests-new/android/app/google-services.json new file mode 100644 index 00000000..30a94c5d --- /dev/null +++ b/tests-new/android/app/google-services.json @@ -0,0 +1,42 @@ +{ + "project_info": { + "project_number": "305229645282", + "firebase_url": "https://rnfirebase-b9ad4.firebaseio.com", + "project_id": "rnfirebase-b9ad4", + "storage_bucket": "rnfirebase-b9ad4.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:305229645282:android:efe37851d57e1d05", + "android_client_info": { + "package_name": "com.reactnativefirebasedemo" + } + }, + "oauth_client": [ + { + "client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} diff --git a/tests-new/android/build.gradle b/tests-new/android/build.gradle index 8f3a5f42..e4a1617e 100755 --- a/tests-new/android/build.gradle +++ b/tests-new/android/build.gradle @@ -2,9 +2,15 @@ buildscript { repositories { jcenter() google() + maven { + url 'https://maven.fabric.io/public' + } } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.google.gms:google-services:3.1.2' + classpath 'com.google.firebase:firebase-plugins:1.1.1' + classpath 'io.fabric.tools:gradle:1.25.1' } } @@ -18,3 +24,26 @@ allprojects { } } } + +subprojects { + ext { + compileSdk = 27 + buildTools = "27.0.2" + minSdk = 18 + targetSdk = 26 + } + + afterEvaluate { project -> + if (!project.name.equalsIgnoreCase("app") + && project.hasProperty("android")) { + android { + compileSdkVersion compileSdk + buildToolsVersion buildTools + defaultConfig { + minSdkVersion minSdk + targetSdkVersion targetSdk + } + } + } + } + diff --git a/tests-new/android/gradle.properties b/tests-new/android/gradle.properties index b3052b36..135b0123 100755 --- a/tests-new/android/gradle.properties +++ b/tests-new/android/gradle.properties @@ -19,3 +19,4 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro android.useDeprecatedNdk=true android.enableAapt2=false +org.gradle.jvmargs=-Xmx1536M diff --git a/tests-new/android/settings.gradle b/tests-new/android/settings.gradle index 64bd8d53..0f7f3a44 100755 --- a/tests-new/android/settings.gradle +++ b/tests-new/android/settings.gradle @@ -1,6 +1,8 @@ rootProject.name = 'DetoxRNExample' +include ':react-native-firebase' +project(':react-native-firebase').projectDir = new File(rootProject.projectDir, './../../android') include ':app' include ':detox' -project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') \ No newline at end of file +project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') diff --git a/tests-new/e2eExplicitRequire/example.spec.js b/tests-new/e2eExplicitRequire/example.spec.js deleted file mode 100755 index c822777f..00000000 --- a/tests-new/e2eExplicitRequire/example.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -const {device, expect, element, by, waitFor} = require('detox'); - -describe('Example', () => { - beforeEach(async () => { - await device.reloadReactNative(); - }); - - it('should have welcome screen', async () => { - await expect(element(by.id('welcome'))).toBeVisible(); - }); - - it('should show hello screen after tap', async () => { - await element(by.id('hello_button')).tap(); - await expect(element(by.text('Hello!!!'))).toBeVisible(); - }); - - it('should show world screen after tap', async () => { - await element(by.id('world_button')).tap(); - await expect(element(by.text('World!!!'))).toBeVisible(); - }); - - it('waitFor should be exported', async () => { - await waitFor(element(by.id('welcome'))).toExist().withTimeout(2000); - await expect(element(by.id('welcome'))).toExist(); - }); -}); diff --git a/tests-new/e2eExplicitRequire/init.js b/tests-new/e2eExplicitRequire/init.js deleted file mode 100755 index 9ff09dc1..00000000 --- a/tests-new/e2eExplicitRequire/init.js +++ /dev/null @@ -1,14 +0,0 @@ -const detox = require('detox'); -const config = require('../package.json').detox; - -/* -Example showing how to use Detox with required objects rather than globally exported. -e.g `const {device, expect, element, by, waitFor} = require('detox');` - */ -before(async () => { - await detox.init(config, {initGlobals: false}); -}); - -after(async () => { - await detox.cleanup(); -}); diff --git a/tests-new/e2eExplicitRequire/mocha.opts b/tests-new/e2eExplicitRequire/mocha.opts deleted file mode 100755 index 99b1114b..00000000 --- a/tests-new/e2eExplicitRequire/mocha.opts +++ /dev/null @@ -1,3 +0,0 @@ ---recursive ---timeout 120000 ---bail \ No newline at end of file diff --git a/tests-new/firebase/firebase-app.js b/tests-new/firebase/firebase-app.js new file mode 100644 index 00000000..ae1a254d --- /dev/null +++ b/tests-new/firebase/firebase-app.js @@ -0,0 +1,175 @@ +import { NativeModules } from 'react-native'; + +import INTERNALS from './internals'; +import { isObject, isAndroid } from './utils'; + +import AdMob, { statics as AdMobStatics } from './modules/admob'; +import Auth, { statics as AuthStatics } from './modules/auth'; +import Analytics from './modules/analytics'; +import Crash from './modules/crash'; +import Performance from './modules/perf'; +import RemoteConfig from './modules/config'; +import Storage, { statics as StorageStatics } from './modules/storage'; +import Database, { statics as DatabaseStatics } from './modules/database'; +import Messaging, { statics as MessagingStatics } from './modules/messaging'; +import Firestore, { statics as FirestoreStatics } from './modules/firestore'; +import Links, { statics as LinksStatics } from './modules/links'; +import Utils, { statics as UtilsStatics } from './modules/utils'; + +const FirebaseCoreModule = NativeModules.RNFirebase; + +export default class FirebaseApp { + constructor(name: string, options: Object = {}) { + this._name = name; + this._namespaces = {}; + this._options = Object.assign({}, options); + + // native ios/android to confirm initialized + this._initialized = false; + this._nativeInitialized = false; + + // modules + this.admob = this._staticsOrModuleInstance(AdMobStatics, AdMob); + this.auth = this._staticsOrModuleInstance(AuthStatics, Auth); + this.analytics = this._staticsOrModuleInstance({}, Analytics); + this.config = this._staticsOrModuleInstance({}, RemoteConfig); + this.crash = this._staticsOrModuleInstance({}, Crash); + this.database = this._staticsOrModuleInstance(DatabaseStatics, Database); + this.firestore = this._staticsOrModuleInstance(FirestoreStatics, Firestore); + this.links = this._staticsOrModuleInstance(LinksStatics, Links); + this.messaging = this._staticsOrModuleInstance(MessagingStatics, Messaging); + this.perf = this._staticsOrModuleInstance({}, Performance); + this.storage = this._staticsOrModuleInstance(StorageStatics, Storage); + this.utils = this._staticsOrModuleInstance(UtilsStatics, Utils); + this._extendedProps = {}; + } + + /** + * + * @param native + * @private + */ + _initializeApp(native = false) { + if (native) { + // for apps already initialized natively that + // we have info from RN constants + this._initialized = true; + this._nativeInitialized = true; + } else { + FirebaseCoreModule.initializeApp(this._name, this._options, (error, result) => { + this._initialized = true; + INTERNALS.SharedEventEmitter.emit(`AppReady:${this._name}`, { error, result }); + }); + } + } + + /** + * + * @return {*} + */ + get name() { + if (this._name === INTERNALS.STRINGS.DEFAULT_APP_NAME) { + // ios and android firebase sdk's return different + // app names - so we just return what the web sdk + // would if it was default. + return '[DEFAULT]'; + } + + return this._name; + } + + /** + * + * @return {*} + */ + get options() { + return Object.assign({}, this._options); + } + + /** + * Undocumented firebase web sdk method that allows adding additional properties onto + * a firebase app instance. + * + * See: https://github.com/firebase/firebase-js-sdk/blob/master/tests/app/firebase_app.test.ts#L328 + * + * @param props + */ + extendApp(props: Object) { + if (!isObject(props)) throw new Error(INTERNALS.ERROR_MISSING_ARG('Object', 'extendApp')); + const keys = Object.keys(props); + + for (let i = 0, len = keys.length; i < len; i++) { + const key = keys[i]; + + if (!this._extendedProps[key] && Object.hasOwnProperty.call(this, key)) { + throw new Error(INTERNALS.ERROR_PROTECTED_PROP(key)); + } + + this[key] = props[key]; + this._extendedProps[key] = true; + } + } + + /** + * + * @return {Promise} + */ + delete() { + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('app', 'delete')); + // TODO only the ios sdk currently supports delete, add back in when android also supports it + // if (this._name === INTERNALS.STRINGS.DEFAULT_APP_NAME && this._nativeInitialized) { + // return Promise.reject( + // new Error('Unable to delete the default native firebase app instance.'), + // ); + // } + // + // return FirebaseCoreModule.deleteApp(this._name); + } + + + /** + * + * @return {*} + */ + onReady(): Promise { + if (this._initialized) return Promise.resolve(this); + + return new Promise((resolve, reject) => { + INTERNALS.SharedEventEmitter.once(`AppReady:${this._name}`, ({ error }) => { + if (error) return reject(new Error(error)); // error is a string as it's from native + return resolve(this); // return app + }); + }); + } + + /** + * + * @param name + * @param statics + * @param InstanceClass + * @return {function()} + * @private + */ + _staticsOrModuleInstance(statics = {}, InstanceClass): Function { + const getInstance = () => { + const _name = `_${InstanceClass._NAMESPACE}`; + + if (isAndroid && InstanceClass._NAMESPACE !== Utils._NAMESPACE && !INTERNALS.FLAGS.checkedPlayServices) { + INTERNALS.FLAGS.checkedPlayServices = true; + this.utils().checkPlayServicesAvailability(); + } + + if (!this._namespaces[_name]) { + this._namespaces[_name] = new InstanceClass(this, this._options); + } + + return this._namespaces[_name]; + }; + + Object.assign(getInstance, statics, { + nativeModuleExists: !!NativeModules[InstanceClass._NATIVE_MODULE], + }); + + return getInstance; + } +} diff --git a/tests-new/firebase/firebase.js b/tests-new/firebase/firebase.js new file mode 100644 index 00000000..1cfc1afb --- /dev/null +++ b/tests-new/firebase/firebase.js @@ -0,0 +1,228 @@ +/** + * @providesModule Firebase + * @flow + */ +import { NativeModules, NativeEventEmitter } from 'react-native'; + +import INTERNALS from './internals'; +import FirebaseApp from './firebase-app'; +import { isObject, isString, isAndroid } from './utils'; + +// module imports +import AdMob, { statics as AdMobStatics } from './modules/admob'; +import Auth, { statics as AuthStatics } from './modules/auth'; +import Analytics from './modules/analytics'; +import Crash from './modules/crash'; +import Performance from './modules/perf'; +import Links, { statics as LinksStatics } from './modules/links'; +import RemoteConfig from './modules/config'; +import Storage, { statics as StorageStatics } from './modules/storage'; +import Database, { statics as DatabaseStatics } from './modules/database'; +import Messaging, { statics as MessagingStatics } from './modules/messaging'; +import Firestore, { statics as FirestoreStatics } from './modules/firestore'; +import Utils, { statics as UtilsStatics } from './modules/utils'; + +const FirebaseCoreModule = NativeModules.RNFirebase; + +class FirebaseCore { + constructor() { + this._nativeEmitters = {}; + this._nativeSubscriptions = {}; + + if (!FirebaseCoreModule) { + throw (new Error(INTERNALS.STRINGS.ERROR_MISSING_CORE)); + } + + this._initializeNativeApps(); + + // modules + this.admob = this._appNamespaceOrStatics(AdMobStatics, AdMob); + this.auth = this._appNamespaceOrStatics(AuthStatics, Auth); + this.analytics = this._appNamespaceOrStatics({}, Analytics); + this.config = this._appNamespaceOrStatics({}, RemoteConfig); + this.crash = this._appNamespaceOrStatics({}, Crash); + this.database = this._appNamespaceOrStatics(DatabaseStatics, Database); + this.firestore = this._appNamespaceOrStatics(FirestoreStatics, Firestore); + this.links = this._appNamespaceOrStatics(LinksStatics, Links); + this.messaging = this._appNamespaceOrStatics(MessagingStatics, Messaging); + this.perf = this._appNamespaceOrStatics(DatabaseStatics, Performance); + this.storage = this._appNamespaceOrStatics(StorageStatics, Storage); + this.utils = this._appNamespaceOrStatics(UtilsStatics, Utils); + } + + /** + * Bootstraps all native app instances that were discovered on boot + * @private + */ + _initializeNativeApps() { + for (let i = 0, len = FirebaseCoreModule.apps.length; i < len; i++) { + const app = FirebaseCoreModule.apps[i]; + const options = Object.assign({}, app); + delete options.name; + INTERNALS.APPS[app.name] = new FirebaseApp(app.name, options); + INTERNALS.APPS[app.name]._initializeApp(true); + } + } + + /** + * Web SDK initializeApp + * + * @param options + * @param name + * @return {*} + */ + initializeApp(options: Object = {}, name: string): FirebaseApp { + if (name && !isString(name)) { + throw new Error(INTERNALS.STRINGS.ERROR_INIT_STRING_NAME); + } + + const _name = (name || INTERNALS.STRINGS.DEFAULT_APP_NAME).toUpperCase(); + + // return an existing app if found + // todo in v4 remove deprecation and throw an error + if (INTERNALS.APPS[_name]) { + console.warn(INTERNALS.STRINGS.WARN_INITIALIZE_DEPRECATION); + return INTERNALS.APPS[_name]; + } + + // only validate if app doesn't already exist + // to allow apps already initialized natively + // to still go through init without erroring (backwards compatibility) + if (!isObject(options)) { + throw new Error(INTERNALS.STRINGS.ERROR_INIT_OBJECT); + } + + if (!options.apiKey) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('apiKey')); + } + + if (!options.appId) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('appId')); + } + + if (!options.databaseURL) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('databaseURL')); + } + + if (!options.messagingSenderId) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('messagingSenderId')); + } + + if (!options.projectId) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('projectId')); + } + + if (!options.storageBucket) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('storageBucket')); + } + + INTERNALS.APPS[_name] = new FirebaseApp(_name, options); + // only initialize if certain props are available + if (options.databaseURL && options.apiKey) { + INTERNALS.APPS[_name]._initializeApp(); + } + + return INTERNALS.APPS[_name]; + } + + /** + * Retrieves a Firebase app instance. + * + * When called with no arguments, the default app is returned. + * When an app name is provided, the app corresponding to that name is returned. + * + * @param name + * @return {*} + */ + app(name?: string): FirebaseApp { + const _name = name ? name.toUpperCase() : INTERNALS.STRINGS.DEFAULT_APP_NAME; + const app = INTERNALS.APPS[_name]; + if (!app) throw new Error(INTERNALS.STRINGS.ERROR_APP_NOT_INIT(_name)); + return app; + } + + /** + * A (read-only) array of all initialized apps. + * @return {Array} + */ + get apps(): Array { + return Object.values(INTERNALS.APPS); + } + + /* + * INTERNALS + */ + + /** + * Subscribe to a native event for js side distribution by appName + * React Native events are hard set at compile - cant do dynamic event names + * so we use a single event send it to js and js then internally can prefix it + * and distribute dynamically. + * + * @param eventName + * @param nativeEmitter + * @private + */ + _subscribeForDistribution(eventName, nativeEmitter) { + if (!this._nativeSubscriptions[eventName]) { + nativeEmitter.addListener(eventName, (event) => { + if (event.appName) { + // native event has an appName property - auto prefix and internally emit + INTERNALS.SharedEventEmitter.emit(`${event.appName}-${eventName}`, event); + } else { + // standard event - no need to prefix + INTERNALS.SharedEventEmitter.emit(eventName, event); + } + }); + + this._nativeSubscriptions[eventName] = true; + } + } + + /** + * + * @param statics + * @param InstanceClass + * @return {function(FirebaseApp=)} + * @private + */ + _appNamespaceOrStatics(statics = {}, InstanceClass): Function { + const namespace = InstanceClass._NAMESPACE; + + const getNamespace = (app?: FirebaseApp) => { + let _app = app; + + // throw an error if it's not a valid app instance + if (_app && !(_app instanceof FirebaseApp)) throw new Error(INTERNALS.STRINGS.ERROR_NOT_APP(namespace)); + + // default to the 'DEFAULT' app if no arg provided - will throw an error + // if default app not initialized + else if (!_app) _app = this.app(INTERNALS.STRINGS.DEFAULT_APP_NAME); + return INTERNALS.APPS[_app._name][namespace](_app); + }; + + Object.assign(getNamespace, statics, { + nativeModuleExists: !!NativeModules[InstanceClass._NATIVE_MODULE], + }); + + return getNamespace; + } + + /** + * + * @param name + * @param nativeModule + * @return {*} + * @private + */ + _getOrSetNativeEmitter(name, nativeModule) { + if (this._nativeEmitters[name]) { + return this._nativeEmitters[name]; + } + + return this._nativeEmitters[name] = new NativeEventEmitter(nativeModule); + } + +} + +export default new FirebaseCore(); diff --git a/tests-new/firebase/flow.js b/tests-new/firebase/flow.js new file mode 100644 index 00000000..f5295758 --- /dev/null +++ b/tests-new/firebase/flow.js @@ -0,0 +1,50 @@ +/* eslint-disable */ +// declare module 'react-native' { +// // noinspection ES6ConvertVarToLetConst +// declare var exports: any; +// } + +declare type AuthResultType = { + authenticated: boolean, + user: Object|null +} | null; + +declare type CredentialType = { + providerId: string, + token: string, + secret: string +}; + +declare type DatabaseListener = { + listenerId: number; + eventName: string; + successCallback: Function; + failureCallback?: Function; +}; + +declare type DatabaseModifier = { + type: 'orderBy' | 'limit' | 'filter'; + name?: string; + key?: string; + limit?: number; + value?: any; + valueType?: string; +}; + +declare type GoogleApiAvailabilityType = { + status: number, + isAvailable: boolean, + isUserResolvableError?: boolean, + hasResolution?: boolean, + error?: string +}; + +declare class FirebaseError { + message: string, + name: string, + code: string, + stack: string, + path: string, + details: string, + modifiers: string +}; diff --git a/tests-new/firebase/index.d.ts b/tests-new/firebase/index.d.ts new file mode 100644 index 00000000..205580ac --- /dev/null +++ b/tests-new/firebase/index.d.ts @@ -0,0 +1,1368 @@ +// Type definitions for React Native Firebase v1.0.0-alpha7 +// Project: https://github.com/invertase/react-native-firebase +// Definitions by: Tal +// TypeScript Version: 2.1 + +declare module "react-native-firebase" { + + /** 3rd party provider Credentials */ + type AuthCredential = { + providerId: string, + token: string, + secret: string + } + + type FirebaseModuleAndStatics = { + (): M; + nativeModuleExists: boolean; + } & S + + // Modules commented-out do not currently have type definitions + export class Firebase { + private constructor(); + // admob: FirebaseModuleAndStatics; + analytics: FirebaseModuleAndStatics; + auth: FirebaseModuleAndStatics; + // config: FirebaseModule; + crash: FirebaseModuleAndStatics; + database: FirebaseModuleAndStatics; + fabric: { + crashlytics: FirebaseModuleAndStatics; + }; + firestore: FirebaseModuleAndStatics; + links: FirebaseModuleAndStatics; + messaging: FirebaseModuleAndStatics; + // perf: FirebaseModuleAndStatics; + storage: FirebaseModuleAndStatics; + // utils: FirebaseModuleAndStatics; + initializeApp(options: Firebase.Options, name: string): App; + app(name?: string): App; + readonly apps: App[]; + readonly SDK_VERSION: string; + } + namespace Firebase { + interface Options { + apiKey: string; + appId: string; + databaseURL: string; + messagingSenderId: string; + projectId: string; + storageBucket: string; + } + } + const firebase: Firebase; + export default firebase; + + // Modules commented-out do not currently have type definitions + export class App { + private constructor(); + // admob(): RNFirebase.admob.AdMob; + analytics(): RNFirebase.Analytics; + auth(): RNFirebase.auth.Auth; + // config(): RNFirebase.config.Config; + crash(): RNFirebase.crash.Crash; + database(): RNFirebase.database.Database; + fabric: { + crashlytics(): RNFirebase.crashlytics.Crashlytics, + }; + firestore(): RNFirebase.firestore.Firestore; + links(): RNFirebase.links.Links; + messaging(): RNFirebase.messaging.Messaging; + // perf(): RNFirebase.perf.Performance; + storage(): RNFirebase.storage.Storage; + // utils(): RNFirebase.utils.Utils; + readonly name: string; + readonly options: Firebase.Options; + } + + export namespace RNFirebase { + interface RnError extends Error { + code?: string; + } + + type GoogleApiAvailabilityType = { + status: number, + isAvailable: boolean, + isUserResolvableError?: boolean, + error?: string + }; + + /** + * pass custom options by passing an object with configuration options. + * The configuration object will be generated first by the native configuration object, if set and then will be overridden if passed in JS. + * That is, all of the following key/value pairs are optional if the native configuration is set. + */ + interface configurationOptions { + /** + * default false + * When set to true, RNFirebase will log messages to the console and fire debug events we can listen to in js + * @usage + * firebase.on('debug', msg => console.log('Received debug message', msg)) + */ + debug?: boolean; + /** + * default false + * When set to true, database persistence will be enabled. + */ + persistence?: boolean; + /** + * Default from app [NSBundle mainBundle] The bundle ID for the app to be bundled with + */ + bundleID?: string; + /** + * defualt "" + * The Google App ID that is used to uniquely identify an instance of an app. + */ + googleAppID?: string; + /** + * deufalt "" + * The database root (i.e. https://my-app.firebaseio.com) + */ + databaseURL?: string; + /** + * defualt "" + * URL scheme to set up durable deep link service + */ + deepLinkURLScheme?: string; + /** + * defualt "" + * The Google Cloud storage bucket name + */ + storageBucket?: string; + /** + * default "" + * The Android client ID used in Google AppInvite when an iOS app has it's android version + */ + androidClientID?: string; + /** + * default "" + * The Project number from the Google Developer's console used to configure Google Cloud Messaging + */ + GCMSenderID?: string; + /** + * default "" + * The tracking ID for Google Analytics + */ + trackingID?: string; + /** + * default "" + * The OAuth2 client ID for iOS application used to authenticate Google Users for signing in with Google + */ + clientID?: string; + /** + * defualt "" + * The secret iOS API key used for authenticating requests from our app + */ + APIKey?: string + } + + namespace storage { + + interface StorageTask extends Promise { + on(event: TaskEvent, + nextOrObserver: (snapshot: any) => any, + error: (error: RnError) => any, + complete: (complete: any) => any): any + + /** + * is not currently supported by react-native-firebase + */ + pause(): void + + /** + * is not currently supported by react-native-firebase + */ + resume(): void + + /** + * is not currently supported by react-native-firebase + */ + cancel(): void + + } + + interface RNStorage extends Reference { + /** + * Downloads a reference to the device + * @param {String} filePath Where to store the file + * @return {Promise} + * */ + downloadFile(filePath: string): StorageTask; + + /** + * Upload a file path + * @returns {Promise} + */ + putFile(filePath: string, metadata?: any): StorageTask; + + setMaxDownloadRetryTime(time: number): void + + [key: string]: any; + } + + interface Storage { + maxOperationRetryTime: number; + maxUploadRetryTime: number; + + ref(path?: string): storage.RNStorage; + + refFromURL(url: string): storage.RNStorage; + + setMaxOperationRetryTime(time: number): any; + + setMaxUploadRetryTime(time: number): any; + } + + interface Reference { + bucket: string; + + child(path: string): storage.Reference; + + delete(): Promise; + + fullPath: string; + + getDownloadURL(): Promise; + + getMetadata(): Promise; + + name: string; + parent: storage.Reference | null; + + put(data: any | Uint8Array | ArrayBuffer, + metadata?: storage.UploadMetadata): storage.UploadTask; + + putString(data: string, format?: storage.StringFormat, + metadata?: storage.UploadMetadata): storage.UploadTask; + + root: storage.Reference; + storage: storage.Storage; + + toString(): string; + + updateMetadata(metadata: storage.SettableMetadata): Promise; + } + + interface UploadMetadata extends storage.SettableMetadata { + md5Hash?: string | null; + } + + interface SettableMetadata { + cacheControl?: string | null; + contentDisposition?: string | null; + contentEncoding?: string | null; + contentLanguage?: string | null; + contentType?: string | null; + customMetadata?: { [/* warning: coerced from ? */ key: string]: string } | null; + } + + type StringFormat = string; + var StringFormat: { + BASE64: StringFormat, + BASE64URL: StringFormat, + DATA_URL: StringFormat, + RAW: StringFormat, + } + + interface UploadTask { + cancel(): boolean; + + catch(onRejected: (a: RnError) => any): Promise; + + on(event: storage.TaskEvent, nextOrObserver?: null | Object, + error?: ((a: RnError) => any) | null, complete?: (() => any) | null): Function; + + pause(): boolean; + + resume(): boolean; + + snapshot: storage.UploadTaskSnapshot; + + then(onFulfilled?: ((a: storage.UploadTaskSnapshot) => any) | null, + onRejected?: ((a: RnError) => any) | null): Promise; + } + + interface UploadTaskSnapshot { + bytesTransferred: number; + downloadURL: string | null; + metadata: storage.FullMetadata; + ref: storage.Reference; + state: storage.TaskState; + task: storage.UploadTask; + totalBytes: number; + } + + interface FullMetadata extends storage.UploadMetadata { + bucket: string; + downloadURLs: string[]; + fullPath: string; + generation: string; + metageneration: string; + name: string; + size: number; + timeCreated: string; + updated: string; + } + + type TaskEvent = string; + var TaskEvent: { + STATE_CHANGED: TaskEvent, + }; + + type TaskState = string; + var TaskState: { + CANCELED: TaskState, + ERROR: TaskState, + PAUSED: TaskState, + RUNNING: TaskState, + SUCCESS: TaskState, + }; + } + + + namespace database { + + + interface Database { + /** + * Returns a new firebase reference instance + * */ + ref(path?: string): RnReference + + /** + * register listener + */ + on(path: string, modifiersString: string, modifiers: Array, eventName: string, cb: () => void, errorCb: () => void): any + + /** + * unregister listener + */ + off(path: string, modifiersString: string, eventName?: string, origCB?: () => void): any + + /** + * Removes all event handlers and their native subscriptions + */ + cleanup(): Promise + + /** + * connect to firebase backend + */ + goOnline(): void + + /** + * disconnect to firebase backend + */ + goOffline(): void + + [key: string]: any; + } + + interface RnReference extends Reference { + keepSynced(bool: boolean): any + + filter(name: string, value: any, key?: string): any; + + [key: string]: any; + } + + type QueryEventType = "value" | "child_added" | "child_removed" | "child_changed" | "child_moved"; + type QuerySuccessCallback = (snapshot: DataSnapshot, previousChildId?: string | null) => void; + type QueryErrorCallback = (e: Error) => void; + + interface Query { + endAt(value: number | string | boolean | null, key?: string): database.Query; + + equalTo(value: number | string | boolean | null, key?: string): database.Query; + + isEqual(other: database.Query | null): boolean; + + limitToFirst(limit: number): database.Query; + + limitToLast(limit: number): database.Query; + + off(eventType?: QueryEventType, + callback?: QuerySuccessCallback, + context?: Object): void; + + on(eventType: QueryEventType, + callback: QuerySuccessCallback, + cancelCallbackOrContext?: QueryErrorCallback, + context?: Object): (a: database.DataSnapshot | null, b?: string) => QuerySuccessCallback; + + once(eventType: QueryEventType, + successCallback?: QuerySuccessCallback, + failureCallbackOrContext?: QueryErrorCallback, + context?: Object): Promise; + + orderByChild(path: string): database.Query; + + orderByKey(): database.Query; + + orderByPriority(): database.Query; + + orderByValue(): database.Query; + + ref: database.Reference; + + startAt(value: number | string | boolean | null, key?: string): database.Query; + + toJSON(): Object; + + toString(): string; + } + + interface DataSnapshot { + child(path: string): database.DataSnapshot; + + exists(): boolean; + + exportVal(): any; + + forEach(action: (a: database.DataSnapshot) => boolean): boolean; + + getPriority(): string | number | null; + + hasChild(path: string): boolean; + + hasChildren(): boolean; + + key: string | null; + + numChildren(): number; + + ref: database.Reference; + + toJSON(): Object | null; + + val(): any; + } + + interface ThenableReference extends Promise { + } + + interface ThenableReference extends Reference { + } + + interface Reference extends database.Query { + child(path: string): database.Reference; + + key: string | null; + + onDisconnect(): any; + + parent: database.Reference | null; + + push(value?: any, onComplete?: (a: RnError | null) => any): ThenableReference + + remove(onComplete?: (a: RnError | null) => any): Promise; + + root: database.Reference; + + set(value: any, onComplete?: (a: RnError | null) => any): Promise; + + setPriority(priority: string | number | null, + onComplete: (a: RnError | null) => any): Promise; + + setWithPriority(newVal: any, newPriority: string | number | null, + onComplete?: (a: RnError | null) => any): Promise; + + transaction(transactionUpdate: (a: any) => any, + onComplete?: (a: RnError | null, b: boolean, + c: database.DataSnapshot | null) => any, + applyLocally?: boolean): Promise; + + update(values: Object, onComplete?: (a: RnError | null) => any): Promise; + } + + interface DatabaseStatics { + /** @see https://www.firebase.com/docs/java-api/javadoc/com/firebase/client/ServerValue.html#TIMESTAMP */ + ServerValue: { + TIMESTAMP: { + [key: string]: string + } + } + } + } + + /** + * firebase Analytics + */ + interface Analytics { + /**Log a custom event with optional params. */ + logEvent(event: string, params?: Object): void + + /** Sets whether analytics collection is enabled for this app on this device. */ + setAnalyticsCollectionEnabled(enabled: boolean): void + + /** + * Sets the current screen name, which specifies the current visual context in your app. + * Whilst screenClassOverride is optional, + * it is recommended it is always sent as your current class name, + * for example on Android it will always show as 'MainActivity' if not specified. + */ + setCurrentScreen(screenName: string | null, screenClassOverride?: string): void + + /** + * Sets the minimum engagement time required before starting a session. + * The default value is 10000 (10 seconds) + */ + setMinimumSessionDuration(miliseconds: number): void + + /** + * Sets the duration of inactivity that terminates the current session. + * The default value is 1800000 (30 minutes). + */ + setSessionTimeoutDuration(miliseconds: number): void + + /** + * Gives a user a uniqiue identificaition. + * @example + * const id = firebase.auth().currentUser.uid; + * + * firebase.analytics().setUserId(id); + */ + setUserId(id: string | null): void + + /** + * Sets a key/value pair of data on the current user. + */ + setUserProperty(name: string, value: string | null): void; + + [key: string]: any; + } + + type AdditionalUserInfo = { + isNewUser: boolean, + profile?: Object, + providerId: string, + username?: string, + } + + type UserCredential = { + additionalUserInfo?: AdditionalUserInfo, + user: User, + } + + type UserInfo = { + displayName?: string, + email?: string, + phoneNumber?: string, + photoURL?: string, + providerId: string, + uid: string, + } + + type UpdateProfile = { + displayName?: string, + photoURL?: string, + } + + type UserMetadata = { + creationTime?: string, + lastSignInTime?: string, + } + + interface User { + /** + * The user's display name (if available). + */ + displayName: string | null + /** + * - The user's email address (if available). + */ + email: string | null + /** + * - True if the user's email address has been verified. + */ + emailVerified: boolean + /** + * + */ + isAnonymous: boolean + + metadata: UserMetadata + + phoneNumber: string | null + /** + * - The URL of the user's profile picture (if available). + */ + photoURL: string | null + /** + * - Additional provider-specific information about the user. + */ + providerData: Array + /** + * - The authentication provider ID for the current user. + * For example, 'facebook.com', or 'google.com'. + */ + providerId: string + /** + * - The user's unique ID. + */ + uid: string + + /** + * Delete the current user. + */ + delete(): Promise + + /** + * Returns the users authentication token. + * + * @param forceRefresh: boolean - default to false + */ + getIdToken(forceRefresh?: boolean): Promise + + getToken(forceRefresh?: boolean): Promise + + linkAndRetrieveDataWithCredential(credential: AuthCredential): Promise + + /** + * Link the user with a 3rd party credential provider. + */ + linkWithCredential(credential: AuthCredential): Promise + + reauthenticateAndRetrieveDataWithCredential(credential: AuthCredential): Promise + + /** + * Re-authenticate a user with a third-party authentication provider + */ + reauthenticateWithCredential(credential: AuthCredential): Promise + + /** + * Refreshes the current user. + */ + reload(): Promise + + /** + * Sends a verification email to a user. + * This will Promise reject is the user is anonymous. + */ + sendEmailVerification(actionCodeSettings?: ActionCodeSettings): Promise + + toJSON(): object + + unlink(providerId: string): Promise + + /** + * Updates the user's email address. + * See Firebase docs for more information on security & email validation. + * This will Promise reject is the user is anonymous. + */ + updateEmail(email: string): Promise + + /** + * Important: this is a security sensitive operation that requires the user to have recently signed in. + * If this requirement isn't met, ask the user to authenticate again and then call firebase.User#reauthenticate. + * This will Promise reject is the user is anonymous. + */ + updatePassword(password: string): Promise + + /** + * Updates a user's profile data. + * Profile data should be an object of fields to update: + */ + updateProfile(updates: UpdateProfile): Promise + } + + type ActionCodeSettings = { + android: { + installApp?: boolean, + minimumVersion?: string, + packageName: string, + }, + handleCodeInApp?: boolean, + iOS: { + bundleId?: string, + }, + url: string, + } + + interface ActionCodeInfo { + data: { + email?: string, + fromEmail?: string + }, + operation: 'PASSWORD_RESET' | 'VERIFY_EMAIL' | 'RECOVER_EMAIL' + } + + interface ConfirmationResult { + + confirm(verificationCode: string): Promise; + + verificationId: string | null; + } + + type PhoneAuthSnapshot = { + state: 'sent' | 'timeout' | 'verified' | 'error', + verificationId: string, + code: string | null, + error: Error | null, + }; + + type PhoneAuthError = { + code: string | null, + verificationId: string, + message: string | null, + stack: string | null, + }; + + interface PhoneAuthListener { + + on(event: string, + observer: () => PhoneAuthSnapshot, + errorCb?: () => PhoneAuthError, + successCb?: () => PhoneAuthSnapshot): PhoneAuthListener; + + then(fn: () => PhoneAuthSnapshot): Promise + + catch(fn: () => Error): Promise + } + + namespace auth { + type AuthResult = { + authenticated: boolean, + user: object | null + } | null; + + type AuthProvider = { + PROVIDER_ID: string, + credential: (token: string, secret?: string) => AuthCredential, + }; + + interface Auth { + readonly app: App; + /** + * Returns the current Firebase authentication state. + */ + authResult: AuthResult | null; + /** + * Returns the currently signed-in user (or null). See the User class documentation for further usage. + */ + currentUser: User | null + + /** + * Gets/Sets the language for the app instance + */ + languageCode: string | null; + + /** + * Listen for changes in the users auth state (logging in and out). + * This method returns a unsubscribe function to stop listening to events. + * Always ensure you unsubscribe from the listener when no longer needed to prevent updates to components no longer in use. + */ + onAuthStateChanged(listener: Function): () => void; + + /** + * Listen for changes in id token. + * This method returns a unsubscribe function to stop listening to events. + * Always ensure you unsubscribe from the listener when no longer needed to prevent updates to components no longer in use. + */ + onIdTokenChanged(listener: Function): () => void; + + /** + * Listen for changes in the user. + * This method returns a unsubscribe function to stop listening to events. + * Always ensure you unsubscribe from the listener when no longer needed to prevent updates to components no longer in use. + */ + onUserChanged(listener: Function): () => void; + + signOut(): Promise + + signInAnonymouslyAndRetrieveData(): Promise + + /** + * Sign an anonymous user. + * If the user has already signed in, that user will be returned + */ + signInAnonymously(): Promise + + createUserAndRetrieveDataWithEmailAndPassword(email: string, password: string): Promise + + /** + * We can create a user by calling the createUserWithEmailAndPassword() function. + * The method accepts two parameters, an email and a password. + */ + createUserWithEmailAndPassword(email: string, password: string): Promise + + signInAndRetrieveDataWithEmailAndPassword(email: string, password: string): Promise + + /** + * To sign a user in with their email and password, use the signInWithEmailAndPassword() function. + * It accepts two parameters, the user's email and password: + */ + signInWithEmailAndPassword(email: string, password: string): Promise + + signInAndRetrieveDataWithCustomToken(token: string): Promise + + /** + * Sign a user in with a self-signed JWT token. + * To sign a user using a self-signed custom token, + * use the signInWithCustomToken() function. + * It accepts one parameter, the custom token: + */ + signInWithCustomToken(token: string): Promise + + signInAndRetrieveDataWithCredential(credential: AuthCredential): Promise + + /** + * Sign in the user with a 3rd party credential provider. + * credential requires the following properties: + */ + signInWithCredential(credential: AuthCredential): Promise + + /** + * Asynchronously signs in using a phone number. + */ + signInWithPhoneNumber(phoneNumber: string): Promise + + /** + * Returns a PhoneAuthListener to listen to phone verification events, + * on the final completion event a PhoneAuthCredential can be generated for + * authentication purposes. + */ + verifyPhoneNumber(phoneNumber: string, autoVerifyTimeout?: number): PhoneAuthListener + + /** + * Sends a password reset email to the given email address. + * Unlike the web SDK, + * the email will contain a password reset link rather than a code. + */ + sendPasswordResetEmail(email: string, actionCodeSettings?: ActionCodeSettings): Promise + + /** + * Completes the password reset process, given a confirmation code and new password. + */ + confirmPasswordReset(code: string, newPassword: string): Promise + + /** + * Applies a verification code sent to the user by email or other out-of-band mechanism. + */ + applyActionCode(code: string): Promise + + /** + * Checks a verification code sent to the user by email or other out-of-band mechanism. + */ + checkActionCode(code: string): Promise + + /** + * Returns a list of authentication providers that can be used to sign in a given user (identified by its main email address). + */ + fetchProvidersForEmail(email: string): Promise> + + verifyPasswordResetCode(code: string): Promise + + [key: string]: any; + } + + interface AuthStatics { + EmailAuthProvider: AuthProvider; + PhoneAuthProvider: AuthProvider; + GoogleAuthProvider: AuthProvider; + GithubAuthProvider: AuthProvider; + OAuthProvider: AuthProvider; + TwitterAuthProvider: AuthProvider; + FacebookAuthProvider: AuthProvider; + PhoneAuthState: { + CODE_SENT: string; + AUTO_VERIFY_TIMEOUT: string; + AUTO_VERIFIED: string; + ERROR: string; + }; + } + } + + namespace messaging { + + interface Messaging { + /** + * Subscribes the device to a topic. + */ + subscribeToTopic(topic: string): void + + /** + * Unsubscribes the device from a topic. + */ + unsubscribeFromTopic(topic: string): void + + /** + * When the application has been opened from a notification + * getInitialNotification is called and the notification payload is returned. + * Use onMessage for notifications when the app is running. + */ + getInitialNotification(): Promise + + /** + * Returns the devices FCM token. + * This token can be used in the Firebase console to send messages to directly. + */ + getToken(forceRefresh?: Boolean): Promise + + /** + * Reset Instance ID and revokes all tokens. + */ + deleteInstanceId(): Promise + + /** + * On the event a devices FCM token is refreshed by Google, + * the new token is returned in a callback listener. + */ + onTokenRefresh(listener: (token: string) => any): () => any + + /** + * On a new message, + * the payload object is passed to the listener callback. + * This method is only triggered when the app is running. + * Use getInitialNotification for notifications which cause the app to open. + */ + onMessage(listener: (message: any) => any): () => any + + /** + * Create a local notification from the device itself. + */ + createLocalNotification(notification: any): any + + /** + * Schedule a local notification to be shown on the device. + */ + scheduleLocalNotification(notification: any): any + + /** + * Returns an array of all currently scheduled notifications. + * ``` + * firebase.messaging().getScheduledLocalNotifications() + * .then((notifications) => { + * console.log('Current scheduled notifications: ', notifications); + * }); + * ``` + */ + getScheduledLocalNotifications(): Promise + + /** + * Cancels a location notification by ID, + * or all notifications by *. + */ + cancelLocalNotification(id: string): void + + /** + * Removes all delivered notifications from device by ID, + * or all notifications by *. + */ + removeDeliveredNotification(id: string): void + + /** + * IOS + * Requests app notification permissions in an Alert dialog. + */ + requestPermissions(): Promise<{ granted: boolean }>; + + /** + * Sets the badge number on the iOS app icon. + */ + setBadgeNumber(value: number): void + + /** + * Returns the current badge number on the app icon. + */ + getBadgeNumber(): Promise + + /** + * Send an upstream message + * @param senderId + * @param payload + */ + sendMessage(senderId: string, payload: RemoteMessage): any + + NOTIFICATION_TYPE: Object + REMOTE_NOTIFICATION_RESULT: Object + WILL_PRESENT_RESULT: Object + EVENT_TYPE: Object + } + + interface RemoteMessage { + id: string, + type: string, + ttl?: number, + sender: string, + collapseKey?: string, + data: Object, + } + } + namespace crash { + + interface Crash { + /** Logs a message that will appear in a subsequent crash report. */ + log(message: string): void + + /** + * Android: Logs a message that will appear in a subsequent crash report as well as in logcat. + * iOS: Logs the message in the subsequest crash report only (same as log). + */ + logcat(level: number, tag: string, message: string): void + + /** + * Files a crash report, along with any previous logs to Firebase. + * An Error object must be passed into the report method. + */ + report(error: RnError, maxStackSize: Number): void + + [key: string]: any; + } + } + + namespace crashlytics { + + interface Crashlytics { + /** + * Forces a crash. Useful for testing your application is set up correctly. + */ + crash(): void; + + /** + * Logs a message that will appear in any subsequent crash reports. + */ + log(message: string): void; + + /** + * Logs a non fatal exception. + */ + recordError(code: number, message: string): void; + + /** + * Set a boolean value to show alongside any subsequent crash reports. + */ + setBoolValue(key: string, value: boolean): void; + + /** + * Set a float value to show alongside any subsequent crash reports. + */ + setFloatValue(key: string, value: number): void; + + /** + * Set an integer value to show alongside any subsequent crash reports. + */ + setIntValue(key: string, value: number): void; + + /** + * Set a string value to show alongside any subsequent crash reports. + */ + setStringValue(key: string, value: string): void; + + /** + * Set the user ID to show alongside any subsequent crash reports. + */ + setUserIdentifier(userId: string): void; + } + } + + namespace links { + interface Links { + /** Creates a standard dynamic link. */ + createDynamicLink(parameters: LinkConfiguration): Promise; + /** Creates a short dynamic link. */ + createShortDynamicLink(parameters: LinkConfiguration): Promise; + /** + * Returns the URL that the app has been launched from. If the app was + * not launched from a URL the return value will be null. + */ + getInitialLink(): Promise; + /** + * Subscribe to URL open events while the app is still running. + * The listener is called from URL open events whilst the app is still + * running, use getInitialLink for URLs which cause the app to open + * from a previously closed / not running state. + * Returns an unsubscribe function, call the returned function to + * unsubscribe from all future events. + */ + onLink(listener: (url: string) => void): () => void; + } + + /** + * Configuration when creating a Dynamic Link (standard or short). For + * more information about each parameter, see the official Firebase docs: + * https://firebase.google.com/docs/reference/dynamic-links/link-shortener + */ + interface LinkConfiguration { + link: string, + dynamicLinkDomain: string, + androidInfo?: { + androidLink?: string, + androidPackageName: string, + androidFallbackLink?: string, + androidMinPackageVersionCode?: string, + }, + iosInfo?: { + iosBundleId: string, + iosAppStoreId?: string, + iosFallbackLink?: string, + iosCustomScheme?: string, + iosIpadBundleId?: string, + iosIpadFallbackLink?: string, + }, + socialMetaTagInfo?: { + socialTitle: string, + socialImageLink: string, + socialDescription: string, + }, + suffix?: { + option: 'SHORT' | 'UNGUESSABLE', + }, + } + } + + namespace firestore { + interface Firestore { + readonly app: App; + batch(): WriteBatch; + collection(collectionPath: string): CollectionReference; + doc(documentPath: string): DocumentReference; + + /** NOT SUPPORTED YET */ + // enablePersistence(): Promise; + /** NOT SUPPORTED YET */ + // runTransaction(): Promise; + /** NOT SUPPORTED YET */ + // settings(): void; + } + + interface FirestoreStatics { + FieldPath: typeof FieldPath; + FieldValue: typeof FieldValue; + GeoPoint: typeof GeoPoint; + enableLogging(enabled: boolean): void; + } + + interface CollectionReference { + readonly firestore: Firestore; + readonly id: string; + readonly parent: DocumentReference; + add(data: object): Promise; + doc(documentPath?: string): DocumentReference; + endAt(snapshot: DocumentSnapshot): Query; + endAt(...varargs: any[]): Query; + endBefore(snapshot: DocumentSnapshot): Query; + endBefore(...varargs: any[]): Query; + get(): Promise; + limit(limit: number): Query; + onSnapshot(onNext: Query.ObserverOnNext, onError?: Query.ObserverOnError): () => void; + onSnapshot(observer: Query.Observer): () => void; + onSnapshot(queryListenOptions: Query.QueryListenOptions, onNext: Query.ObserverOnNext, onError?: Query.ObserverOnError): () => void; + onSnapshot(queryListenOptions: Query.QueryListenOptions, observer: Query.Observer): () => void; + orderBy(fieldPath: string | FieldPath, directionStr?: Types.QueryDirection): Query; + startAfter(snapshot: DocumentSnapshot): Query; + startAfter(...varargs: any[]): Query; + startAt(snapshot: DocumentSnapshot): Query; + startAt(...varargs: any[]): Query; + where(fieldPath: string, op: Types.QueryOperator, value: any): Query; + } + + interface DocumentChange { + readonly doc: DocumentSnapshot; + readonly newIndex: number; + readonly oldIndex: number; + readonly type: string; + } + + interface DocumentReference { + readonly firestore: Firestore; + readonly id: string | null; + readonly parent: CollectionReference; + readonly path: string; + collection(collectionPath: string): CollectionReference; + delete(): Promise; + get(): Promise; + onSnapshot(onNext: DocumentReference.ObserverOnNext, onError?: DocumentReference.ObserverOnError): () => void; + onSnapshot(observer: DocumentReference.Observer): () => void; + onSnapshot(documentListenOptions: DocumentReference.DocumentListenOptions, onNext: DocumentReference.ObserverOnNext, onError?: DocumentReference.ObserverOnError): () => void; + onSnapshot(documentListenOptions: DocumentReference.DocumentListenOptions, observer: DocumentReference.Observer): () => void; + set(data: object, writeOptions?: Types.WriteOptions): Promise; + update(obj: object): Promise; + update(key1: Types.UpdateKey, val1: any): Promise; + update(key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any): Promise; + update(key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any, key3: Types.UpdateKey, val3: any): Promise; + update(key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any, key3: Types.UpdateKey, val3: any, key4: Types.UpdateKey, val4: any): Promise; + update(key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any, key3: Types.UpdateKey, val3: any, key4: Types.UpdateKey, val4: any, key5: Types.UpdateKey, val5: any): Promise; + } + namespace DocumentReference { + interface DocumentListenOptions { + includeMetadataChanges: boolean; + } + + type ObserverOnNext = (documentSnapshot: DocumentSnapshot) => void; + type ObserverOnError = (err: object) => void; + interface Observer { + next: ObserverOnNext; + error?: ObserverOnError; + } + } + + interface DocumentSnapshot { + readonly exists: boolean; + readonly id: string | null; + readonly metadata: Types.SnapshotMetadata; + readonly ref: DocumentReference; + data(): object | void; + get(fieldPath: string | FieldPath): any | undefined; + } + + class FieldPath { + static documentId(): FieldPath; + constructor(...segments: string[]); + } + + class FieldValue { + static delete(): FieldValue; + static serverTimestamp(): FieldValue; + } + + class GeoPoint { + constructor(latitude: number, longitude: number); + readonly latitude: number; + readonly longitude: number; + } + + class Path { + static fromName(name: string): Path; + constructor(pathComponents: string[]); + readonly id: string | null; + readonly isDocument: boolean; + readonly isCollection: boolean; + readonly relativeName: string; + child(relativePath: string): Path; + parent(): Path | null; + } + + interface Query { + readonly firestore: Firestore; + endAt(snapshot: DocumentSnapshot): Query; + endAt(...varargs: any[]): Query; + endBefore(snapshot: DocumentSnapshot): Query; + endBefore(...varargs: any[]): Query; + get(): Promise; + limit(limit: number): Query; + onSnapshot(onNext: Query.ObserverOnNext, onError?: Query.ObserverOnError): () => void; + onSnapshot(observer: Query.Observer): () => void; + onSnapshot(queryListenOptions: Query.QueryListenOptions, onNext: Query.ObserverOnNext, onError?: Query.ObserverOnError): () => void; + onSnapshot(queryListenOptions: Query.QueryListenOptions, observer: Query.Observer): () => void; + orderBy(fieldPath: string | FieldPath, directionStr?: Types.QueryDirection): Query; + startAfter(snapshot: DocumentSnapshot): Query; + startAfter(...varargs: any[]): Query; + startAt(snapshot: DocumentSnapshot): Query; + startAt(...varargs: any[]): Query; + where(fieldPath: string, op: Types.QueryOperator, value: any): Query; + } + namespace Query { + interface NativeFieldPath { + elements?: string[]; + string?: string; + type: 'fieldpath' | 'string'; + } + + interface FieldFilter { + fieldPath: NativeFieldPath; + operator: string; + value: any; + } + + interface FieldOrder { + direction: string; + fieldPath: NativeFieldPath; + } + + interface QueryOptions { + endAt?: any[]; + endBefore?: any[]; + limit?: number; + offset?: number; + selectFields?: string[]; + startAfter?: any[]; + startAt?: any[]; + } + + // The JS code expects at least one of 'includeDocumentMetadataChanges' + // or 'includeQueryMetadataChanges' to be defined. + interface _IncludeDocumentMetadataChanges { + includeDocumentMetadataChanges: boolean; + } + interface _IncludeQueryMetadataChanges { + includeQueryMetadataChanges: boolean; + } + type QueryListenOptions = _IncludeDocumentMetadataChanges | _IncludeQueryMetadataChanges | (_IncludeDocumentMetadataChanges & _IncludeQueryMetadataChanges); + + type ObserverOnNext = (querySnapshot: QuerySnapshot) => void; + type ObserverOnError = (err: object) => void; + interface Observer { + next: ObserverOnNext; + error?: ObserverOnError; + } + } + + interface QuerySnapshot { + readonly docChanges: DocumentChange[]; + readonly docs: DocumentSnapshot[]; + readonly empty: boolean; + readonly metadata: Types.SnapshotMetadata; + readonly query: Query; + readonly size: number; + forEach(callback: (snapshot: DocumentSnapshot) => any): void; + } + namespace QuerySnapshot { + interface NativeData { + changes: Types.NativeDocumentChange[]; + documents: Types.NativeDocumentSnapshot[]; + metadata: Types.SnapshotMetadata; + } + } + + interface WriteBatch { + commit(): Promise; + delete(docRef: DocumentReference): WriteBatch; + set(docRef: DocumentReference, data: object, options?: Types.WriteOptions): WriteBatch; + // multiple overrides for update() to allow strong-typed var_args + update(docRef: DocumentReference, obj: object): WriteBatch; + update(docRef: DocumentReference, key1: Types.UpdateKey, val1: any): WriteBatch; + update(docRef: DocumentReference, key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any): WriteBatch; + update(docRef: DocumentReference, key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any, key3: Types.UpdateKey, val3: any): WriteBatch; + update(docRef: DocumentReference, key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any, key3: Types.UpdateKey, val3: any, key4: Types.UpdateKey, val4: any): WriteBatch; + update(docRef: DocumentReference, key1: Types.UpdateKey, val1: any, key2: Types.UpdateKey, val2: any, key3: Types.UpdateKey, val3: any, key4: Types.UpdateKey, val4: any, key5: Types.UpdateKey, val5: any): WriteBatch; + } + + namespace Types { + interface NativeDocumentChange { + document: NativeDocumentSnapshot; + newIndex: number; + oldIndex: number; + type: string; + } + + interface NativeDocumentSnapshot { + data: { + [key: string]: TypeMap; + }; + metadata: SnapshotMetadata; + path: string; + } + + interface SnapshotMetadata { + fromCache: boolean; + hasPendingWrites: boolean; + } + + type QueryDirection = 'asc' | 'ASC' | 'desc' | 'DESC'; + type QueryOperator = '=' | '==' | '>' | '>=' | '<' | '<='; + + interface TypeMap { + type: 'array' | 'boolean' | 'date' | 'documentid' | 'fieldvalue' | 'geopoint' | 'null' | 'number' | 'object' | 'reference' | 'string'; + value: any; + } + + /** The key in update() function for DocumentReference and WriteBatch. */ + type UpdateKey = string | FieldPath + + interface WriteOptions { + merge?: boolean; + } + } + } + } +} diff --git a/tests-new/firebase/index.js b/tests-new/firebase/index.js new file mode 100644 index 00000000..9d8a3e8a --- /dev/null +++ b/tests-new/firebase/index.js @@ -0,0 +1,80 @@ +/** + * @flow + */ +import firebase from './modules/core/firebase'; + +export default firebase; + +/* + * Export App types + */ +export type { default as App } from './modules/core/app'; + +/* + * Export Auth types + */ +export type { + ActionCodeInfo, + ActionCodeSettings, + AdditionalUserInfo, + AuthCredential, + UserCredential, + UserInfo, + UserMetadata, +} from './modules/auth/types'; +export type { + default as ConfirmationResult, +} from './modules/auth/phone/ConfirmationResult'; +export type { default as User } from './modules/auth/User'; + +/* + * Export Database types + */ +export type { default as DataSnapshot } from './modules/database/DataSnapshot'; +export type { default as OnDisconnect } from './modules/database/OnDisconnect'; +export type { default as Reference } from './modules/database/Reference'; +export type { default as DataQuery } from './modules/database/Query'; + +/* + * Export Firestore types + */ +export type { + DocumentListenOptions, + QueryListenOptions, + SetOptions, + SnapshotMetadata, +} from './modules/firestore/types'; +export type { + default as CollectionReference, +} from './modules/firestore/CollectionReference'; +export type { + default as DocumentChange, +} from './modules/firestore/DocumentChange'; +export type { + default as DocumentReference, +} from './modules/firestore/DocumentReference'; +export type { + default as DocumentSnapshot, +} from './modules/firestore/DocumentSnapshot'; +export type { default as FieldPath } from './modules/firestore/FieldPath'; +export type { default as FieldValue } from './modules/firestore/FieldValue'; +export type { default as GeoPoint } from './modules/firestore/GeoPoint'; +export type { default as Query } from './modules/firestore/Query'; +export type { + default as QuerySnapshot, +} from './modules/firestore/QuerySnapshot'; +export type { default as WriteBatch } from './modules/firestore/WriteBatch'; + +/* + * Export Messaging types + */ +export type { + default as RemoteMessage, +} from './modules/messaging/RemoteMessage'; + +/* + * Export Notifications types + */ +export type { + default as Notification, +} from './modules/notifications/Notification'; diff --git a/tests-new/firebase/internals.js b/tests-new/firebase/internals.js new file mode 100644 index 00000000..159ba007 --- /dev/null +++ b/tests-new/firebase/internals.js @@ -0,0 +1,239 @@ +import { Platform, NativeModules } from 'react-native'; + +import EventEmitter from './utils/emitter/EventEmitter'; +import SyncTree from './utils/SyncTree'; + +const DEFAULT_APP_NAME = Platform.OS === 'ios' ? '__FIRAPP_DEFAULT' : '[DEFAULT]'; + +const NAMESPACE_PODS = { + admob: 'Firebase/AdMob', + analytics: 'Firebase/Analytics', + auth: 'Firebase/Auth', + config: 'Firebase/RemoteConfig', + crash: 'Firebase/Crash', + database: 'Firebase/Database', + links: 'Firebase/DynamicLinks', + messaging: 'Firebase/Messaging', + perf: 'Firebase/Performance', + storage: 'Firebase/Storage', +}; + +const GRADLE_DEPS = { + admob: 'ads', +}; + +const PLAY_SERVICES_CODES = { + 1: { + code: 'SERVICE_MISSING', + message: 'Google Play services is missing on this device.', + }, + 2: { + code: 'SERVICE_VERSION_UPDATE_REQUIRED', + message: 'The installed version of Google Play services on this device is out of date.', + }, + 3: { + code: 'SERVICE_DISABLED', + message: 'The installed version of Google Play services has been disabled on this device.', + }, + 9: { + code: 'SERVICE_INVALID', + message: 'The version of the Google Play services installed on this device is not authentic.', + }, + 18: { + code: 'SERVICE_UPDATING', + message: 'Google Play services is currently being updated on this device.', + }, + 19: { + code: 'SERVICE_MISSING_PERMISSION', + message: 'Google Play service doesn\'t have one or more required permissions.', + }, +}; + +export default { + // default options + OPTIONS: { + logLevel: 'warn', + errorOnMissingPlayServices: true, + promptOnMissingPlayServices: true, + }, + + FLAGS: { + checkedPlayServices: false, + }, + + // track all initialized firebase apps + APPS: { + [DEFAULT_APP_NAME]: null, + }, + + STRINGS: { + WARN_INITIALIZE_DEPRECATION: 'Deprecation: Calling \'initializeApp()\' for apps that are already initialised natively ' + + 'is unnecessary, use \'firebase.app()\' instead to access the already initialized default app instance.', + + /** + * @return {string} + */ + get ERROR_MISSING_CORE() { + if (Platform.OS === 'ios') { + return 'RNFirebase core module was not found natively on iOS, ensure you have ' + + 'correctly included the RNFirebase pod in your projects `Podfile` and have run `pod install`.' + + '\r\n\r\n See http://invertase.link/ios for the ios setup guide.'; + } + + return 'RNFirebase core module was not found natively on Android, ensure you have ' + + 'correctly added the RNFirebase and Firebase gradle dependencies to your `android/app/build.gradle` file.' + + '\r\n\r\n See http://invertase.link/android for the android setup guide.'; + }, + + + ERROR_INIT_OBJECT: 'Firebase.initializeApp(options <-- requires a valid configuration object.', + ERROR_INIT_STRING_NAME: 'Firebase.initializeApp(options, name <-- requires a valid string value.', + + /** + * @return {string} + */ + ERROR_MISSING_CB(method) { + return `Missing required callback for method ${method}().`; + }, + + /** + * @return {string} + */ + ERROR_MISSING_ARG(type, method) { + return `Missing required argument of type '${type}' for method '${method}()'.`; + }, + + /** + * @return {string} + */ + ERROR_MISSING_ARG_NAMED(name, type, method) { + return `Missing required argument '${name}' of type '${type}' for method '${method}()'.`; + }, + + /** + * @return {string} + */ + ERROR_ARG_INVALID_VALUE(name, expected, got) { + return `Invalid value for argument '${name}' expected value '${expected}' but got '${got}'.`; + }, + + /** + * @return {string} + */ + ERROR_PROTECTED_PROP(name) { + return `Property '${name}' is protected and can not be overridden by extendApp.`; + }, + + /** + * @return {string} + * @param namespace + * @param nativeModule + */ + ERROR_MISSING_MODULE(namespace, nativeModule) { + const snippet = `firebase.${namespace}()`; + if (Platform.OS === 'ios') { + return `You attempted to use a firebase module that's not installed natively on your iOS project by calling ${snippet}.` + + '\r\n\r\nEnsure you have the required Firebase iOS SDK pod for this module included in your Podfile, in this instance ' + + `confirm you've added "pod '${NAMESPACE_PODS[namespace]}'" to your Podfile` + + '\r\n\r\nSee http://invertase.link/ios for full setup instructions.'; + } + + const fbSDKDep = `'com.google.firebase:firebase-${GRADLE_DEPS[namespace] || namespace}'`; + const rnFirebasePackage = `'io.invertase.firebase.${namespace}.${nativeModule}Package'`; + const newInstance = `'new ${nativeModule}Package()'`; + return `You attempted to use a firebase module that's not installed on your Android project by calling ${snippet}.` + + `\r\n\r\nEnsure you have:\r\n\r\n1) Installed the required Firebase Android SDK dependency ${fbSDKDep} in your 'android/app/build.gradle' ` + + `file.\r\n\r\n2) Imported the ${rnFirebasePackage} module in your 'MainApplication.java' file.\r\n\r\n3) Added the ` + + `${newInstance} line inside of the RN 'getPackages()' method list.` + + '\r\n\r\nSee http://invertase.link/android for full setup instructions.'; + }, + + /** + * @return {string} + */ + ERROR_APP_NOT_INIT(appName) { + return `The [${appName}] firebase app has not been initialized!`; + }, + + /** + * @param optName + * @return {string} + * @constructor + */ + ERROR_MISSING_OPT(optName) { + return `Failed to initialize app. FirebaseOptions missing or invalid '${optName}' property.`; + }, + + /** + * @return {string} + */ + ERROR_NOT_APP(namespace) { + return `Invalid FirebaseApp instance passed to firebase.${namespace}(app <--).`; + }, + + /** + * @return {string} + */ + ERROR_UNSUPPORTED_CLASS_METHOD(className, method) { + return `${className}.${method}() is unsupported by the native Firebase SDKs.`; + }, + + /** + * @return {string} + */ + ERROR_UNSUPPORTED_CLASS_PROPERTY(className, property) { + return `${className}.${property} is unsupported by the native Firebase SDKs.`; + }, + + /** + * @return {string} + */ + ERROR_UNSUPPORTED_MODULE_METHOD(module, method) { + return `firebase.${module._NAMESPACE}().${method}() is unsupported by the native Firebase SDKs.`; + }, + + + /** + * @return {string} + */ + ERROR_PLAY_SERVICES(statusCode) { + const knownError = PLAY_SERVICES_CODES[statusCode]; + let start = 'Google Play Services is required to run firebase services on android but a valid installation was not found on this device.'; + + if (statusCode === 2) { + start = 'Google Play Services is out of date and may cause some firebase services like authentication to hang when used. It is recommended that you update it.'; + } + + // eslint-disable-next-line prefer-template + return `${start}\r\n\r\n` + + '-------------------------\r\n' + + (knownError ? + `${knownError.code}: ${knownError.message} (code ${statusCode})` : + `A specific play store availability reason reason was not available (unknown code: ${statusCode || null})` + ) + + '\r\n-------------------------' + + '\r\n\r\n' + + 'For more information on how to resolve this issue, configure Play Services checks or for guides on how to validate Play Services on your users devices see the link below:' + + '\r\n\r\nhttp://invertase.link/play-services'; + }, + + + DEFAULT_APP_NAME, + }, + + + SharedEventEmitter: new EventEmitter(), + SyncTree: NativeModules.RNFirebaseDatabase ? new SyncTree(NativeModules.RNFirebaseDatabase) : null, + + // internal utils + deleteApp(name: String) { + const app = this.APPS[name]; + if (!app) return Promise.resolve(); + + // https://firebase.google.com/docs/reference/js/firebase.app.App#delete + return app.delete().then(() => { + delete this.APPS[name]; + return true; + }); + }, +}; diff --git a/tests-new/firebase/modules/admob/AdMobComponent.js b/tests-new/firebase/modules/admob/AdMobComponent.js new file mode 100644 index 00000000..c39b4c07 --- /dev/null +++ b/tests-new/firebase/modules/admob/AdMobComponent.js @@ -0,0 +1,100 @@ +import React from 'react'; +import { ViewPropTypes, requireNativeComponent } from 'react-native'; +import PropTypes from 'prop-types'; +import EventTypes, { NativeExpressEventTypes } from './EventTypes'; +import { nativeToJSError } from '../../utils'; + +import AdRequest from './AdRequest'; +import VideoOptions from './VideoOptions'; + +const adMobPropTypes = { + ...ViewPropTypes, + size: PropTypes.string.isRequired, + unitId: PropTypes.string.isRequired, + /* eslint-disable react/forbid-prop-types */ + request: PropTypes.object, + video: PropTypes.object, + /* eslint-enable react/forbid-prop-types */ +}; +Object.keys(EventTypes).forEach(eventType => { + adMobPropTypes[eventType] = PropTypes.func; +}); +Object.keys(NativeExpressEventTypes).forEach(eventType => { + adMobPropTypes[eventType] = PropTypes.func; +}); + +const nativeComponents = {}; + +function getNativeComponent(name) { + if (nativeComponents[name]) return nativeComponents[name]; + const component = requireNativeComponent(name, AdMobComponent, { + nativeOnly: { + onBannerEvent: true, + }, + }); + nativeComponents[name] = component; + return component; +} + +class AdMobComponent extends React.Component { + static propTypes = adMobPropTypes; + + static defaultProps = { + request: new AdRequest().addTestDevice().build(), + video: new VideoOptions().build(), + }; + + constructor(props) { + super(props); + this.state = { + width: 0, + height: 0, + }; + + this.nativeView = getNativeComponent(props.class); + } + + /** + * Handle a single banner event and pass to + * any props watching it + * @param nativeEvent + */ + onBannerEvent = ({ nativeEvent }) => { + if (this.props[nativeEvent.type]) { + if (nativeEvent.type === 'onAdFailedToLoad') { + const { code, message } = nativeEvent.payload; + this.props[nativeEvent.type](nativeToJSError(code, message)); + } else { + this.props[nativeEvent.type](nativeEvent.payload || {}); + } + } + + if (nativeEvent.type === 'onSizeChange') + this.updateSize(nativeEvent.payload); + }; + + /** + * Set the JS size of the loaded banner + * @param width + * @param height + */ + updateSize = ({ width, height }) => { + this.setState({ width, height }); + }; + + /** + * Render the native component + * @returns {XML} + */ + render() { + return ( + + ); + } +} + +export default AdMobComponent; diff --git a/tests-new/firebase/modules/admob/AdRequest.js b/tests-new/firebase/modules/admob/AdRequest.js new file mode 100644 index 00000000..41d2d5d6 --- /dev/null +++ b/tests-new/firebase/modules/admob/AdRequest.js @@ -0,0 +1,58 @@ +export default class AdRequest { + constructor() { + this._props = { + keywords: [], + testDevices: [], + }; + } + + build() { + return this._props; + } + + addTestDevice(deviceId?: string) { + this._props.testDevices.push(deviceId || 'DEVICE_ID_EMULATOR'); + return this; + } + + addKeyword(keyword: string) { + this._props.keywords.push(keyword); + return this; + } + + setBirthday() { + // TODO + } + + setContentUrl(url: string) { + this._props.contentUrl = url; + return this; + } + + setGender(gender: 'male | female | unknown') { + const genders = ['male', 'female', 'unknown']; + if (genders.includes(gender)) { + this._props.gender = gender; + } + return this; + } + + setLocation() { + // TODO + } + + setRequestAgent(requestAgent: string) { + this._props.requestAgent = requestAgent; + return this; + } + + setIsDesignedForFamilies(isDesignedForFamilies: boolean) { + this._props.isDesignedForFamilies = isDesignedForFamilies; + return this; + } + + tagForChildDirectedTreatment(tagForChildDirectedTreatment: boolean) { + this._props.tagForChildDirectedTreatment = tagForChildDirectedTreatment; + return this; + } +} diff --git a/tests-new/firebase/modules/admob/Banner.js b/tests-new/firebase/modules/admob/Banner.js new file mode 100644 index 00000000..82eac4e8 --- /dev/null +++ b/tests-new/firebase/modules/admob/Banner.js @@ -0,0 +1,14 @@ +import React from 'react'; +import AdMobComponent from './AdMobComponent'; + +function Banner({ ...props }) { + return ; +} + +Banner.propTypes = AdMobComponent.propTypes; + +Banner.defaultProps = { + size: 'SMART_BANNER', +}; + +export default Banner; diff --git a/tests-new/firebase/modules/admob/EventTypes.js b/tests-new/firebase/modules/admob/EventTypes.js new file mode 100644 index 00000000..510f62e3 --- /dev/null +++ b/tests-new/firebase/modules/admob/EventTypes.js @@ -0,0 +1,23 @@ +/** + * @flow + */ +export default { + onAdLoaded: 'onAdLoaded', + onAdOpened: 'onAdOpened', + onAdLeftApplication: 'onAdLeftApplication', + onAdClosed: 'onAdClosed', + onAdFailedToLoad: 'onAdFailedToLoad', +}; + +export const NativeExpressEventTypes = { + onVideoEnd: 'onVideoEnd', + onVideoMute: 'onVideoMute', + onVideoPause: 'onVideoPause', + onVideoPlay: 'onVideoPlay', + onVideoStart: 'onVideoStart', +}; + +export const RewardedVideoEventTypes = { + onRewarded: 'onRewarded', + onRewardedVideoStarted: 'onRewardedVideoStarted', +}; diff --git a/tests-new/firebase/modules/admob/Interstitial.js b/tests-new/firebase/modules/admob/Interstitial.js new file mode 100644 index 00000000..a2f4c821 --- /dev/null +++ b/tests-new/firebase/modules/admob/Interstitial.js @@ -0,0 +1,119 @@ +import { Platform } from 'react-native'; +import { statics } from './'; +import AdRequest from './AdRequest'; +import { SharedEventEmitter } from '../../utils/events'; +import { getNativeModule } from '../../utils/native'; +import { nativeToJSError } from '../../utils'; +import type AdMob from './'; + +let subscriptions = []; + +export default class Interstitial { + _admob: AdMob; + + constructor(admob: AdMob, adUnit: string) { + // Interstitials on iOS require a new instance each time + if (Platform.OS === 'ios') { + getNativeModule(admob).clearInterstitial(adUnit); + } + + for (let i = 0, len = subscriptions.length; i < len; i++) { + subscriptions[i].remove(); + } + subscriptions = []; + + this._admob = admob; + this.adUnit = adUnit; + this.loaded = false; + SharedEventEmitter.removeAllListeners(`interstitial_${adUnit}`); + SharedEventEmitter.addListener( + `interstitial_${adUnit}`, + this._onInterstitialEvent + ); + } + + /** + * Handle a JS emit event + * @param event + * @private + */ + _onInterstitialEvent = event => { + const eventType = `interstitial:${this.adUnit}:${event.type}`; + + let emitData = Object.assign({}, event); + + switch (event.type) { + case 'onAdLoaded': + this.loaded = true; + break; + case 'onAdFailedToLoad': + emitData = nativeToJSError(event.payload.code, event.payload.message); + emitData.type = event.type; + break; + default: + } + + SharedEventEmitter.emit(eventType, emitData); + SharedEventEmitter.emit(`interstitial:${this.adUnit}:*`, emitData); + }; + + /** + * Load an ad with an instance of AdRequest + * @param request + * @returns {*} + */ + loadAd(request?: AdRequest) { + let adRequest = request; + + if (!adRequest || !Object.keys(adRequest)) { + adRequest = new AdRequest().addTestDevice().build(); + } + + return getNativeModule(this._admob).interstitialLoadAd( + this.adUnit, + adRequest + ); + } + + /** + * Return a local instance of isLoaded + * @returns {boolean} + */ + isLoaded() { + return this.loaded; + } + + /** + * Show the advert - will only show if loaded + * @returns {*} + */ + show() { + if (this.loaded) { + getNativeModule(this._admob).interstitialShowAd(this.adUnit); + } + } + + /** + * Listen to an Ad event + * @param eventType + * @param listenerCb + * @returns {null} + */ + on(eventType, listenerCb) { + if (!statics.EventTypes[eventType]) { + console.warn( + `Invalid event type provided, must be one of: ${Object.keys( + statics.EventTypes + ).join(', ')}` + ); + return null; + } + + const sub = SharedEventEmitter.addListener( + `interstitial:${this.adUnit}:${eventType}`, + listenerCb + ); + subscriptions.push(sub); + return sub; + } +} diff --git a/tests-new/firebase/modules/admob/NativeExpress.js b/tests-new/firebase/modules/admob/NativeExpress.js new file mode 100644 index 00000000..1dd57232 --- /dev/null +++ b/tests-new/firebase/modules/admob/NativeExpress.js @@ -0,0 +1,14 @@ +import React from 'react'; +import AdMobComponent from './AdMobComponent'; + +function NativeExpress({ ...props }) { + return ; +} + +NativeExpress.propTypes = AdMobComponent.propTypes; + +NativeExpress.defaultProps = { + size: 'SMART_BANNER', +}; + +export default NativeExpress; diff --git a/tests-new/firebase/modules/admob/RewardedVideo.js b/tests-new/firebase/modules/admob/RewardedVideo.js new file mode 100644 index 00000000..f5f6e0db --- /dev/null +++ b/tests-new/firebase/modules/admob/RewardedVideo.js @@ -0,0 +1,118 @@ +import { statics } from './'; +import AdRequest from './AdRequest'; +import { SharedEventEmitter } from '../../utils/events'; +import { getNativeModule } from '../../utils/native'; +import { nativeToJSError } from '../../utils'; +import type AdMob from './'; + +let subscriptions = []; + +export default class RewardedVideo { + _admob: AdMob; + + constructor(admob: AdMob, adUnit: string) { + for (let i = 0, len = subscriptions.length; i < len; i++) { + subscriptions[i].remove(); + } + subscriptions = []; + + this._admob = admob; + this.adUnit = adUnit; + this.loaded = false; + SharedEventEmitter.removeAllListeners(`rewarded_video_${adUnit}`); + SharedEventEmitter.addListener( + `rewarded_video_${adUnit}`, + this._onRewardedVideoEvent + ); + } + + /** + * Handle a JS emit event + * @param event + * @private + */ + _onRewardedVideoEvent = event => { + const eventType = `rewarded_video:${this.adUnit}:${event.type}`; + + let emitData = Object.assign({}, event); + + switch (event.type) { + case 'onAdLoaded': + this.loaded = true; + break; + case 'onAdFailedToLoad': + emitData = nativeToJSError(event.payload.code, event.payload.message); + emitData.type = event.type; + break; + default: + } + + SharedEventEmitter.emit(eventType, emitData); + SharedEventEmitter.emit(`rewarded_video:${this.adUnit}:*`, emitData); + }; + + /** + * Load an ad with an instance of AdRequest + * @param request + * @returns {*} + */ + loadAd(request?: AdRequest) { + let adRequest = request; + + if (!adRequest || !Object.keys(adRequest)) { + adRequest = new AdRequest().addTestDevice().build(); + } + + return getNativeModule(this._admob).rewardedVideoLoadAd( + this.adUnit, + adRequest + ); + } + + /** + * Return a local instance of isLoaded + * @returns {boolean} + */ + isLoaded() { + return this.loaded; + } + + /** + * Show the advert - will only show if loaded + * @returns {*} + */ + show() { + if (this.loaded) { + getNativeModule(this._admob).rewardedVideoShowAd(this.adUnit); + } + } + + /** + * Listen to an Ad event + * @param eventType + * @param listenerCb + * @returns {null} + */ + on(eventType, listenerCb) { + const types = { + ...statics.EventTypes, + ...statics.RewardedVideoEventTypes, + }; + + if (!types[eventType]) { + console.warn( + `Invalid event type provided, must be one of: ${Object.keys(types).join( + ', ' + )}` + ); + return null; + } + + const sub = SharedEventEmitter.addListener( + `rewarded_video:${this.adUnit}:${eventType}`, + listenerCb + ); + subscriptions.push(sub); + return sub; + } +} diff --git a/tests-new/firebase/modules/admob/VideoOptions.js b/tests-new/firebase/modules/admob/VideoOptions.js new file mode 100644 index 00000000..ab8a4777 --- /dev/null +++ b/tests-new/firebase/modules/admob/VideoOptions.js @@ -0,0 +1,16 @@ +export default class VideoOptions { + constructor() { + this._props = { + startMuted: true, + }; + } + + build() { + return this._props; + } + + setStartMuted(muted: boolean = true) { + this._props.startMuted = muted; + return this; + } +} diff --git a/tests-new/firebase/modules/admob/index.js b/tests-new/firebase/modules/admob/index.js new file mode 100644 index 00000000..063e42ea --- /dev/null +++ b/tests-new/firebase/modules/admob/index.js @@ -0,0 +1,121 @@ +/** + * @flow + * AdMob representation wrapper + */ +import { SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import { getNativeModule } from '../../utils/native'; +import ModuleBase from '../../utils/ModuleBase'; + +import Interstitial from './Interstitial'; +import RewardedVideo from './RewardedVideo'; +import AdRequest from './AdRequest'; +import VideoOptions from './VideoOptions'; +import Banner from './Banner'; +import NativeExpress from './NativeExpress'; + +import EventTypes, { + NativeExpressEventTypes, + RewardedVideoEventTypes, +} from './EventTypes'; + +import type App from '../core/app'; + +type NativeEvent = { + adUnit: string, + payload: Object, + type: string, +}; + +const NATIVE_EVENTS = ['interstitial_event', 'rewarded_video_event']; + +export const MODULE_NAME = 'RNFirebaseAdMob'; +export const NAMESPACE = 'admob'; + +export default class AdMob extends ModuleBase { + _appId: ?string; + _initialized: boolean; + + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + + this._initialized = false; + this._appId = null; + + SharedEventEmitter.addListener( + 'interstitial_event', + this._onInterstitialEvent.bind(this) + ); + SharedEventEmitter.addListener( + 'rewarded_video_event', + this._onRewardedVideoEvent.bind(this) + ); + } + + _onInterstitialEvent(event: NativeEvent): void { + const { adUnit } = event; + const jsEventType = `interstitial_${adUnit}`; + + if (SharedEventEmitter.listeners(jsEventType).length === 0) { + // TODO + } + + SharedEventEmitter.emit(jsEventType, event); + } + + _onRewardedVideoEvent(event: NativeEvent): void { + const { adUnit } = event; + const jsEventType = `rewarded_video_${adUnit}`; + + if (SharedEventEmitter.listeners(jsEventType).length === 0) { + // TODO + } + + SharedEventEmitter.emit(jsEventType, event); + } + + initialize(appId: string): void { + if (this._initialized) { + getLogger(this).warn('AdMob has already been initialized!'); + } else { + this._initialized = true; + this._appId = appId; + getNativeModule(this).initialize(appId); + } + } + + openDebugMenu(): void { + if (!this._initialized) { + getLogger(this).warn( + 'AdMob needs to be initialized before opening the dev menu!' + ); + } else { + getLogger(this).info('Opening debug menu'); + getNativeModule(this).openDebugMenu(this._appId); + } + } + + interstitial(adUnit: string): Interstitial { + return new Interstitial(this, adUnit); + } + + rewarded(adUnit: string): RewardedVideo { + return new RewardedVideo(this, adUnit); + } +} + +export const statics = { + Banner, + NativeExpress, + AdRequest, + VideoOptions, + EventTypes, + RewardedVideoEventTypes, + NativeExpressEventTypes, +}; diff --git a/tests-new/firebase/modules/analytics/index.js b/tests-new/firebase/modules/analytics/index.js new file mode 100644 index 00000000..7e1af364 --- /dev/null +++ b/tests-new/firebase/modules/analytics/index.js @@ -0,0 +1,167 @@ +/** + * @flow + * Analytics representation wrapper + */ +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; +import { isString, isObject } from '../../utils'; + +import type App from '../core/app'; + +const AlphaNumericUnderscore = /^[a-zA-Z0-9_]+$/; + +const ReservedEventNames = [ + 'app_clear_data', + 'app_uninstall', + 'app_update', + 'error', + 'first_open', + 'in_app_purchase', + 'notification_dismiss', + 'notification_foreground', + 'notification_open', + 'notification_receive', + 'os_update', + 'session_start', + 'user_engagement', +]; + +export const MODULE_NAME = 'RNFirebaseAnalytics'; +export const NAMESPACE = 'analytics'; + +export default class Analytics extends ModuleBase { + constructor(app: App) { + super(app, { + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + } + + /** + * Logs an app event. + * @param {string} name + * @param params + * @return {Promise} + */ + logEvent(name: string, params: Object = {}): void { + if (!isString(name)) { + throw new Error( + `analytics.logEvent(): First argument 'name' is required and must be a string value.` + ); + } + + if (typeof params !== 'undefined' && !isObject(params)) { + throw new Error( + `analytics.logEvent(): Second optional argument 'params' must be an object if provided.` + ); + } + + // check name is not a reserved event name + if (ReservedEventNames.includes(name)) { + throw new Error( + `analytics.logEvent(): event name '${name}' is a reserved event name and can not be used.` + ); + } + + // name format validation + if (!AlphaNumericUnderscore.test(name)) { + throw new Error( + `analytics.logEvent(): Event name '${name}' is invalid. Names should contain 1 to 32 alphanumeric characters or underscores.` + ); + } + + // maximum number of allowed params check + if (params && Object.keys(params).length > 25) + throw new Error( + 'analytics.logEvent(): Maximum number of parameters exceeded (25).' + ); + + // Parameter names can be up to 24 characters long and must start with an alphabetic character + // and contain only alphanumeric characters and underscores. Only String, long and double param + // types are supported. String parameter values can be up to 36 characters long. The "firebase_" + // prefix is reserved and should not be used for parameter names. + + getNativeModule(this).logEvent(name, params); + } + + /** + * Sets whether analytics collection is enabled for this app on this device. + * @param enabled + */ + setAnalyticsCollectionEnabled(enabled: boolean): void { + getNativeModule(this).setAnalyticsCollectionEnabled(enabled); + } + + /** + * Sets the current screen name, which specifies the current visual context in your app. + * @param screenName + * @param screenClassOverride + */ + setCurrentScreen(screenName: string, screenClassOverride: string): void { + getNativeModule(this).setCurrentScreen(screenName, screenClassOverride); + } + + /** + * Sets the minimum engagement time required before starting a session. The default value is 10000 (10 seconds). + * @param milliseconds + */ + setMinimumSessionDuration(milliseconds: number = 10000): void { + getNativeModule(this).setMinimumSessionDuration(milliseconds); + } + + /** + * Sets the duration of inactivity that terminates the current session. The default value is 1800000 (30 minutes). + * @param milliseconds + */ + setSessionTimeoutDuration(milliseconds: number = 1800000): void { + getNativeModule(this).setSessionTimeoutDuration(milliseconds); + } + + /** + * Sets the user ID property. + * @param id + */ + setUserId(id: string | null): void { + if (id !== null && !isString(id)) { + throw new Error( + 'analytics.setUserId(): The supplied userId must be a string value or null.' + ); + } + getNativeModule(this).setUserId(id); + } + + /** + * Sets a user property to a given value. + * @param name + * @param value + */ + setUserProperty(name: string, value: string | null): void { + if (value !== null && !isString(value)) { + throw new Error( + 'analytics.setUserProperty(): The supplied property must be a string value or null.' + ); + } + getNativeModule(this).setUserProperty(name, value); + } + + /** + * Sets multiple user properties to the supplied values. + * @RNFirebaseSpecific + * @param object + */ + setUserProperties(object: Object): void { + Object.keys(object).forEach(property => { + const value = object[property]; + if (value !== null && !isString(value)) { + throw new Error( + `analytics.setUserProperties(): The property with name '${property}' must be a string value or null.` + ); + } + getNativeModule(this).setUserProperty(property, object[property]); + }); + } +} + +export const statics = {}; diff --git a/tests-new/firebase/modules/auth/ConfirmationResult.js b/tests-new/firebase/modules/auth/ConfirmationResult.js new file mode 100644 index 00000000..fd76e69a --- /dev/null +++ b/tests-new/firebase/modules/auth/ConfirmationResult.js @@ -0,0 +1,37 @@ +/** + * @flow + * ConfirmationResult representation wrapper + */ +import { getNativeModule } from '../../utils/native'; +import type Auth from './'; +import type User from './User'; + +export default class ConfirmationResult { + _auth: Auth; + _verificationId: string; + + /** + * + * @param auth + * @param verificationId The phone number authentication operation's verification ID. + */ + constructor(auth: Auth, verificationId: string) { + this._auth = auth; + this._verificationId = verificationId; + } + + /** + * + * @param verificationCode + * @return {*} + */ + confirm(verificationCode: string): Promise { + return getNativeModule(this._auth) + ._confirmVerificationCode(verificationCode) + .then(user => this._auth._setUser(user)); + } + + get verificationId(): string | null { + return this._verificationId; + } +} diff --git a/tests-new/firebase/modules/auth/PhoneAuthListener.js b/tests-new/firebase/modules/auth/PhoneAuthListener.js new file mode 100644 index 00000000..d85bcace --- /dev/null +++ b/tests-new/firebase/modules/auth/PhoneAuthListener.js @@ -0,0 +1,347 @@ +// @flow +import INTERNALS from '../../utils/internals'; +import { SharedEventEmitter } from '../../utils/events'; +import { + generatePushID, + isFunction, + isAndroid, + isIOS, + isString, + nativeToJSError, +} from '../../utils'; +import { getNativeModule } from '../../utils/native'; + +import type Auth from './'; + +type PhoneAuthSnapshot = { + state: 'sent' | 'timeout' | 'verified' | 'error', + verificationId: string, + code: string | null, + error: Error | null, +}; + +type PhoneAuthError = { + code: string | null, + verificationId: string, + message: string | null, + stack: string | null, +}; + +export default class PhoneAuthListener { + _auth: Auth; + _timeout: number; + _publicEvents: Object; + _internalEvents: Object; + _reject: Function | null; + _resolve: Function | null; + _credential: Object | null; + _promise: Promise<*> | null; + _phoneAuthRequestKey: string; + + /** + * + * @param auth + * @param phoneNumber + * @param timeout + */ + constructor(auth: Auth, phoneNumber: string, timeout?: number) { + this._auth = auth; + this._reject = null; + this._resolve = null; + this._promise = null; + this._credential = null; + + this._timeout = timeout || 20; // 20 secs + this._phoneAuthRequestKey = generatePushID(); + + // internal events + this._internalEvents = { + codeSent: `phone:auth:${this._phoneAuthRequestKey}:onCodeSent`, + verificationFailed: `phone:auth:${ + this._phoneAuthRequestKey + }:onVerificationFailed`, + verificationComplete: `phone:auth:${ + this._phoneAuthRequestKey + }:onVerificationComplete`, + codeAutoRetrievalTimeout: `phone:auth:${ + this._phoneAuthRequestKey + }:onCodeAutoRetrievalTimeout`, + }; + + // user observer events + this._publicEvents = { + // error cb + error: `phone:auth:${this._phoneAuthRequestKey}:error`, + // observer + event: `phone:auth:${this._phoneAuthRequestKey}:event`, + // success cb + success: `phone:auth:${this._phoneAuthRequestKey}:success`, + }; + + // setup internal event listeners + this._subscribeToEvents(); + + // start verification flow natively + if (isAndroid) { + getNativeModule(this._auth).verifyPhoneNumber( + phoneNumber, + this._phoneAuthRequestKey, + this._timeout + ); + } + + if (isIOS) { + getNativeModule(this._auth).verifyPhoneNumber( + phoneNumber, + this._phoneAuthRequestKey + ); + } + } + + /** + * Subscribes to all EE events on this._internalEvents + * @private + */ + _subscribeToEvents() { + const events = Object.keys(this._internalEvents); + + for (let i = 0, len = events.length; i < len; i++) { + const type = events[i]; + SharedEventEmitter.once( + this._internalEvents[type], + // $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323 + this[`_${type}Handler`].bind(this) + ); + } + } + + /** + * Subscribe a users listener cb to the snapshot events. + * @param observer + * @private + */ + _addUserObserver(observer) { + SharedEventEmitter.addListener(this._publicEvents.event, observer); + } + + /** + * Send a snapshot event to users event observer. + * @param snapshot PhoneAuthSnapshot + * @private + */ + _emitToObservers(snapshot: PhoneAuthSnapshot) { + SharedEventEmitter.emit(this._publicEvents.event, snapshot); + } + + /** + * Send a error snapshot event to any subscribed errorCb's + * @param snapshot + * @private + */ + _emitToErrorCb(snapshot) { + const { error } = snapshot; + if (this._reject) this._reject(error); + SharedEventEmitter.emit(this._publicEvents.error, error); + } + + /** + * Send a success snapshot event to any subscribed completeCb's + * @param snapshot + * @private + */ + _emitToSuccessCb(snapshot) { + if (this._resolve) this._resolve(snapshot); + SharedEventEmitter.emit(this._publicEvents.success, snapshot); + } + + /** + * Removes all listeners for this phone auth instance + * @private + */ + _removeAllListeners() { + setTimeout(() => { + // move to next event loop - not sure if needed + // internal listeners + Object.values(this._internalEvents).forEach(event => { + SharedEventEmitter.removeAllListeners(event); + }); + + // user observer listeners + Object.values(this._publicEvents).forEach(publicEvent => { + SharedEventEmitter.removeAllListeners(publicEvent); + }); + }, 0); + } + + /** + * Create a new internal deferred promise, if not already created + * @private + */ + _promiseDeferred() { + if (!this._promise) { + this._promise = new Promise((resolve, reject) => { + this._resolve = result => { + this._resolve = null; + return resolve(result); + }; + + this._reject = possibleError => { + this._reject = null; + return reject(possibleError); + }; + }); + } + } + + /* -------------------------- + --- INTERNAL EVENT HANDLERS + ---------------------------- */ + + /** + * Internal code sent event handler + * @private + * @param credential + */ + _codeSentHandler(credential) { + const snapshot: PhoneAuthSnapshot = { + verificationId: credential.verificationId, + code: null, + error: null, + state: 'sent', + }; + + this._emitToObservers(snapshot); + + if (isIOS) { + this._emitToSuccessCb(snapshot); + } + + if (isAndroid) { + // android can auto retrieve so we don't emit to successCb immediately, + // if auto retrieve times out then that will emit to successCb + } + } + + /** + * Internal code auto retrieve timeout event handler + * @private + * @param credential + */ + _codeAutoRetrievalTimeoutHandler(credential) { + const snapshot: PhoneAuthSnapshot = { + verificationId: credential.verificationId, + code: null, + error: null, + state: 'timeout', + }; + + this._emitToObservers(snapshot); + this._emitToSuccessCb(snapshot); + } + + /** + * Internal verification complete event handler + * @param credential + * @private + */ + _verificationCompleteHandler(credential) { + const snapshot: PhoneAuthSnapshot = { + verificationId: credential.verificationId, + code: credential.code || null, + error: null, + state: 'verified', + }; + + this._emitToObservers(snapshot); + this._emitToSuccessCb(snapshot); + this._removeAllListeners(); + } + + /** + * Internal verification failed event handler + * @param state + * @private + */ + _verificationFailedHandler(state) { + const snapshot: PhoneAuthSnapshot = { + verificationId: state.verificationId, + code: null, + error: null, + state: 'error', + }; + + const { code, message, nativeErrorMessage } = state.error; + snapshot.error = nativeToJSError(code, message, { nativeErrorMessage }); + + this._emitToObservers(snapshot); + this._emitToErrorCb(snapshot); + this._removeAllListeners(); + } + + /* ------------- + -- PUBLIC API + --------------*/ + + on( + event: string, + observer: () => PhoneAuthSnapshot, + errorCb?: () => PhoneAuthError, + successCb?: () => PhoneAuthSnapshot + ): this { + if (!isString(event)) { + throw new Error( + INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('event', 'string', 'on') + ); + } + + if (event !== 'state_changed') { + throw new Error( + INTERNALS.STRINGS.ERROR_ARG_INVALID_VALUE( + 'event', + 'state_changed', + event + ) + ); + } + + if (!isFunction(observer)) { + throw new Error( + INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('observer', 'function', 'on') + ); + } + + this._addUserObserver(observer); + + if (isFunction(errorCb)) { + SharedEventEmitter.once(this._publicEvents.error, errorCb); + } + + if (isFunction(successCb)) { + SharedEventEmitter.once(this._publicEvents.success, successCb); + } + + return this; + } + + /** + * Promise .then proxy + * @param fn + */ + then(fn: () => PhoneAuthSnapshot) { + this._promiseDeferred(); + // $FlowFixMe: Unsure how to annotate `bind` here + if (this._promise) return this._promise.then.bind(this._promise)(fn); + return undefined; // will never get here - just to keep flow happy + } + + /** + * Promise .catch proxy + * @param fn + */ + catch(fn: () => Error) { + this._promiseDeferred(); + // $FlowFixMe: Unsure how to annotate `bind` here + if (this._promise) return this._promise.catch.bind(this._promise)(fn); + return undefined; // will never get here - just to keep flow happy + } +} diff --git a/tests-new/firebase/modules/auth/User.js b/tests-new/firebase/modules/auth/User.js new file mode 100644 index 00000000..e509bb94 --- /dev/null +++ b/tests-new/firebase/modules/auth/User.js @@ -0,0 +1,334 @@ +/** + * @flow + * User representation wrapper + */ +import INTERNALS from '../../utils/internals'; +import { getNativeModule } from '../../utils/native'; + +import type Auth from './'; +import type { + ActionCodeSettings, + AuthCredential, + NativeUser, + UserCredential, + UserInfo, + UserMetadata, +} from './types'; + +type UpdateProfile = { + displayName?: string, + photoURL?: string, +}; + +export default class User { + _auth: Auth; + _user: NativeUser; + + /** + * + * @param auth Instance of Authentication class + * @param user user result object from native + */ + constructor(auth: Auth, user: NativeUser) { + this._auth = auth; + this._user = user; + } + + /** + * PROPERTIES + */ + + get displayName(): ?string { + return this._user.displayName || null; + } + + get email(): ?string { + return this._user.email || null; + } + + get emailVerified(): boolean { + return this._user.emailVerified || false; + } + + get isAnonymous(): boolean { + return this._user.isAnonymous || false; + } + + get metadata(): UserMetadata { + return this._user.metadata; + } + + get phoneNumber(): ?string { + return this._user.phoneNumber || null; + } + + get photoURL(): ?string { + return this._user.photoURL || null; + } + + get providerData(): Array { + return this._user.providerData; + } + + get providerId(): string { + return this._user.providerId; + } + + get uid(): string { + return this._user.uid; + } + + /** + * METHODS + */ + + /** + * Delete the current user + * @return {Promise} + */ + delete(): Promise { + return getNativeModule(this._auth) + .delete() + .then(() => { + this._auth._setUser(); + }); + } + + /** + * get the token of current user + * @return {Promise} + */ + getIdToken(forceRefresh: boolean = false): Promise { + return getNativeModule(this._auth).getToken(forceRefresh); + } + + /** + * get the token of current user + * @deprecated Deprecated getToken in favor of getIdToken. + * @return {Promise} + */ + getToken(forceRefresh: boolean = false): Promise { + console.warn( + 'Deprecated firebase.User.prototype.getToken in favor of firebase.User.prototype.getIdToken.' + ); + return getNativeModule(this._auth).getToken(forceRefresh); + } + + /** + * @deprecated Deprecated linkWithCredential in favor of linkAndRetrieveDataWithCredential. + * @param credential + */ + linkWithCredential(credential: AuthCredential): Promise { + console.warn( + 'Deprecated firebase.User.prototype.linkWithCredential in favor of firebase.User.prototype.linkAndRetrieveDataWithCredential.' + ); + return getNativeModule(this._auth) + .linkWithCredential( + credential.providerId, + credential.token, + credential.secret + ) + .then(user => this._auth._setUser(user)); + } + + /** + * + * @param credential + */ + linkAndRetrieveDataWithCredential( + credential: AuthCredential + ): Promise { + return getNativeModule(this._auth) + .linkAndRetrieveDataWithCredential( + credential.providerId, + credential.token, + credential.secret + ) + .then(userCredential => this._auth._setUserCredential(userCredential)); + } + + /** + * Re-authenticate a user with a third-party authentication provider + * @return {Promise} A promise resolved upon completion + */ + reauthenticateWithCredential(credential: AuthCredential): Promise { + console.warn( + 'Deprecated firebase.User.prototype.reauthenticateWithCredential in favor of firebase.User.prototype.reauthenticateAndRetrieveDataWithCredential.' + ); + return getNativeModule(this._auth) + .reauthenticateWithCredential( + credential.providerId, + credential.token, + credential.secret + ) + .then(user => { + this._auth._setUser(user); + }); + } + + /** + * Re-authenticate a user with a third-party authentication provider + * @return {Promise} A promise resolved upon completion + */ + reauthenticateAndRetrieveDataWithCredential( + credential: AuthCredential + ): Promise { + return getNativeModule(this._auth) + .reauthenticateAndRetrieveDataWithCredential( + credential.providerId, + credential.token, + credential.secret + ) + .then(userCredential => this._auth._setUserCredential(userCredential)); + } + + /** + * Reload the current user + * @return {Promise} + */ + reload(): Promise { + return getNativeModule(this._auth) + .reload() + .then(user => { + this._auth._setUser(user); + }); + } + + /** + * Send verification email to current user. + */ + sendEmailVerification( + actionCodeSettings?: ActionCodeSettings + ): Promise { + return getNativeModule(this._auth) + .sendEmailVerification(actionCodeSettings) + .then(user => { + this._auth._setUser(user); + }); + } + + toJSON(): Object { + return Object.assign({}, this._user); + } + + /** + * + * @param providerId + * @return {Promise.|*} + */ + unlink(providerId: string): Promise { + return getNativeModule(this._auth) + .unlink(providerId) + .then(user => this._auth._setUser(user)); + } + + /** + * Update the current user's email + * + * @param {string} email The user's _new_ email + * @return {Promise} A promise resolved upon completion + */ + updateEmail(email: string): Promise { + return getNativeModule(this._auth) + .updateEmail(email) + .then(user => { + this._auth._setUser(user); + }); + } + + /** + * Update the current user's password + * @param {string} password the new password + * @return {Promise} + */ + updatePassword(password: string): Promise { + return getNativeModule(this._auth) + .updatePassword(password) + .then(user => { + this._auth._setUser(user); + }); + } + + /** + * Update the current user's profile + * @param {Object} updates An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) + * @return {Promise} + */ + updateProfile(updates: UpdateProfile = {}): Promise { + return getNativeModule(this._auth) + .updateProfile(updates) + .then(user => { + this._auth._setUser(user); + }); + } + + /** + * KNOWN UNSUPPORTED METHODS + */ + + linkWithPhoneNumber() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD( + 'User', + 'linkWithPhoneNumber' + ) + ); + } + + linkWithPopup() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('User', 'linkWithPopup') + ); + } + + linkWithRedirect() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD( + 'User', + 'linkWithRedirect' + ) + ); + } + + reauthenticateWithPhoneNumber() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD( + 'User', + 'reauthenticateWithPhoneNumber' + ) + ); + } + + reauthenticateWithPopup() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD( + 'User', + 'reauthenticateWithPopup' + ) + ); + } + + reauthenticateWithRedirect() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD( + 'User', + 'reauthenticateWithRedirect' + ) + ); + } + + updatePhoneNumber() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD( + 'User', + 'updatePhoneNumber' + ) + ); + } + + get refreshToken(): string { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('User', 'refreshToken') + ); + } +} diff --git a/tests-new/firebase/modules/auth/index.js b/tests-new/firebase/modules/auth/index.js new file mode 100644 index 00000000..b4b697af --- /dev/null +++ b/tests-new/firebase/modules/auth/index.js @@ -0,0 +1,526 @@ +/** + * @flow + * Auth representation wrapper + */ +import User from './User'; +import ModuleBase from '../../utils/ModuleBase'; +import { getAppEventName, SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import { getNativeModule } from '../../utils/native'; +import INTERNALS from '../../utils/internals'; +import ConfirmationResult from './phone/ConfirmationResult'; +import PhoneAuthListener from './phone/PhoneAuthListener'; + +// providers +import EmailAuthProvider from './providers/EmailAuthProvider'; +import PhoneAuthProvider from './providers/PhoneAuthProvider'; +import GoogleAuthProvider from './providers/GoogleAuthProvider'; +import GithubAuthProvider from './providers/GithubAuthProvider'; +import OAuthProvider from './providers/OAuthProvider'; +import TwitterAuthProvider from './providers/TwitterAuthProvider'; +import FacebookAuthProvider from './providers/FacebookAuthProvider'; + +import type { + ActionCodeInfo, + ActionCodeSettings, + AuthCredential, + NativeUser, + NativeUserCredential, + UserCredential, +} from './types'; +import type App from '../core/app'; + +type AuthState = { + user?: NativeUser, +}; + +const NATIVE_EVENTS = [ + 'auth_state_changed', + 'auth_id_token_changed', + 'phone_auth_state_changed', +]; + +export const MODULE_NAME = 'RNFirebaseAuth'; +export const NAMESPACE = 'auth'; + +export default class Auth extends ModuleBase { + _authResult: boolean; + _languageCode: string; + _user: User | null; + + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + moduleName: MODULE_NAME, + multiApp: true, + hasShards: false, + namespace: NAMESPACE, + }); + this._user = null; + this._authResult = false; + this._languageCode = + getNativeModule(this).APP_LANGUAGE[app._name] || + getNativeModule(this).APP_LANGUAGE['[DEFAULT]']; + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onAuthStateChanged + getAppEventName(this, 'auth_state_changed'), + (state: AuthState) => { + this._setUser(state.user); + SharedEventEmitter.emit( + getAppEventName(this, 'onAuthStateChanged'), + this._user + ); + } + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public events based on event.type + getAppEventName(this, 'phone_auth_state_changed'), + (event: Object) => { + const eventKey = `phone:auth:${event.requestKey}:${event.type}`; + SharedEventEmitter.emit(eventKey, event.state); + } + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onIdTokenChanged + getAppEventName(this, 'auth_id_token_changed'), + (auth: AuthState) => { + this._setUser(auth.user); + SharedEventEmitter.emit( + getAppEventName(this, 'onIdTokenChanged'), + this._user + ); + } + ); + + getNativeModule(this).addAuthStateListener(); + getNativeModule(this).addIdTokenListener(); + } + + _setUser(user: ?NativeUser): ?User { + this._authResult = true; + this._user = user ? new User(this, user) : null; + SharedEventEmitter.emit(getAppEventName(this, 'onUserChanged'), this._user); + return this._user; + } + + _setUserCredential(userCredential: NativeUserCredential): UserCredential { + const user = new User(this, userCredential.user); + this._authResult = true; + this._user = user; + SharedEventEmitter.emit(getAppEventName(this, 'onUserChanged'), this._user); + return { + additionalUserInfo: userCredential.additionalUserInfo, + user, + }; + } + + /* + * WEB API + */ + + /** + * Listen for auth changes. + * @param listener + */ + onAuthStateChanged(listener: Function) { + getLogger(this).info('Creating onAuthStateChanged listener'); + SharedEventEmitter.addListener( + getAppEventName(this, 'onAuthStateChanged'), + listener + ); + if (this._authResult) listener(this._user || null); + + return () => { + getLogger(this).info('Removing onAuthStateChanged listener'); + SharedEventEmitter.removeListener( + getAppEventName(this, 'onAuthStateChanged'), + listener + ); + }; + } + + /** + * Listen for id token changes. + * @param listener + */ + onIdTokenChanged(listener: Function) { + getLogger(this).info('Creating onIdTokenChanged listener'); + SharedEventEmitter.addListener( + getAppEventName(this, 'onIdTokenChanged'), + listener + ); + if (this._authResult) listener(this._user || null); + + return () => { + getLogger(this).info('Removing onIdTokenChanged listener'); + SharedEventEmitter.removeListener( + getAppEventName(this, 'onIdTokenChanged'), + listener + ); + }; + } + + /** + * Listen for user changes. + * @param listener + */ + onUserChanged(listener: Function) { + getLogger(this).info('Creating onUserChanged listener'); + SharedEventEmitter.addListener( + getAppEventName(this, 'onUserChanged'), + listener + ); + if (this._authResult) listener(this._user || null); + + return () => { + getLogger(this).info('Removing onUserChanged listener'); + SharedEventEmitter.removeListener( + getAppEventName(this, 'onUserChanged'), + listener + ); + }; + } + + /** + * Sign the current user out + * @return {Promise} + */ + signOut(): Promise { + return getNativeModule(this) + .signOut() + .then(() => { + this._setUser(); + }); + } + + /** + * Sign a user in anonymously + * @deprecated Deprecated signInAnonymously in favor of signInAnonymouslyAndRetrieveData. + * @return {Promise} A promise resolved upon completion + */ + signInAnonymously(): Promise { + console.warn( + 'Deprecated firebase.User.prototype.signInAnonymously in favor of firebase.User.prototype.signInAnonymouslyAndRetrieveData.' + ); + return getNativeModule(this) + .signInAnonymously() + .then(user => this._setUser(user)); + } + + /** + * Sign a user in anonymously + * @return {Promise} A promise resolved upon completion + */ + signInAnonymouslyAndRetrieveData(): Promise { + return getNativeModule(this) + .signInAnonymouslyAndRetrieveData() + .then(userCredential => this._setUserCredential(userCredential)); + } + + /** + * Create a user with the email/password functionality + * @deprecated Deprecated createUserWithEmailAndPassword in favor of createUserAndRetrieveDataWithEmailAndPassword. + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise indicating the completion + */ + createUserWithEmailAndPassword( + email: string, + password: string + ): Promise { + console.warn( + 'Deprecated firebase.User.prototype.createUserWithEmailAndPassword in favor of firebase.User.prototype.createUserAndRetrieveDataWithEmailAndPassword.' + ); + return getNativeModule(this) + .createUserWithEmailAndPassword(email, password) + .then(user => this._setUser(user)); + } + + /** + * Create a user with the email/password functionality + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise indicating the completion + */ + createUserAndRetrieveDataWithEmailAndPassword( + email: string, + password: string + ): Promise { + return getNativeModule(this) + .createUserAndRetrieveDataWithEmailAndPassword(email, password) + .then(userCredential => this._setUserCredential(userCredential)); + } + + /** + * Sign a user in with email/password + * @deprecated Deprecated signInWithEmailAndPassword in favor of signInAndRetrieveDataWithEmailAndPassword + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise that is resolved upon completion + */ + signInWithEmailAndPassword(email: string, password: string): Promise { + console.warn( + 'Deprecated firebase.User.prototype.signInWithEmailAndPassword in favor of firebase.User.prototype.signInAndRetrieveDataWithEmailAndPassword.' + ); + return getNativeModule(this) + .signInWithEmailAndPassword(email, password) + .then(user => this._setUser(user)); + } + + /** + * Sign a user in with email/password + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise that is resolved upon completion + */ + signInAndRetrieveDataWithEmailAndPassword( + email: string, + password: string + ): Promise { + return getNativeModule(this) + .signInAndRetrieveDataWithEmailAndPassword(email, password) + .then(userCredential => this._setUserCredential(userCredential)); + } + + /** + * Sign the user in with a custom auth token + * @deprecated Deprecated signInWithCustomToken in favor of signInAndRetrieveDataWithCustomToken + * @param {string} customToken A self-signed custom auth token. + * @return {Promise} A promise resolved upon completion + */ + signInWithCustomToken(customToken: string): Promise { + console.warn( + 'Deprecated firebase.User.prototype.signInWithCustomToken in favor of firebase.User.prototype.signInAndRetrieveDataWithCustomToken.' + ); + return getNativeModule(this) + .signInWithCustomToken(customToken) + .then(user => this._setUser(user)); + } + + /** + * Sign the user in with a custom auth token + * @param {string} customToken A self-signed custom auth token. + * @return {Promise} A promise resolved upon completion + */ + signInAndRetrieveDataWithCustomToken( + customToken: string + ): Promise { + return getNativeModule(this) + .signInAndRetrieveDataWithCustomToken(customToken) + .then(userCredential => this._setUserCredential(userCredential)); + } + + /** + * Sign the user in with a third-party authentication provider + * @deprecated Deprecated signInWithCredential in favor of signInAndRetrieveDataWithCredential. + * @return {Promise} A promise resolved upon completion + */ + signInWithCredential(credential: AuthCredential): Promise { + console.warn( + 'Deprecated firebase.User.prototype.signInWithCredential in favor of firebase.User.prototype.signInAndRetrieveDataWithCredential.' + ); + return getNativeModule(this) + .signInWithCredential( + credential.providerId, + credential.token, + credential.secret + ) + .then(user => this._setUser(user)); + } + + /** + * Sign the user in with a third-party authentication provider + * @return {Promise} A promise resolved upon completion + */ + signInAndRetrieveDataWithCredential( + credential: AuthCredential + ): Promise { + return getNativeModule(this) + .signInAndRetrieveDataWithCredential( + credential.providerId, + credential.token, + credential.secret + ) + .then(userCredential => this._setUserCredential(userCredential)); + } + + /** + * Asynchronously signs in using a phone number. + * + */ + signInWithPhoneNumber(phoneNumber: string): Promise { + return getNativeModule(this) + .signInWithPhoneNumber(phoneNumber) + .then(result => new ConfirmationResult(this, result.verificationId)); + } + + /** + * Returns a PhoneAuthListener to listen to phone verification events, + * on the final completion event a PhoneAuthCredential can be generated for + * authentication purposes. + * + * @param phoneNumber + * @param autoVerifyTimeout Android Only + * @returns {PhoneAuthListener} + */ + verifyPhoneNumber( + phoneNumber: string, + autoVerifyTimeout?: number + ): PhoneAuthListener { + return new PhoneAuthListener(this, phoneNumber, autoVerifyTimeout); + } + + /** + * Send reset password instructions via email + * @param {string} email The email to send password reset instructions + */ + sendPasswordResetEmail( + email: string, + actionCodeSettings?: ActionCodeSettings + ): Promise { + return getNativeModule(this).sendPasswordResetEmail( + email, + actionCodeSettings + ); + } + + /** + * Completes the password reset process, given a confirmation code and new password. + * + * @link https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset + * @param code + * @param newPassword + * @return {Promise.} + */ + confirmPasswordReset(code: string, newPassword: string): Promise { + return getNativeModule(this).confirmPasswordReset(code, newPassword); + } + + /** + * Applies a verification code sent to the user by email or other out-of-band mechanism. + * + * @link https://firebase.google.com/docs/reference/js/firebase.auth.Auth#applyActionCode + * @param code + * @return {Promise.} + */ + applyActionCode(code: string): Promise { + return getNativeModule(this).applyActionCode(code); + } + + /** + * Checks a verification code sent to the user by email or other out-of-band mechanism. + * + * @link https://firebase.google.com/docs/reference/js/firebase.auth.Auth#checkActionCode + * @param code + * @return {Promise.|Promise} + */ + checkActionCode(code: string): Promise { + return getNativeModule(this).checkActionCode(code); + } + + /** + * Returns a list of authentication providers that can be used to sign in a given user (identified by its main email address). + * @return {Promise} + */ + fetchProvidersForEmail(email: string): Promise { + return getNativeModule(this).fetchProvidersForEmail(email); + } + + verifyPasswordResetCode(code: string): Promise { + return getNativeModule(this).verifyPasswordResetCode(code); + } + + /** + * Sets the language for the auth module + * @param code + * @returns {*} + */ + set languageCode(code: string) { + this._languageCode = code; + getNativeModule(this).setLanguageCode(code); + } + + /** + * Get the currently signed in user + * @return {Promise} + */ + get currentUser(): User | null { + return this._user; + } + + get languageCode(): string { + return this._languageCode; + } + + /** + * KNOWN UNSUPPORTED METHODS + */ + + getRedirectResult() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'auth', + 'getRedirectResult' + ) + ); + } + + setPersistence() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'auth', + 'setPersistence' + ) + ); + } + + signInWithPopup() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'auth', + 'signInWithPopup' + ) + ); + } + + signInWithRedirect() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'auth', + 'signInWithRedirect' + ) + ); + } + + // firebase issue - https://github.com/invertase/react-native-firebase/pull/655#issuecomment-349904680 + useDeviceLanguage() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'auth', + 'useDeviceLanguage' + ) + ); + } +} + +export const statics = { + EmailAuthProvider, + PhoneAuthProvider, + GoogleAuthProvider, + GithubAuthProvider, + TwitterAuthProvider, + FacebookAuthProvider, + OAuthProvider, + PhoneAuthState: { + CODE_SENT: 'sent', + AUTO_VERIFY_TIMEOUT: 'timeout', + AUTO_VERIFIED: 'verified', + ERROR: 'error', + }, +}; diff --git a/tests-new/firebase/modules/auth/phone/ConfirmationResult.js b/tests-new/firebase/modules/auth/phone/ConfirmationResult.js new file mode 100644 index 00000000..25d701d1 --- /dev/null +++ b/tests-new/firebase/modules/auth/phone/ConfirmationResult.js @@ -0,0 +1,37 @@ +/** + * @flow + * ConfirmationResult representation wrapper + */ +import { getNativeModule } from '../../../utils/native'; +import type Auth from '../'; +import type User from '../User'; + +export default class ConfirmationResult { + _auth: Auth; + _verificationId: string; + + /** + * + * @param auth + * @param verificationId The phone number authentication operation's verification ID. + */ + constructor(auth: Auth, verificationId: string) { + this._auth = auth; + this._verificationId = verificationId; + } + + /** + * + * @param verificationCode + * @return {*} + */ + confirm(verificationCode: string): Promise { + return getNativeModule(this._auth) + ._confirmVerificationCode(verificationCode) + .then(user => this._auth._setUser(user)); + } + + get verificationId(): string | null { + return this._verificationId; + } +} diff --git a/tests-new/firebase/modules/auth/phone/PhoneAuthListener.js b/tests-new/firebase/modules/auth/phone/PhoneAuthListener.js new file mode 100644 index 00000000..68bc78fb --- /dev/null +++ b/tests-new/firebase/modules/auth/phone/PhoneAuthListener.js @@ -0,0 +1,347 @@ +// @flow +import INTERNALS from '../../../utils/internals'; +import { SharedEventEmitter } from '../../../utils/events'; +import { + generatePushID, + isFunction, + isAndroid, + isIOS, + isString, + nativeToJSError, +} from '../../../utils'; +import { getNativeModule } from '../../../utils/native'; + +import type Auth from '../'; + +type PhoneAuthSnapshot = { + state: 'sent' | 'timeout' | 'verified' | 'error', + verificationId: string, + code: string | null, + error: Error | null, +}; + +type PhoneAuthError = { + code: string | null, + verificationId: string, + message: string | null, + stack: string | null, +}; + +export default class PhoneAuthListener { + _auth: Auth; + _timeout: number; + _publicEvents: Object; + _internalEvents: Object; + _reject: Function | null; + _resolve: Function | null; + _credential: Object | null; + _promise: Promise<*> | null; + _phoneAuthRequestKey: string; + + /** + * + * @param auth + * @param phoneNumber + * @param timeout + */ + constructor(auth: Auth, phoneNumber: string, timeout?: number) { + this._auth = auth; + this._reject = null; + this._resolve = null; + this._promise = null; + this._credential = null; + + this._timeout = timeout || 20; // 20 secs + this._phoneAuthRequestKey = generatePushID(); + + // internal events + this._internalEvents = { + codeSent: `phone:auth:${this._phoneAuthRequestKey}:onCodeSent`, + verificationFailed: `phone:auth:${ + this._phoneAuthRequestKey + }:onVerificationFailed`, + verificationComplete: `phone:auth:${ + this._phoneAuthRequestKey + }:onVerificationComplete`, + codeAutoRetrievalTimeout: `phone:auth:${ + this._phoneAuthRequestKey + }:onCodeAutoRetrievalTimeout`, + }; + + // user observer events + this._publicEvents = { + // error cb + error: `phone:auth:${this._phoneAuthRequestKey}:error`, + // observer + event: `phone:auth:${this._phoneAuthRequestKey}:event`, + // success cb + success: `phone:auth:${this._phoneAuthRequestKey}:success`, + }; + + // setup internal event listeners + this._subscribeToEvents(); + + // start verification flow natively + if (isAndroid) { + getNativeModule(this._auth).verifyPhoneNumber( + phoneNumber, + this._phoneAuthRequestKey, + this._timeout + ); + } + + if (isIOS) { + getNativeModule(this._auth).verifyPhoneNumber( + phoneNumber, + this._phoneAuthRequestKey + ); + } + } + + /** + * Subscribes to all EE events on this._internalEvents + * @private + */ + _subscribeToEvents() { + const events = Object.keys(this._internalEvents); + + for (let i = 0, len = events.length; i < len; i++) { + const type = events[i]; + SharedEventEmitter.once( + this._internalEvents[type], + // $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323 + this[`_${type}Handler`].bind(this) + ); + } + } + + /** + * Subscribe a users listener cb to the snapshot events. + * @param observer + * @private + */ + _addUserObserver(observer) { + SharedEventEmitter.addListener(this._publicEvents.event, observer); + } + + /** + * Send a snapshot event to users event observer. + * @param snapshot PhoneAuthSnapshot + * @private + */ + _emitToObservers(snapshot: PhoneAuthSnapshot) { + SharedEventEmitter.emit(this._publicEvents.event, snapshot); + } + + /** + * Send a error snapshot event to any subscribed errorCb's + * @param snapshot + * @private + */ + _emitToErrorCb(snapshot) { + const { error } = snapshot; + if (this._reject) this._reject(error); + SharedEventEmitter.emit(this._publicEvents.error, error); + } + + /** + * Send a success snapshot event to any subscribed completeCb's + * @param snapshot + * @private + */ + _emitToSuccessCb(snapshot) { + if (this._resolve) this._resolve(snapshot); + SharedEventEmitter.emit(this._publicEvents.success, snapshot); + } + + /** + * Removes all listeners for this phone auth instance + * @private + */ + _removeAllListeners() { + setTimeout(() => { + // move to next event loop - not sure if needed + // internal listeners + Object.values(this._internalEvents).forEach(event => { + SharedEventEmitter.removeAllListeners(event); + }); + + // user observer listeners + Object.values(this._publicEvents).forEach(publicEvent => { + SharedEventEmitter.removeAllListeners(publicEvent); + }); + }, 0); + } + + /** + * Create a new internal deferred promise, if not already created + * @private + */ + _promiseDeferred() { + if (!this._promise) { + this._promise = new Promise((resolve, reject) => { + this._resolve = result => { + this._resolve = null; + return resolve(result); + }; + + this._reject = possibleError => { + this._reject = null; + return reject(possibleError); + }; + }); + } + } + + /* -------------------------- + --- INTERNAL EVENT HANDLERS + ---------------------------- */ + + /** + * Internal code sent event handler + * @private + * @param credential + */ + _codeSentHandler(credential) { + const snapshot: PhoneAuthSnapshot = { + verificationId: credential.verificationId, + code: null, + error: null, + state: 'sent', + }; + + this._emitToObservers(snapshot); + + if (isIOS) { + this._emitToSuccessCb(snapshot); + } + + if (isAndroid) { + // android can auto retrieve so we don't emit to successCb immediately, + // if auto retrieve times out then that will emit to successCb + } + } + + /** + * Internal code auto retrieve timeout event handler + * @private + * @param credential + */ + _codeAutoRetrievalTimeoutHandler(credential) { + const snapshot: PhoneAuthSnapshot = { + verificationId: credential.verificationId, + code: null, + error: null, + state: 'timeout', + }; + + this._emitToObservers(snapshot); + this._emitToSuccessCb(snapshot); + } + + /** + * Internal verification complete event handler + * @param credential + * @private + */ + _verificationCompleteHandler(credential) { + const snapshot: PhoneAuthSnapshot = { + verificationId: credential.verificationId, + code: credential.code || null, + error: null, + state: 'verified', + }; + + this._emitToObservers(snapshot); + this._emitToSuccessCb(snapshot); + this._removeAllListeners(); + } + + /** + * Internal verification failed event handler + * @param state + * @private + */ + _verificationFailedHandler(state) { + const snapshot: PhoneAuthSnapshot = { + verificationId: state.verificationId, + code: null, + error: null, + state: 'error', + }; + + const { code, message, nativeErrorMessage } = state.error; + snapshot.error = nativeToJSError(code, message, { nativeErrorMessage }); + + this._emitToObservers(snapshot); + this._emitToErrorCb(snapshot); + this._removeAllListeners(); + } + + /* ------------- + -- PUBLIC API + --------------*/ + + on( + event: string, + observer: () => PhoneAuthSnapshot, + errorCb?: () => PhoneAuthError, + successCb?: () => PhoneAuthSnapshot + ): this { + if (!isString(event)) { + throw new Error( + INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('event', 'string', 'on') + ); + } + + if (event !== 'state_changed') { + throw new Error( + INTERNALS.STRINGS.ERROR_ARG_INVALID_VALUE( + 'event', + 'state_changed', + event + ) + ); + } + + if (!isFunction(observer)) { + throw new Error( + INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('observer', 'function', 'on') + ); + } + + this._addUserObserver(observer); + + if (isFunction(errorCb)) { + SharedEventEmitter.once(this._publicEvents.error, errorCb); + } + + if (isFunction(successCb)) { + SharedEventEmitter.once(this._publicEvents.success, successCb); + } + + return this; + } + + /** + * Promise .then proxy + * @param fn + */ + then(fn: () => PhoneAuthSnapshot) { + this._promiseDeferred(); + // $FlowFixMe: Unsure how to annotate `bind` here + if (this._promise) return this._promise.then.bind(this._promise)(fn); + return undefined; // will never get here - just to keep flow happy + } + + /** + * Promise .catch proxy + * @param fn + */ + catch(fn: () => Error) { + this._promiseDeferred(); + // $FlowFixMe: Unsure how to annotate `bind` here + if (this._promise) return this._promise.catch.bind(this._promise)(fn); + return undefined; // will never get here - just to keep flow happy + } +} diff --git a/tests-new/firebase/modules/auth/providers/EmailAuthProvider.js b/tests-new/firebase/modules/auth/providers/EmailAuthProvider.js new file mode 100644 index 00000000..0c37d661 --- /dev/null +++ b/tests-new/firebase/modules/auth/providers/EmailAuthProvider.js @@ -0,0 +1,27 @@ +/** + * @flow + * EmailAuthProvider representation wrapper + */ +import type { AuthCredential } from '../types'; + +const providerId = 'password'; + +export default class EmailAuthProvider { + constructor() { + throw new Error( + '`new EmailAuthProvider()` is not supported on the native Firebase SDKs.' + ); + } + + static get PROVIDER_ID(): string { + return providerId; + } + + static credential(email: string, password: string): AuthCredential { + return { + token: email, + secret: password, + providerId, + }; + } +} diff --git a/tests-new/firebase/modules/auth/providers/FacebookAuthProvider.js b/tests-new/firebase/modules/auth/providers/FacebookAuthProvider.js new file mode 100644 index 00000000..67fa957b --- /dev/null +++ b/tests-new/firebase/modules/auth/providers/FacebookAuthProvider.js @@ -0,0 +1,27 @@ +/** + * @flow + * FacebookAuthProvider representation wrapper + */ +import type { AuthCredential } from '../types'; + +const providerId = 'facebook.com'; + +export default class FacebookAuthProvider { + constructor() { + throw new Error( + '`new FacebookAuthProvider()` is not supported on the native Firebase SDKs.' + ); + } + + static get PROVIDER_ID(): string { + return providerId; + } + + static credential(token: string): AuthCredential { + return { + token, + secret: '', + providerId, + }; + } +} diff --git a/tests-new/firebase/modules/auth/providers/GithubAuthProvider.js b/tests-new/firebase/modules/auth/providers/GithubAuthProvider.js new file mode 100644 index 00000000..a6e8c13c --- /dev/null +++ b/tests-new/firebase/modules/auth/providers/GithubAuthProvider.js @@ -0,0 +1,27 @@ +/** + * @flow + * GithubAuthProvider representation wrapper + */ +import type { AuthCredential } from '../types'; + +const providerId = 'github.com'; + +export default class GithubAuthProvider { + constructor() { + throw new Error( + '`new GithubAuthProvider()` is not supported on the native Firebase SDKs.' + ); + } + + static get PROVIDER_ID(): string { + return providerId; + } + + static credential(token: string): AuthCredential { + return { + token, + secret: '', + providerId, + }; + } +} diff --git a/tests-new/firebase/modules/auth/providers/GoogleAuthProvider.js b/tests-new/firebase/modules/auth/providers/GoogleAuthProvider.js new file mode 100644 index 00000000..25c81a21 --- /dev/null +++ b/tests-new/firebase/modules/auth/providers/GoogleAuthProvider.js @@ -0,0 +1,27 @@ +/** + * @flow + * EmailAuthProvider representation wrapper + */ +import type { AuthCredential } from '../types'; + +const providerId = 'google.com'; + +export default class GoogleAuthProvider { + constructor() { + throw new Error( + '`new GoogleAuthProvider()` is not supported on the native Firebase SDKs.' + ); + } + + static get PROVIDER_ID(): string { + return providerId; + } + + static credential(token: string, secret: string): AuthCredential { + return { + token, + secret, + providerId, + }; + } +} diff --git a/tests-new/firebase/modules/auth/providers/OAuthProvider.js b/tests-new/firebase/modules/auth/providers/OAuthProvider.js new file mode 100644 index 00000000..2bd28ed2 --- /dev/null +++ b/tests-new/firebase/modules/auth/providers/OAuthProvider.js @@ -0,0 +1,27 @@ +/** + * @flow + * OAuthProvider representation wrapper + */ +import type { AuthCredential } from '../types'; + +const providerId = 'oauth'; + +export default class OAuthProvider { + constructor() { + throw new Error( + '`new OAuthProvider()` is not supported on the native Firebase SDKs.' + ); + } + + static get PROVIDER_ID(): string { + return providerId; + } + + static credential(idToken: string, accessToken: string): AuthCredential { + return { + token: idToken, + secret: accessToken, + providerId, + }; + } +} diff --git a/tests-new/firebase/modules/auth/providers/PhoneAuthProvider.js b/tests-new/firebase/modules/auth/providers/PhoneAuthProvider.js new file mode 100644 index 00000000..57ce6558 --- /dev/null +++ b/tests-new/firebase/modules/auth/providers/PhoneAuthProvider.js @@ -0,0 +1,27 @@ +/** + * @flow + * PhoneAuthProvider representation wrapper + */ +import type { AuthCredential } from '../types'; + +const providerId = 'phone'; + +export default class PhoneAuthProvider { + constructor() { + throw new Error( + '`new PhoneAuthProvider()` is not supported on the native Firebase SDKs.' + ); + } + + static get PROVIDER_ID(): string { + return providerId; + } + + static credential(verificationId: string, code: string): AuthCredential { + return { + token: verificationId, + secret: code, + providerId, + }; + } +} diff --git a/tests-new/firebase/modules/auth/providers/TwitterAuthProvider.js b/tests-new/firebase/modules/auth/providers/TwitterAuthProvider.js new file mode 100644 index 00000000..150926bb --- /dev/null +++ b/tests-new/firebase/modules/auth/providers/TwitterAuthProvider.js @@ -0,0 +1,27 @@ +/** + * @flow + * TwitterAuthProvider representation wrapper + */ +import type { AuthCredential } from '../types'; + +const providerId = 'twitter.com'; + +export default class TwitterAuthProvider { + constructor() { + throw new Error( + '`new TwitterAuthProvider()` is not supported on the native Firebase SDKs.' + ); + } + + static get PROVIDER_ID(): string { + return providerId; + } + + static credential(token: string, secret: string): AuthCredential { + return { + token, + secret, + providerId, + }; + } +} diff --git a/tests-new/firebase/modules/auth/types.js b/tests-new/firebase/modules/auth/types.js new file mode 100644 index 00000000..64655509 --- /dev/null +++ b/tests-new/firebase/modules/auth/types.js @@ -0,0 +1,75 @@ +/** + * @flow + */ +import type User from './User'; + +export type ActionCodeInfo = { + data: { + email?: string, + fromEmail?: string, + }, + operation: 'PASSWORD_RESET' | 'VERIFY_EMAIL' | 'RECOVER_EMAIL', +}; + +export type ActionCodeSettings = { + android: { + installApp?: boolean, + minimumVersion?: string, + packageName: string, + }, + handleCodeInApp?: boolean, + iOS: { + bundleId?: string, + }, + url: string, +}; + +export type AdditionalUserInfo = { + isNewUser: boolean, + profile?: Object, + providerId: string, + username?: string, +}; + +export type AuthCredential = { + providerId: string, + token: string, + secret: string, +}; + +export type UserCredential = {| + additionalUserInfo?: AdditionalUserInfo, + user: User, +|}; + +export type UserInfo = { + displayName?: string, + email?: string, + phoneNumber?: string, + photoURL?: string, + providerId: string, + uid: string, +}; + +export type UserMetadata = { + creationTime?: string, + lastSignInTime?: string, +}; + +export type NativeUser = { + displayName?: string, + email?: string, + emailVerified?: boolean, + isAnonymous?: boolean, + metadata: UserMetadata, + phoneNumber?: string, + photoURL?: string, + providerData: UserInfo[], + providerId: string, + uid: string, +}; + +export type NativeUserCredential = {| + additionalUserInfo?: AdditionalUserInfo, + user: NativeUser, +|}; diff --git a/tests-new/firebase/modules/base.js b/tests-new/firebase/modules/base.js new file mode 100644 index 00000000..f1ea818f --- /dev/null +++ b/tests-new/firebase/modules/base.js @@ -0,0 +1,21 @@ +/** + * @flow + */ + +// todo move out +export class ReferenceBase extends Base { + constructor(path: string) { + super(); + this.path = path || '/'; + } + + /** + * The last part of a Reference's path (after the last '/') + * The key of a root Reference is null. + * @type {String} + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#key} + */ + get key(): string | null { + return this.path === '/' ? null : this.path.substring(this.path.lastIndexOf('/') + 1); + } +} diff --git a/tests-new/firebase/modules/config/index.js b/tests-new/firebase/modules/config/index.js new file mode 100644 index 00000000..05511262 --- /dev/null +++ b/tests-new/firebase/modules/config/index.js @@ -0,0 +1,185 @@ +/** + * @flow + * Remote Config representation wrapper + */ +import { getLogger } from '../../utils/log'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; + +import type App from '../core/app'; + +type NativeValue = { + stringValue?: string, + numberValue?: number, + dataValue?: Object, + boolValue?: boolean, + source: + | 'remoteConfigSourceRemote' + | 'remoteConfigSourceDefault' + | ' remoteConfigSourceStatic', +}; + +export const MODULE_NAME = 'RNFirebaseRemoteConfig'; +export const NAMESPACE = 'config'; + +/** + * @class Config + */ +export default class RemoteConfig extends ModuleBase { + _developerModeEnabled: boolean; + + constructor(app: App) { + super(app, { + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + this._developerModeEnabled = false; + } + + /** + * Converts a native map to single JS value + * @param nativeValue + * @returns {*} + * @private + */ + _nativeValueToJS(nativeValue: NativeValue) { + return { + source: nativeValue.source, + val() { + if ( + nativeValue.boolValue !== null && + (nativeValue.stringValue === 'true' || + nativeValue.stringValue === 'false' || + nativeValue.stringValue === null) + ) + return nativeValue.boolValue; + if ( + nativeValue.numberValue !== null && + nativeValue.numberValue !== undefined && + (nativeValue.stringValue == null || + nativeValue.stringValue === '' || + nativeValue.numberValue.toString() === nativeValue.stringValue) + ) + return nativeValue.numberValue; + if ( + nativeValue.dataValue !== nativeValue.stringValue && + (nativeValue.stringValue == null || nativeValue.stringValue === '') + ) + return nativeValue.dataValue; + return nativeValue.stringValue; + }, + }; + } + + /** + * Enable Remote Config developer mode to allow for frequent refreshes of the cache + */ + enableDeveloperMode() { + if (!this._developerModeEnabled) { + getLogger(this).debug('Enabled developer mode'); + getNativeModule(this).enableDeveloperMode(); + this._developerModeEnabled = true; + } + } + + /** + * Fetches Remote Config data + * Call activateFetched to make fetched data available in app + * @returns {*|Promise.}: + */ + fetch(expiration?: number) { + if (expiration !== undefined) { + getLogger(this).debug( + `Fetching remote config data with expiration ${expiration.toString()}` + ); + return getNativeModule(this).fetchWithExpirationDuration(expiration); + } + getLogger(this).debug('Fetching remote config data'); + return getNativeModule(this).fetch(); + } + + /** + * Applies Fetched Config data to the Active Config + * @returns {*|Promise.} + * resolves if there was a Fetched Config, and it was activated, + * rejects if no Fetched Config was found, or the Fetched Config was already activated. + */ + activateFetched() { + getLogger(this).debug('Activating remote config'); + return getNativeModule(this).activateFetched(); + } + + /** + * Gets the config value of the default namespace. + * @param key: Config key + * @returns {*|Promise.}, will always resolve + * Object looks like + * { + * "stringValue" : stringValue, + * "numberValue" : numberValue, + * "dataValue" : dataValue, + * "boolValue" : boolValue, + * "source" : OneOf(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic) + * } + */ + getValue(key: string) { + return getNativeModule(this) + .getValue(key || '') + .then(this._nativeValueToJS); + } + + /** + * Gets the config value of the default namespace. + * @param keys: Config key + * @returns {*|Promise.}, will always resolve. + * Result will be a dictionary of key and config objects + * Object looks like + * { + * "stringValue" : stringValue, + * "numberValue" : numberValue, + * "dataValue" : dataValue, + * "boolValue" : boolValue, + * "source" : OneOf(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic) + * } + */ + getValues(keys: Array) { + return getNativeModule(this) + .getValues(keys || []) + .then(nativeValues => { + const values: { [string]: Object } = {}; + for (let i = 0, len = keys.length; i < len; i++) { + values[keys[i]] = this._nativeValueToJS(nativeValues[i]); + } + return values; + }); + } + + /** + * Get the set of parameter keys that start with the given prefix, from the default namespace + * @param prefix: The key prefix to look for. If prefix is nil or empty, returns all the keys. + * @returns {*|Promise.>} + */ + getKeysByPrefix(prefix?: string) { + return getNativeModule(this).getKeysByPrefix(prefix); + } + + /** + * Sets config defaults for parameter keys and values in the default namespace config. + * @param defaults: A dictionary mapping a String key to a Object values. + */ + setDefaults(defaults: Object) { + getNativeModule(this).setDefaults(defaults); + } + + /** + * Sets default configs from plist for default namespace; + * @param resource: The plist file name or resource ID + */ + setDefaultsFromResource(resource: string | number) { + getNativeModule(this).setDefaultsFromResource(resource); + } +} + +export const statics = {}; diff --git a/tests-new/firebase/modules/core/app.js b/tests-new/firebase/modules/core/app.js new file mode 100644 index 00000000..352ec7d4 --- /dev/null +++ b/tests-new/firebase/modules/core/app.js @@ -0,0 +1,196 @@ +/* + * @flow + */ +import { NativeModules } from 'react-native'; + +import APPS from '../../utils/apps'; +import { SharedEventEmitter } from '../../utils/events'; +import INTERNALS from '../../utils/internals'; +import { isObject } from '../../utils'; + +import AdMob, { NAMESPACE as AdmobNamespace } from '../admob'; +import Auth, { NAMESPACE as AuthNamespace } from '../auth'; +import Analytics, { NAMESPACE as AnalyticsNamespace } from '../analytics'; +import Config, { NAMESPACE as ConfigNamespace } from '../config'; +import Crash, { NAMESPACE as CrashNamespace } from '../crash'; +import Crashlytics, { + NAMESPACE as CrashlyticsNamespace, +} from '../fabric/crashlytics'; +import Database, { NAMESPACE as DatabaseNamespace } from '../database'; +import Firestore, { NAMESPACE as FirestoreNamespace } from '../firestore'; +import InstanceId, { NAMESPACE as InstanceIdNamespace } from '../instanceid'; +import Invites, { NAMESPACE as InvitesNamespace } from '../invites'; +import Links, { NAMESPACE as LinksNamespace } from '../links'; +import Messaging, { NAMESPACE as MessagingNamespace } from '../messaging'; +import Notifications, { + NAMESPACE as NotificationsNamespace, +} from '../notifications'; +import Performance, { NAMESPACE as PerfNamespace } from '../perf'; +import Storage, { NAMESPACE as StorageNamespace } from '../storage'; +import Utils, { NAMESPACE as UtilsNamespace } from '../utils'; + +import type { FirebaseOptions } from '../../types'; + +const FirebaseCoreModule = NativeModules.RNFirebase; + +export default class App { + _extendedProps: { [string]: boolean }; + _initialized: boolean = false; + _name: string; + _nativeInitialized: boolean = false; + _options: FirebaseOptions; + admob: () => AdMob; + analytics: () => Analytics; + auth: () => Auth; + config: () => Config; + crash: () => Crash; + database: () => Database; + fabric: { + crashlytics: () => Crashlytics, + }; + firestore: () => Firestore; + instanceid: () => InstanceId; + invites: () => Invites; + links: () => Links; + messaging: () => Messaging; + notifications: () => Notifications; + perf: () => Performance; + storage: () => Storage; + utils: () => Utils; + + constructor( + name: string, + options: FirebaseOptions, + fromNative: boolean = false + ) { + this._name = name; + this._options = Object.assign({}, options); + + if (fromNative) { + this._initialized = true; + this._nativeInitialized = true; + } else if (options.databaseURL && options.apiKey) { + FirebaseCoreModule.initializeApp( + this._name, + this._options, + (error, result) => { + this._initialized = true; + SharedEventEmitter.emit(`AppReady:${this._name}`, { error, result }); + } + ); + } + + // modules + this.admob = APPS.appModule(this, AdmobNamespace, AdMob); + this.analytics = APPS.appModule(this, AnalyticsNamespace, Analytics); + this.auth = APPS.appModule(this, AuthNamespace, Auth); + this.config = APPS.appModule(this, ConfigNamespace, Config); + this.crash = APPS.appModule(this, CrashNamespace, Crash); + this.database = APPS.appModule(this, DatabaseNamespace, Database); + this.fabric = { + crashlytics: APPS.appModule(this, CrashlyticsNamespace, Crashlytics), + }; + this.firestore = APPS.appModule(this, FirestoreNamespace, Firestore); + this.instanceid = APPS.appModule(this, InstanceIdNamespace, InstanceId); + this.invites = APPS.appModule(this, InvitesNamespace, Invites); + this.links = APPS.appModule(this, LinksNamespace, Links); + this.messaging = APPS.appModule(this, MessagingNamespace, Messaging); + this.notifications = APPS.appModule( + this, + NotificationsNamespace, + Notifications + ); + this.perf = APPS.appModule(this, PerfNamespace, Performance); + this.storage = APPS.appModule(this, StorageNamespace, Storage); + this.utils = APPS.appModule(this, UtilsNamespace, Utils); + this._extendedProps = {}; + } + + /** + * + * @return {*} + */ + get name(): string { + return this._name; + } + + /** + * + * @return {*} + */ + get options(): FirebaseOptions { + return Object.assign({}, this._options); + } + + /** + * Undocumented firebase web sdk method that allows adding additional properties onto + * a firebase app instance. + * + * See: https://github.com/firebase/firebase-js-sdk/blob/master/tests/app/firebase_app.test.ts#L328 + * + * @param props + */ + extendApp(props: Object) { + if (!isObject(props)) { + throw new Error( + INTERNALS.STRINGS.ERROR_MISSING_ARG('Object', 'extendApp') + ); + } + + const keys = Object.keys(props); + + for (let i = 0, len = keys.length; i < len; i++) { + const key = keys[i]; + + if (!this._extendedProps[key] && Object.hasOwnProperty.call(this, key)) { + throw new Error(INTERNALS.STRINGS.ERROR_PROTECTED_PROP(key)); + } + + // $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323 + this[key] = props[key]; + this._extendedProps[key] = true; + } + } + + /** + * + * @return {Promise} + */ + delete() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('app', 'delete') + ); + // TODO only the ios sdk currently supports delete, add back in when android also supports it + // if (this._name === APPS.DEFAULT_APP_NAME && this._nativeInitialized) { + // return Promise.reject( + // new Error('Unable to delete the default native firebase app instance.'), + // ); + // } + // + // return FirebaseCoreModule.deleteApp(this._name); + } + + /** + * + * @return {*} + */ + onReady(): Promise { + if (this._initialized) return Promise.resolve(this); + + return new Promise((resolve, reject) => { + SharedEventEmitter.once(`AppReady:${this._name}`, ({ error }) => { + if (error) return reject(new Error(error)); // error is a string as it's from native + return resolve(this); // return app + }); + }); + } + + /** + * toString returns the name of the app. + * + * @return {string} + */ + toString() { + return this._name; + } +} diff --git a/tests-new/firebase/modules/core/firebase.js b/tests-new/firebase/modules/core/firebase.js new file mode 100644 index 00000000..91f3ffcc --- /dev/null +++ b/tests-new/firebase/modules/core/firebase.js @@ -0,0 +1,226 @@ +/** + * @flow + */ +import { NativeModules } from 'react-native'; + +import APPS from '../../utils/apps'; +import INTERNALS from '../../utils/internals'; +import App from './app'; +import VERSION from '../../version'; + +// module imports +import { + statics as AdMobStatics, + MODULE_NAME as AdmobModuleName, +} from '../admob'; +import { statics as AuthStatics, MODULE_NAME as AuthModuleName } from '../auth'; +import { + statics as AnalyticsStatics, + MODULE_NAME as AnalyticsModuleName, +} from '../analytics'; +import { + statics as ConfigStatics, + MODULE_NAME as ConfigModuleName, +} from '../config'; +import { + statics as CrashStatics, + MODULE_NAME as CrashModuleName, +} from '../crash'; +import { + statics as CrashlyticsStatics, + MODULE_NAME as CrashlyticsModuleName, +} from '../fabric/crashlytics'; +import { + statics as DatabaseStatics, + MODULE_NAME as DatabaseModuleName, +} from '../database'; +import { + statics as FirestoreStatics, + MODULE_NAME as FirestoreModuleName, +} from '../firestore'; +import { + statics as InstanceIdStatics, + MODULE_NAME as InstanceIdModuleName, +} from '../instanceid'; +import { + statics as InvitesStatics, + MODULE_NAME as InvitesModuleName, +} from '../invites'; +import { + statics as LinksStatics, + MODULE_NAME as LinksModuleName, +} from '../links'; +import { + statics as MessagingStatics, + MODULE_NAME as MessagingModuleName, +} from '../messaging'; +import { + statics as NotificationsStatics, + MODULE_NAME as NotificationsModuleName, +} from '../notifications'; +import { + statics as PerformanceStatics, + MODULE_NAME as PerfModuleName, +} from '../perf'; +import { + statics as StorageStatics, + MODULE_NAME as StorageModuleName, +} from '../storage'; +import { + statics as UtilsStatics, + MODULE_NAME as UtilsModuleName, +} from '../utils'; + +import type { + AdMobModule, + AnalyticsModule, + AuthModule, + ConfigModule, + CrashModule, + DatabaseModule, + FabricModule, + FirebaseOptions, + FirestoreModule, + InstanceIdModule, + InvitesModule, + LinksModule, + MessagingModule, + NotificationsModule, + PerformanceModule, + StorageModule, + UtilsModule, +} from '../../types'; + +const FirebaseCoreModule = NativeModules.RNFirebase; + +class Firebase { + admob: AdMobModule; + analytics: AnalyticsModule; + auth: AuthModule; + config: ConfigModule; + crash: CrashModule; + database: DatabaseModule; + fabric: FabricModule; + firestore: FirestoreModule; + instanceid: InstanceIdModule; + invites: InvitesModule; + links: LinksModule; + messaging: MessagingModule; + notifications: NotificationsModule; + perf: PerformanceModule; + storage: StorageModule; + utils: UtilsModule; + + constructor() { + if (!FirebaseCoreModule) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_CORE); + } + APPS.initializeNativeApps(); + + // modules + this.admob = APPS.moduleAndStatics('admob', AdMobStatics, AdmobModuleName); + this.analytics = APPS.moduleAndStatics( + 'analytics', + AnalyticsStatics, + AnalyticsModuleName + ); + this.auth = APPS.moduleAndStatics('auth', AuthStatics, AuthModuleName); + this.config = APPS.moduleAndStatics( + 'config', + ConfigStatics, + ConfigModuleName + ); + this.crash = APPS.moduleAndStatics('crash', CrashStatics, CrashModuleName); + this.database = APPS.moduleAndStatics( + 'database', + DatabaseStatics, + DatabaseModuleName + ); + this.fabric = { + crashlytics: APPS.moduleAndStatics( + 'crashlytics', + CrashlyticsStatics, + CrashlyticsModuleName + ), + }; + this.firestore = APPS.moduleAndStatics( + 'firestore', + FirestoreStatics, + FirestoreModuleName + ); + this.instanceid = APPS.moduleAndStatics( + 'instanceid', + InstanceIdStatics, + InstanceIdModuleName + ); + this.invites = APPS.moduleAndStatics( + 'invites', + InvitesStatics, + InvitesModuleName + ); + this.links = APPS.moduleAndStatics('links', LinksStatics, LinksModuleName); + this.messaging = APPS.moduleAndStatics( + 'messaging', + MessagingStatics, + MessagingModuleName + ); + this.notifications = APPS.moduleAndStatics( + 'notifications', + NotificationsStatics, + NotificationsModuleName + ); + this.perf = APPS.moduleAndStatics( + 'perf', + PerformanceStatics, + PerfModuleName + ); + this.storage = APPS.moduleAndStatics( + 'storage', + StorageStatics, + StorageModuleName + ); + this.utils = APPS.moduleAndStatics('utils', UtilsStatics, UtilsModuleName); + } + + /** + * Web SDK initializeApp + * + * @param options + * @param name + * @return {*} + */ + initializeApp(options: FirebaseOptions, name: string): App { + return APPS.initializeApp(options, name); + } + + /** + * Retrieves a Firebase app instance. + * + * When called with no arguments, the default app is returned. + * When an app name is provided, the app corresponding to that name is returned. + * + * @param name + * @return {*} + */ + app(name?: string): App { + return APPS.app(name); + } + + /** + * A (read-only) array of all initialized apps. + * @return {Array} + */ + get apps(): Array { + return APPS.apps(); + } + + /** + * The current SDK version. + * @return {string} + */ + get SDK_VERSION(): string { + return VERSION; + } +} + +export default new Firebase(); diff --git a/tests-new/firebase/modules/crash/index.js b/tests-new/firebase/modules/crash/index.js new file mode 100644 index 00000000..2d9a0a8f --- /dev/null +++ b/tests-new/firebase/modules/crash/index.js @@ -0,0 +1,87 @@ +/** + * @flow + * Crash Reporting representation wrapper + */ +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; + +import type App from '../core/app'; +import type { FirebaseError } from '../../types'; + +export const MODULE_NAME = 'RNFirebaseCrash'; +export const NAMESPACE = 'crash'; + +export default class Crash extends ModuleBase { + constructor(app: App) { + super(app, { + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + } + + /** + * Enables/Disables crash reporting + * @param enabled + */ + setCrashCollectionEnabled(enabled: boolean): void { + getNativeModule(this).setCrashCollectionEnabled(enabled); + } + + /** + * Returns whether or not crash reporting is currently enabled + * @returns {Promise.} + */ + isCrashCollectionEnabled(): Promise { + return getNativeModule(this).isCrashCollectionEnabled(); + } + + /** + * Logs a message that will appear in a subsequent crash report. + * @param {string} message + */ + log(message: string): void { + getNativeModule(this).log(message); + } + + /** + * Logs a message that will appear in a subsequent crash report as well as in logcat. + * NOTE: Android only functionality. iOS will just log the message. + * @param {string} message + * @param {number} level + * @param {string} tag + */ + logcat(level: number, tag: string, message: string): void { + getNativeModule(this).logcat(level, tag, message); + } + + /** + * Generates a crash report for the given message. This method should be used for unexpected + * exceptions where recovery is not possible. + * NOTE: on iOS, this will cause the app to crash as it's the only way to ensure the exception + * gets sent to Firebase. Otherwise it just gets lost as a log message. + * @param {Error} error + * @param maxStackSize + */ + report(error: FirebaseError, maxStackSize: number = 10): void { + if (!error || !error.message) return; + + let errorMessage = `Message: ${error.message}\r\n`; + + if (error.code) { + errorMessage = `${errorMessage}Code: ${error.code}\r\n`; + } + + const stackRows = error.stack.split('\n'); + errorMessage = `${errorMessage}\r\nStack: \r\n`; + for (let i = 0, len = stackRows.length; i < len; i++) { + if (i === maxStackSize) break; + errorMessage = `${errorMessage} - ${stackRows[i]}\r\n`; + } + + getNativeModule(this).report(errorMessage); + } +} + +export const statics = {}; diff --git a/tests-new/firebase/modules/database/DataSnapshot.js b/tests-new/firebase/modules/database/DataSnapshot.js new file mode 100644 index 00000000..31597cbf --- /dev/null +++ b/tests-new/firebase/modules/database/DataSnapshot.js @@ -0,0 +1,146 @@ +/** + * @flow + * DataSnapshot representation wrapper + */ +import { isObject, deepGet, deepExists } from './../../utils'; +import type Reference from './Reference'; + +/** + * @class DataSnapshot + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot + */ +export default class DataSnapshot { + ref: Reference; + key: string; + + _value: any; + _priority: any; + _childKeys: Array; + + constructor(ref: Reference, snapshot: Object) { + this.key = snapshot.key; + + if (ref.key !== snapshot.key) { + this.ref = ref.child(snapshot.key); + } else { + this.ref = ref; + } + + // internal use only + this._value = snapshot.value; + this._priority = snapshot.priority === undefined ? null : snapshot.priority; + this._childKeys = snapshot.childKeys || []; + } + + /** + * Extracts a JavaScript value from a DataSnapshot. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#val + * @returns {any} + */ + val(): any { + // clone via JSON stringify/parse - prevent modification of this._value + if (isObject(this._value) || Array.isArray(this._value)) + return JSON.parse(JSON.stringify(this._value)); + return this._value; + } + + /** + * Gets another DataSnapshot for the location at the specified relative path. + * @param path + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach + * @returns {Snapshot} + */ + child(path: string): DataSnapshot { + const value = deepGet(this._value, path); + const childRef = this.ref.child(path); + return new DataSnapshot(childRef, { + value, + key: childRef.key, + exists: value !== null, + + // todo this is wrong - child keys needs to be the ordered keys, from FB + // todo potential solution is build up a tree/map of a snapshot and its children + // todo natively and send that back to JS to be use in this class. + childKeys: isObject(value) ? Object.keys(value) : [], + }); + } + + /** + * Returns true if this DataSnapshot contains any data. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#exists + * @returns {boolean} + */ + exists(): boolean { + return this._value !== null; + } + + /** + * Enumerates the top-level children in the DataSnapshot. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach + * @param action + */ + forEach(action: (key: any) => any): boolean { + if (!this._childKeys.length) return false; + let cancelled = false; + + for (let i = 0, len = this._childKeys.length; i < len; i++) { + const key = this._childKeys[i]; + const childSnapshot = this.child(key); + const returnValue = action(childSnapshot); + + if (returnValue === true) { + cancelled = true; + break; + } + } + + return cancelled; + } + + /** + * Gets the priority value of the data in this DataSnapshot. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#getPriority + * @returns {String|Number|null} + */ + getPriority(): string | number | null { + return this._priority; + } + + /** + * Returns true if the specified child path has (non-null) data. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#hasChild + * @param path + * @returns {Boolean} + */ + hasChild(path: string): boolean { + return deepExists(this._value, path); + } + + /** + * Returns whether or not the DataSnapshot has any non-null child properties. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#hasChildren + * @returns {boolean} + */ + hasChildren(): boolean { + return this.numChildren() > 0; + } + + /** + * Returns the number of child properties of this DataSnapshot. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#numChildren + * @returns {Number} + */ + numChildren(): number { + if (!isObject(this._value)) return 0; + return Object.keys(this._value).length; + } + + /** + * Returns a JSON-serializable representation of this object. + * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#toJSON + * @returns {any} + */ + toJSON(): Object { + return this.val(); + } +} diff --git a/tests-new/firebase/modules/database/OnDisconnect.js b/tests-new/firebase/modules/database/OnDisconnect.js new file mode 100644 index 00000000..1662b085 --- /dev/null +++ b/tests-new/firebase/modules/database/OnDisconnect.js @@ -0,0 +1,68 @@ +/** + * @flow + * OnDisconnect representation wrapper + */ +import { typeOf } from '../../utils'; +import { getNativeModule } from '../../utils/native'; +import type Database from './'; +import type Reference from './Reference'; + +/** + * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect + * @class OmDisconnect + */ +export default class OnDisconnect { + _database: Database; + ref: Reference; + path: string; + + /** + * + * @param ref + */ + constructor(ref: Reference) { + this.ref = ref; + this.path = ref.path; + this._database = ref._database; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#set + * @param value + * @returns {*} + */ + set(value: string | Object): Promise { + return getNativeModule(this._database).onDisconnectSet(this.path, { + type: typeOf(value), + value, + }); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#update + * @param values + * @returns {*} + */ + update(values: Object): Promise { + return getNativeModule(this._database).onDisconnectUpdate( + this.path, + values + ); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#remove + * @returns {*} + */ + remove(): Promise { + return getNativeModule(this._database).onDisconnectRemove(this.path); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#cancel + * @returns {*} + */ + cancel(): Promise { + return getNativeModule(this._database).onDisconnectCancel(this.path); + } +} diff --git a/tests-new/firebase/modules/database/Query.js b/tests-new/firebase/modules/database/Query.js new file mode 100644 index 00000000..b58388be --- /dev/null +++ b/tests-new/firebase/modules/database/Query.js @@ -0,0 +1,108 @@ +/** + * @flow + * Query representation wrapper + */ +import { objectToUniqueId } from '../../utils'; + +import type { DatabaseModifier } from '../../types'; +import type Reference from './Reference'; + +// todo doc methods + +/** + * @class Query + */ +export default class Query { + _reference: Reference; + modifiers: Array; + + constructor(ref: Reference, existingModifiers?: Array) { + this.modifiers = existingModifiers ? [...existingModifiers] : []; + this._reference = ref; + } + + /** + * + * @param name + * @param key + * @return {Reference|*} + */ + orderBy(name: string, key?: string) { + this.modifiers.push({ + id: `orderBy-${name}:${key || ''}`, + type: 'orderBy', + name, + key, + }); + + return this._reference; + } + + /** + * + * @param name + * @param limit + * @return {Reference|*} + */ + limit(name: string, limit: number) { + this.modifiers.push({ + id: `limit-${name}:${limit}`, + type: 'limit', + name, + limit, + }); + + return this._reference; + } + + /** + * + * @param name + * @param value + * @param key + * @return {Reference|*} + */ + filter(name: string, value: any, key?: string) { + this.modifiers.push({ + id: `filter-${name}:${objectToUniqueId(value)}:${key || ''}`, + type: 'filter', + name, + value, + valueType: typeof value, + key, + }); + + return this._reference; + } + + /** + * + * @return {[*]} + */ + getModifiers(): Array { + return [...this.modifiers]; + } + + /** + * + * @return {*} + */ + queryIdentifier() { + // sort modifiers to enforce ordering + const sortedModifiers = this.getModifiers().sort((a, b) => { + if (a.id < b.id) return -1; + if (a.id > b.id) return 1; + return 0; + }); + + // Convert modifiers to unique key + let key = '{'; + for (let i = 0; i < sortedModifiers.length; i++) { + if (i !== 0) key += ','; + key += sortedModifiers[i].id; + } + key += '}'; + + return key; + } +} diff --git a/tests-new/firebase/modules/database/Reference.js b/tests-new/firebase/modules/database/Reference.js new file mode 100644 index 00000000..ce54e645 --- /dev/null +++ b/tests-new/firebase/modules/database/Reference.js @@ -0,0 +1,894 @@ +/** + * @flow + * Database Reference representation wrapper + */ +import Query from './Query'; +import DataSnapshot from './DataSnapshot'; +import OnDisconnect from './OnDisconnect'; +import { getLogger } from '../../utils/log'; +import { getNativeModule } from '../../utils/native'; +import ReferenceBase from '../../utils/ReferenceBase'; + +import { + promiseOrCallback, + isFunction, + isObject, + isString, + tryJSONParse, + tryJSONStringify, + generatePushID, +} from '../../utils'; + +import SyncTree from '../../utils/SyncTree'; + +import type Database from './'; +import type { DatabaseModifier, FirebaseError } from '../../types'; + +// track all event registrations by path +let listeners = 0; + +/** + * Enum for event types + * @readonly + * @enum {String} + */ +const ReferenceEventTypes = { + value: 'value', + child_added: 'child_added', + child_removed: 'child_removed', + child_changed: 'child_changed', + child_moved: 'child_moved', +}; + +type DatabaseListener = { + listenerId: number, + eventName: string, + successCallback: Function, + failureCallback?: Function, +}; + +/** + * @typedef {String} ReferenceLocation - Path to location in the database, relative + * to the root reference. Consists of a path where segments are separated by a + * forward slash (/) and ends in a ReferenceKey - except the root location, which + * has no ReferenceKey. + * + * @example + * // root reference location: '/' + * // non-root reference: '/path/to/referenceKey' + */ + +/** + * @typedef {String} ReferenceKey - Identifier for each location that is unique to that + * location, within the scope of its parent. The last part of a ReferenceLocation. + */ + +/** + * Represents a specific location in your Database that can be used for + * reading or writing data. + * + * You can reference the root using firebase.database().ref() or a child location + * by calling firebase.database().ref("child/path"). + * + * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference + * @class Reference + * @extends ReferenceBase + */ +export default class Reference extends ReferenceBase { + _database: Database; + _promise: ?Promise<*>; + _query: Query; + _refListeners: { [listenerId: number]: DatabaseListener }; + + constructor( + database: Database, + path: string, + existingModifiers?: Array + ) { + super(path); + this._promise = null; + this._refListeners = {}; + this._database = database; + this._query = new Query(this, existingModifiers); + getLogger(database).debug('Created new Reference', this._getRefKey()); + } + + /** + * By calling `keepSynced(true)` on a location, the data for that location will + * automatically be downloaded and kept in sync, even when no listeners are + * attached for that location. Additionally, while a location is kept synced, + * it will not be evicted from the persistent disk cache. + * + * @link https://firebase.google.com/docs/reference/android/com/google/firebase/database/Query.html#keepSynced(boolean) + * @param bool + * @returns {*} + */ + keepSynced(bool: boolean): Promise { + return getNativeModule(this._database).keepSynced( + this._getRefKey(), + this.path, + this._query.getModifiers(), + bool + ); + } + + /** + * Writes data to this Database location. + * + * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#set + * @param value + * @param onComplete + * @returns {Promise} + */ + set(value: any, onComplete?: Function): Promise { + return promiseOrCallback( + getNativeModule(this._database).set( + this.path, + this._serializeAnyType(value) + ), + onComplete + ); + } + + /** + * Sets a priority for the data at this Database location. + * + * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#setPriority + * @param priority + * @param onComplete + * @returns {Promise} + */ + setPriority( + priority: string | number | null, + onComplete?: Function + ): Promise { + const _priority = this._serializeAnyType(priority); + + return promiseOrCallback( + getNativeModule(this._database).setPriority(this.path, _priority), + onComplete + ); + } + + /** + * Writes data the Database location. Like set() but also specifies the priority for that data. + * + * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#setWithPriority + * @param value + * @param priority + * @param onComplete + * @returns {Promise} + */ + setWithPriority( + value: any, + priority: string | number | null, + onComplete?: Function + ): Promise { + const _value = this._serializeAnyType(value); + const _priority = this._serializeAnyType(priority); + + return promiseOrCallback( + getNativeModule(this._database).setWithPriority( + this.path, + _value, + _priority + ), + onComplete + ); + } + + /** + * Writes multiple values to the Database at once. + * + * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#update + * @param val + * @param onComplete + * @returns {Promise} + */ + update(val: Object, onComplete?: Function): Promise { + const value = this._serializeObject(val); + + return promiseOrCallback( + getNativeModule(this._database).update(this.path, value), + onComplete + ); + } + + /** + * Removes the data at this Database location. + * + * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#remove + * @param onComplete + * @return {Promise} + */ + remove(onComplete?: Function): Promise { + return promiseOrCallback( + getNativeModule(this._database).remove(this.path), + onComplete + ); + } + + /** + * Atomically modifies the data at this location. + * + * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction + * @param transactionUpdate + * @param onComplete + * @param applyLocally + */ + transaction( + transactionUpdate: Function, + onComplete: ( + error: ?Error, + committed: boolean, + snapshot: ?DataSnapshot + ) => *, + applyLocally: boolean = false + ) { + if (!isFunction(transactionUpdate)) { + return Promise.reject( + new Error('Missing transactionUpdate function argument.') + ); + } + + return new Promise((resolve, reject) => { + const onCompleteWrapper = (error, committed, snapshotData) => { + if (isFunction(onComplete)) { + if (error) { + onComplete(error, committed, null); + } else { + onComplete(null, committed, new DataSnapshot(this, snapshotData)); + } + } + + if (error) return reject(error); + return resolve({ + committed, + snapshot: new DataSnapshot(this, snapshotData), + }); + }; + + // start the transaction natively + this._database._transactionHandler.add( + this, + transactionUpdate, + onCompleteWrapper, + applyLocally + ); + }); + } + + /** + * + * @param eventName + * @param successCallback + * @param cancelOrContext + * @param context + * @returns {Promise.} + */ + once( + eventName: string = 'value', + successCallback: (snapshot: DataSnapshot) => void, + cancelOrContext: (error: FirebaseError) => void, + context?: Object + ) { + return getNativeModule(this._database) + .once(this._getRefKey(), this.path, this._query.getModifiers(), eventName) + .then(({ snapshot }) => { + const _snapshot = new DataSnapshot(this, snapshot); + + if (isFunction(successCallback)) { + if (isObject(cancelOrContext)) + successCallback.bind(cancelOrContext)(_snapshot); + if (context && isObject(context)) + successCallback.bind(context)(_snapshot); + successCallback(_snapshot); + } + + return _snapshot; + }) + .catch(error => { + if (isFunction(cancelOrContext)) return cancelOrContext(error); + throw error; + }); + } + + /** + * + * @param value + * @param onComplete + * @returns {*} + */ + push(value: any, onComplete?: Function): Reference | Promise { + if (value === null || value === undefined) { + return new Reference( + this._database, + `${this.path}/${generatePushID(this._database._serverTimeOffset)}` + ); + } + + const newRef = new Reference( + this._database, + `${this.path}/${generatePushID(this._database._serverTimeOffset)}` + ); + const promise = newRef.set(value); + + // if callback provided then internally call the set promise with value + if (isFunction(onComplete)) { + return ( + promise + // $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655 + .then(() => onComplete(null, newRef)) + // $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655 + .catch(error => onComplete(error, null)) + ); + } + + // otherwise attach promise to 'thenable' reference and return the + // new reference + newRef._setThenable(promise); + return newRef; + } + + /** + * MODIFIERS + */ + + /** + * + * @returns {Reference} + */ + orderByKey(): Reference { + return this.orderBy('orderByKey'); + } + + /** + * + * @returns {Reference} + */ + orderByPriority(): Reference { + return this.orderBy('orderByPriority'); + } + + /** + * + * @returns {Reference} + */ + orderByValue(): Reference { + return this.orderBy('orderByValue'); + } + + /** + * + * @param key + * @returns {Reference} + */ + orderByChild(key: string): Reference { + return this.orderBy('orderByChild', key); + } + + /** + * + * @param name + * @param key + * @returns {Reference} + */ + orderBy(name: string, key?: string): Reference { + const newRef = new Reference( + this._database, + this.path, + this._query.getModifiers() + ); + newRef._query.orderBy(name, key); + return newRef; + } + + /** + * LIMITS + */ + + /** + * + * @param limit + * @returns {Reference} + */ + limitToLast(limit: number): Reference { + return this.limit('limitToLast', limit); + } + + /** + * + * @param limit + * @returns {Reference} + */ + limitToFirst(limit: number): Reference { + return this.limit('limitToFirst', limit); + } + + /** + * + * @param name + * @param limit + * @returns {Reference} + */ + limit(name: string, limit: number): Reference { + const newRef = new Reference( + this._database, + this.path, + this._query.getModifiers() + ); + newRef._query.limit(name, limit); + return newRef; + } + + /** + * FILTERS + */ + + /** + * + * @param value + * @param key + * @returns {Reference} + */ + equalTo(value: any, key?: string): Reference { + return this.filter('equalTo', value, key); + } + + /** + * + * @param value + * @param key + * @returns {Reference} + */ + endAt(value: any, key?: string): Reference { + return this.filter('endAt', value, key); + } + + /** + * + * @param value + * @param key + * @returns {Reference} + */ + startAt(value: any, key?: string): Reference { + return this.filter('startAt', value, key); + } + + /** + * + * @param name + * @param value + * @param key + * @returns {Reference} + */ + filter(name: string, value: any, key?: string): Reference { + const newRef = new Reference( + this._database, + this.path, + this._query.getModifiers() + ); + newRef._query.filter(name, value, key); + return newRef; + } + + /** + * + * @returns {OnDisconnect} + */ + onDisconnect(): OnDisconnect { + return new OnDisconnect(this); + } + + /** + * Creates a Reference to a child of the current Reference, using a relative path. + * No validation is performed on the path to ensure it has a valid format. + * @param {String} path relative to current ref's location + * @returns {!Reference} A new Reference to the path provided, relative to the current + * Reference + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#child} + */ + child(path: string): Reference { + return new Reference(this._database, `${this.path}/${path}`); + } + + /** + * Return the ref as a path string + * @returns {string} + */ + toString(): string { + return `${this._database.databaseUrl}/${this.path}`; + } + + /** + * Returns whether another Reference represent the same location and are from the + * same instance of firebase.app.App - multiple firebase apps not currently supported. + * @param {Reference} otherRef - Other reference to compare to this one + * @return {Boolean} Whether otherReference is equal to this one + * + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#isEqual} + */ + isEqual(otherRef: Reference): boolean { + return ( + !!otherRef && + otherRef.constructor === Reference && + otherRef.key === this.key && + this._query.queryIdentifier() === otherRef._query.queryIdentifier() + ); + } + + /** + * GETTERS + */ + + /** + * The parent location of a Reference, or null for the root Reference. + * @type {Reference} + * + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#parent} + */ + get parent(): Reference | null { + if (this.path === '/') return null; + return new Reference( + this._database, + this.path.substring(0, this.path.lastIndexOf('/')) + ); + } + + /** + * A reference to itself + * @type {!Reference} + * + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#ref} + */ + get ref(): Reference { + return this; + } + + /** + * Reference to the root of the database: '/' + * @type {!Reference} + * + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#root} + */ + get root(): Reference { + return new Reference(this._database, '/'); + } + + /** + * Access then method of promise if set + * @return {*} + */ + then(fnResolve: any => any, fnReject: any => any) { + if (isFunction(fnResolve) && this._promise && this._promise.then) { + return this._promise.then.bind(this._promise)( + result => { + this._promise = null; + return fnResolve(result); + }, + possibleErr => { + this._promise = null; + + if (isFunction(fnReject)) { + return fnReject(possibleErr); + } + + throw possibleErr; + } + ); + } + + throw new Error("Cannot read property 'then' of undefined."); + } + + /** + * Access catch method of promise if set + * @return {*} + */ + catch(fnReject: any => any) { + if (isFunction(fnReject) && this._promise && this._promise.catch) { + return this._promise.catch.bind(this._promise)(possibleErr => { + this._promise = null; + return fnReject(possibleErr); + }); + } + + throw new Error("Cannot read property 'catch' of undefined."); + } + + /** + * INTERNALS + */ + + /** + * Generate a unique registration key. + * + * @return {string} + */ + _getRegistrationKey(eventType: string): string { + return `$${this._database.databaseUrl}$/${ + this.path + }$${this._query.queryIdentifier()}$${listeners}$${eventType}`; + } + + /** + * Generate a string that uniquely identifies this + * combination of path and query modifiers + * + * @return {string} + * @private + */ + _getRefKey() { + return `$${this._database.databaseUrl}$/${ + this.path + }$${this._query.queryIdentifier()}`; + } + + /** + * Set the promise this 'thenable' reference relates to + * @param promise + * @private + */ + _setThenable(promise: Promise<*>) { + this._promise = promise; + } + + /** + * + * @param obj + * @returns {Object} + * @private + */ + _serializeObject(obj: Object) { + if (!isObject(obj)) return obj; + + // json stringify then parse it calls toString on Objects / Classes + // that support it i.e new Date() becomes a ISO string. + return tryJSONParse(tryJSONStringify(obj)); + } + + /** + * + * @param value + * @returns {*} + * @private + */ + _serializeAnyType(value: any) { + if (isObject(value)) { + return { + type: 'object', + value: this._serializeObject(value), + }; + } + + return { + type: typeof value, + value, + }; + } + + /** + * Register a listener for data changes at the current ref's location. + * The primary method of reading data from a Database. + * + * Listeners can be unbound using {@link off}. + * + * Event Types: + * + * - value: {@link callback}. + * - child_added: {@link callback} + * - child_removed: {@link callback} + * - child_changed: {@link callback} + * - child_moved: {@link callback} + * + * @param {ReferenceEventType} eventType - Type of event to attach a callback for. + * @param {ReferenceEventCallback} callback - Function that will be called + * when the event occurs with the new data. + * @param {cancelCallbackOrContext=} cancelCallbackOrContext - Optional callback that is called + * if the event subscription fails. {@link cancelCallbackOrContext} + * @param {*=} context - Optional object to bind the callbacks to when calling them. + * @returns {ReferenceEventCallback} callback function, unmodified (unbound), for + * convenience if you want to pass an inline function to on() and store it later for + * removing using off(). + * + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#on} + */ + on( + eventType: string, + callback: DataSnapshot => any, + cancelCallbackOrContext?: Object => any | Object, + context?: Object + ): Function { + if (!eventType) { + throw new Error( + 'Query.on failed: Function called with 0 arguments. Expects at least 2.' + ); + } + + if (!isString(eventType) || !ReferenceEventTypes[eventType]) { + throw new Error( + `Query.on failed: First argument must be a valid string event type: "${Object.keys( + ReferenceEventTypes + ).join(', ')}"` + ); + } + + if (!callback) { + throw new Error( + 'Query.on failed: Function called with 1 argument. Expects at least 2.' + ); + } + + if (!isFunction(callback)) { + throw new Error( + 'Query.on failed: Second argument must be a valid function.' + ); + } + + if ( + cancelCallbackOrContext && + !isFunction(cancelCallbackOrContext) && + !isObject(context) && + !isObject(cancelCallbackOrContext) + ) { + throw new Error( + 'Query.on failed: Function called with 3 arguments, but third optional argument `cancelCallbackOrContext` was not a function.' + ); + } + + if ( + cancelCallbackOrContext && + !isFunction(cancelCallbackOrContext) && + context + ) { + throw new Error( + 'Query.on failed: Function called with 4 arguments, but third optional argument `cancelCallbackOrContext` was not a function.' + ); + } + + const eventRegistrationKey = this._getRegistrationKey(eventType); + const registrationCancellationKey = `${eventRegistrationKey}$cancelled`; + const _context = + cancelCallbackOrContext && !isFunction(cancelCallbackOrContext) + ? cancelCallbackOrContext + : context; + const registrationObj = { + eventType, + ref: this, + path: this.path, + key: this._getRefKey(), + appName: this._database.app.name, + dbURL: this._database.databaseUrl, + eventRegistrationKey, + }; + + SyncTree.addRegistration({ + ...registrationObj, + 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 + SyncTree.addRegistration({ + ref: this, + once: true, + path: this.path, + key: this._getRefKey(), + appName: this._database.app.name, + dbURL: this._database.databaseUrl, + eventType: `${eventType}$cancelled`, + eventRegistrationKey: registrationCancellationKey, + listener: _context + ? cancelCallbackOrContext.bind(_context) + : cancelCallbackOrContext, + }); + } + + // initialise the native listener if not already listening + getNativeModule(this._database).on({ + eventType, + path: this.path, + key: this._getRefKey(), + appName: this._database.app.name, + modifiers: this._query.getModifiers(), + hasCancellationCallback: isFunction(cancelCallbackOrContext), + registration: { + eventRegistrationKey, + key: registrationObj.key, + registrationCancellationKey, + }, + }); + + // increment number of listeners - just s short way of making + // every registration unique per .on() call + listeners += 1; + + // return original unbound successCallback for + // the purposes of calling .off(eventType, callback) at a later date + return callback; + } + + /** + * Detaches a callback previously attached with on(). + * + * Detach a callback previously attached with on(). Note that if on() was called + * multiple times with the same eventType and callback, the callback will be called + * multiple times for each event, and off() must be called multiple times to + * remove the callback. Calling off() on a parent listener will not automatically + * remove listeners registered on child nodes, off() must also be called on any + * child listeners to remove the callback. + * + * If a callback is not specified, all callbacks for the specified eventType will be removed. + * Similarly, if no eventType or callback is specified, all callbacks for the Reference will be removed. + * @param eventType + * @param originalCallback + */ + off(eventType?: string = '', originalCallback?: () => any) { + if (!arguments.length) { + // Firebase Docs: + // if no eventType or callback is specified, all callbacks for the Reference will be removed. + return SyncTree.removeListenersForRegistrations( + SyncTree.getRegistrationsByPath(this.path) + ); + } + + /* + * VALIDATE ARGS + */ + if ( + eventType && + (!isString(eventType) || !ReferenceEventTypes[eventType]) + ) { + throw new Error( + `Query.off failed: First argument must be a valid string event type: "${Object.keys( + ReferenceEventTypes + ).join(', ')}"` + ); + } + + if (originalCallback && !isFunction(originalCallback)) { + throw new Error( + 'Query.off failed: Function called with 2 arguments, but second optional argument was not a function.' + ); + } + + // Firebase Docs: + // Note that if on() was called + // multiple times with the same eventType and callback, the callback will be called + // multiple times for each event, and off() must be called multiple times to + // remove the callback. + // Remove only a single registration + if (eventType && originalCallback) { + const registration = SyncTree.getOneByPathEventListener( + this.path, + eventType, + originalCallback + ); + if (!registration) return []; + + // remove the paired cancellation registration if any exist + SyncTree.removeListenersForRegistrations([`${registration}$cancelled`]); + + // remove only the first registration to match firebase web sdk + // call multiple times to remove multiple registrations + return SyncTree.removeListenerRegistrations(originalCallback, [ + registration, + ]); + } + + // Firebase Docs: + // If a callback is not specified, all callbacks for the specified eventType will be removed. + const registrations = SyncTree.getRegistrationsByPathEvent( + this.path, + eventType + ); + + SyncTree.removeListenersForRegistrations( + SyncTree.getRegistrationsByPathEvent(this.path, `${eventType}$cancelled`) + ); + + return SyncTree.removeListenersForRegistrations(registrations); + } +} diff --git a/tests-new/firebase/modules/database/index.js b/tests-new/firebase/modules/database/index.js new file mode 100644 index 00000000..fb198588 --- /dev/null +++ b/tests-new/firebase/modules/database/index.js @@ -0,0 +1,128 @@ +/** + * @flow + * Database representation wrapper + */ +import { NativeModules } from 'react-native'; + +import Reference from './Reference'; +import TransactionHandler from './transaction'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; + +import type App from '../core/app'; +import firebase from '../core/firebase'; + +const NATIVE_EVENTS = [ + 'database_transaction_event', + // 'database_server_offset', // TODO +]; + +export const MODULE_NAME = 'RNFirebaseDatabase'; +export const NAMESPACE = 'database'; + +/** + * @class Database + */ +export default class Database extends ModuleBase { + _offsetRef: Reference; + _serverTimeOffset: number; + _transactionHandler: TransactionHandler; + _serviceUrl: string; + + constructor(appOrUrl: App | string, options: Object = {}) { + let app; + let serviceUrl; + if (typeof appOrUrl === 'string') { + app = firebase.app(); + serviceUrl = appOrUrl.endsWith('/') ? appOrUrl : `${appOrUrl}/`; + } else { + app = appOrUrl; + serviceUrl = app.options.databaseURL; + } + + super( + app, + { + events: NATIVE_EVENTS, + moduleName: MODULE_NAME, + multiApp: true, + hasShards: true, + namespace: NAMESPACE, + }, + serviceUrl + ); + + this._serviceUrl = serviceUrl; + this._transactionHandler = new TransactionHandler(this); + + if (options.persistence) { + getNativeModule(this).setPersistence(options.persistence); + } + + // server time listener + // setTimeout used to avoid setPersistence race conditions + // todo move this and persistence to native side, create a db configure() method natively perhaps? + // todo and then native can call setPersistence and then emit offset events + setTimeout(() => { + this._serverTimeOffset = 0; + this._offsetRef = this.ref('.info/serverTimeOffset'); + this._offsetRef.on('value', snapshot => { + this._serverTimeOffset = snapshot.val() || this._serverTimeOffset; + }); + }, 1); + } + + /** + * + * @return {number} + */ + getServerTime(): number { + return new Date(Date.now() + this._serverTimeOffset); + } + + /** + * + */ + goOnline(): void { + getNativeModule(this).goOnline(); + } + + /** + * + */ + goOffline(): void { + getNativeModule(this).goOffline(); + } + + /** + * Returns a new firebase reference instance + * @param path + * @returns {Reference} + */ + ref(path: string): Reference { + return new Reference(this, path); + } + + /** + * Returns the database url + * @returns {string} + */ + get databaseUrl(): string { + return this._serviceUrl; + } +} + +export const statics = { + ServerValue: NativeModules.RNFirebaseDatabase + ? { + TIMESTAMP: NativeModules.RNFirebaseDatabase.serverValueTimestamp || { + '.sv': 'timestamp', + }, + } + : {}, + enableLogging(enabled: boolean) { + if (NativeModules[MODULE_NAME]) { + NativeModules[MODULE_NAME].enableLogging(enabled); + } + }, +}; diff --git a/tests-new/firebase/modules/database/transaction.js b/tests-new/firebase/modules/database/transaction.js new file mode 100644 index 00000000..5ac6dd4e --- /dev/null +++ b/tests-new/firebase/modules/database/transaction.js @@ -0,0 +1,164 @@ +/** + * @flow + * Database Transaction representation wrapper + */ +import { getAppEventName, SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import { getNativeModule } from '../../utils/native'; +import type Database from './'; + +let transactionId = 0; + +/** + * Uses the push id generator to create a transaction id + * @returns {number} + * @private + */ +const generateTransactionId = (): number => transactionId++; + +/** + * @class TransactionHandler + */ +export default class TransactionHandler { + _database: Database; + _transactions: { [number]: Object }; + + constructor(database: Database) { + this._transactions = {}; + this._database = database; + + SharedEventEmitter.addListener( + getAppEventName(this._database, 'database_transaction_event'), + this._handleTransactionEvent.bind(this) + ); + } + + /** + * Add a new transaction and start it natively. + * @param reference + * @param transactionUpdater + * @param onComplete + * @param applyLocally + */ + add( + reference: Object, + transactionUpdater: Function, + onComplete?: Function, + applyLocally?: boolean = false + ) { + const id = generateTransactionId(); + + this._transactions[id] = { + id, + reference, + transactionUpdater, + onComplete, + applyLocally, + completed: false, + started: true, + }; + + getNativeModule(this._database).transactionStart( + reference.path, + id, + applyLocally + ); + } + + /** + * INTERNALS + */ + + /** + * + * @param event + * @returns {*} + * @private + */ + _handleTransactionEvent(event: Object = {}) { + switch (event.type) { + case 'update': + return this._handleUpdate(event); + case 'error': + return this._handleError(event); + case 'complete': + return this._handleComplete(event); + default: + getLogger(this._database).warn( + `Unknown transaction event type: '${event.type}'`, + event + ); + return undefined; + } + } + + /** + * + * @param event + * @private + */ + _handleUpdate(event: Object = {}) { + let newValue; + const { id, value } = event; + + try { + const transaction = this._transactions[id]; + if (!transaction) return; + + newValue = transaction.transactionUpdater(value); + } finally { + let abort = false; + + if (newValue === undefined) { + abort = true; + } + + getNativeModule(this._database).transactionTryCommit(id, { + value: newValue, + abort, + }); + } + } + + /** + * + * @param event + * @private + */ + _handleError(event: Object = {}) { + const transaction = this._transactions[event.id]; + if (transaction && !transaction.completed) { + transaction.completed = true; + try { + transaction.onComplete(event.error, false, null); + } finally { + setImmediate(() => { + delete this._transactions[event.id]; + }); + } + } + } + + /** + * + * @param event + * @private + */ + _handleComplete(event: Object = {}) { + const transaction = this._transactions[event.id]; + if (transaction && !transaction.completed) { + transaction.completed = true; + try { + transaction.onComplete( + null, + event.committed, + Object.assign({}, event.snapshot) + ); + } finally { + setImmediate(() => { + delete this._transactions[event.id]; + }); + } + } + } +} diff --git a/tests-new/firebase/modules/fabric/crashlytics/index.js b/tests-new/firebase/modules/fabric/crashlytics/index.js new file mode 100644 index 00000000..7e797774 --- /dev/null +++ b/tests-new/firebase/modules/fabric/crashlytics/index.js @@ -0,0 +1,83 @@ +/** + * @flow + * Crash Reporting representation wrapper + */ +import ModuleBase from '../../../utils/ModuleBase'; +import { getNativeModule } from '../../../utils/native'; + +import type App from '../../core/app'; + +export const MODULE_NAME = 'RNFirebaseCrashlytics'; +export const NAMESPACE = 'crashlytics'; + +export default class Crashlytics extends ModuleBase { + constructor(app: App) { + super(app, { + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + } + + /** + * Forces a crash. Useful for testing your application is set up correctly. + */ + crash(): void { + getNativeModule(this).crash(); + } + + /** + * Logs a message that will appear in any subsequent crash reports. + * @param {string} message + */ + log(message: string): void { + getNativeModule(this).log(message); + } + + /** + * Logs a non fatal exception. + * @param {string} code + * @param {string} message + */ + recordError(code: number, message: string): void { + getNativeModule(this).recordError(code, message); + } + + /** + * Set a boolean value to show alongside any subsequent crash reports. + */ + setBoolValue(key: string, value: boolean): void { + getNativeModule(this).setBoolValue(key, value); + } + + /** + * Set a float value to show alongside any subsequent crash reports. + */ + setFloatValue(key: string, value: number): void { + getNativeModule(this).setFloatValue(key, value); + } + + /** + * Set an integer value to show alongside any subsequent crash reports. + */ + setIntValue(key: string, value: number): void { + getNativeModule(this).setIntValue(key, value); + } + + /** + * Set a string value to show alongside any subsequent crash reports. + */ + setStringValue(key: string, value: string): void { + getNativeModule(this).setStringValue(key, value); + } + + /** + * Set the user ID to show alongside any subsequent crash reports. + */ + setUserIdentifier(userId: string): void { + getNativeModule(this).setUserIdentifier(userId); + } +} + +export const statics = {}; diff --git a/tests-new/firebase/modules/firestore/CollectionReference.js b/tests-new/firebase/modules/firestore/CollectionReference.js new file mode 100644 index 00000000..57e6d2a3 --- /dev/null +++ b/tests-new/firebase/modules/firestore/CollectionReference.js @@ -0,0 +1,109 @@ +/** + * @flow + * CollectionReference representation wrapper + */ +import DocumentReference from './DocumentReference'; +import Query from './Query'; +import { firestoreAutoId } from '../../utils'; + +import type Firestore from './'; +import type { + QueryDirection, + QueryListenOptions, + QueryOperator, +} from './types'; +import type FieldPath from './FieldPath'; +import type Path from './Path'; +import type { Observer, ObserverOnError, ObserverOnNext } from './Query'; +import type QuerySnapshot from './QuerySnapshot'; + +/** + * @class CollectionReference + */ +export default class CollectionReference { + _collectionPath: Path; + _firestore: Firestore; + _query: Query; + + constructor(firestore: Firestore, collectionPath: Path) { + this._collectionPath = collectionPath; + this._firestore = firestore; + this._query = new Query(firestore, collectionPath); + } + + get firestore(): Firestore { + return this._firestore; + } + + get id(): string | null { + return this._collectionPath.id; + } + + get parent(): DocumentReference | null { + const parentPath = this._collectionPath.parent(); + return parentPath + ? new DocumentReference(this._firestore, parentPath) + : null; + } + + add(data: Object): Promise { + const documentRef = this.doc(); + return documentRef.set(data).then(() => Promise.resolve(documentRef)); + } + + doc(documentPath?: string): DocumentReference { + const newPath = documentPath || firestoreAutoId(); + + const path = this._collectionPath.child(newPath); + if (!path.isDocument) { + throw new Error('Argument "documentPath" must point to a document.'); + } + + return new DocumentReference(this._firestore, path); + } + + // From Query + endAt(...snapshotOrVarArgs: any[]): Query { + return this._query.endAt(snapshotOrVarArgs); + } + + endBefore(...snapshotOrVarArgs: any[]): Query { + return this._query.endBefore(snapshotOrVarArgs); + } + + get(): Promise { + return this._query.get(); + } + + limit(limit: number): Query { + return this._query.limit(limit); + } + + onSnapshot( + optionsOrObserverOrOnNext: QueryListenOptions | Observer | ObserverOnNext, + observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError, + onError?: ObserverOnError + ): () => void { + return this._query.onSnapshot( + optionsOrObserverOrOnNext, + observerOrOnNextOrOnError, + onError + ); + } + + orderBy(fieldPath: string | FieldPath, directionStr?: QueryDirection): Query { + return this._query.orderBy(fieldPath, directionStr); + } + + startAfter(...snapshotOrVarArgs: any[]): Query { + return this._query.startAfter(snapshotOrVarArgs); + } + + startAt(...snapshotOrVarArgs: any[]): Query { + return this._query.startAt(snapshotOrVarArgs); + } + + where(fieldPath: string, opStr: QueryOperator, value: any): Query { + return this._query.where(fieldPath, opStr, value); + } +} diff --git a/tests-new/firebase/modules/firestore/DocumentChange.js b/tests-new/firebase/modules/firestore/DocumentChange.js new file mode 100644 index 00000000..6adf9577 --- /dev/null +++ b/tests-new/firebase/modules/firestore/DocumentChange.js @@ -0,0 +1,41 @@ +/** + * @flow + * DocumentChange representation wrapper + */ +import DocumentSnapshot from './DocumentSnapshot'; + +import type Firestore from './'; +import type { NativeDocumentChange } from './types'; + +/** + * @class DocumentChange + */ +export default class DocumentChange { + _document: DocumentSnapshot; + _newIndex: number; + _oldIndex: number; + _type: string; + + constructor(firestore: Firestore, nativeData: NativeDocumentChange) { + this._document = new DocumentSnapshot(firestore, nativeData.document); + this._newIndex = nativeData.newIndex; + this._oldIndex = nativeData.oldIndex; + this._type = nativeData.type; + } + + get doc(): DocumentSnapshot { + return this._document; + } + + get newIndex(): number { + return this._newIndex; + } + + get oldIndex(): number { + return this._oldIndex; + } + + get type(): string { + return this._type; + } +} diff --git a/tests-new/firebase/modules/firestore/DocumentReference.js b/tests-new/firebase/modules/firestore/DocumentReference.js new file mode 100644 index 00000000..ebce412b --- /dev/null +++ b/tests-new/firebase/modules/firestore/DocumentReference.js @@ -0,0 +1,260 @@ +/** + * @flow + * DocumentReference representation wrapper + */ +import CollectionReference from './CollectionReference'; +import DocumentSnapshot from './DocumentSnapshot'; +import { parseUpdateArgs } from './utils'; +import { buildNativeMap } from './utils/serialize'; +import { getAppEventName, SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import { firestoreAutoId, isFunction, isObject } from '../../utils'; +import { getNativeModule } from '../../utils/native'; + +import type Firestore from './'; +import type { + DocumentListenOptions, + NativeDocumentSnapshot, + SetOptions, +} from './types'; +import type Path from './Path'; + +type ObserverOnError = Object => void; +type ObserverOnNext = DocumentSnapshot => void; + +type Observer = { + error?: ObserverOnError, + next: ObserverOnNext, +}; + +/** + * @class DocumentReference + */ +export default class DocumentReference { + _documentPath: Path; + _firestore: Firestore; + + constructor(firestore: Firestore, documentPath: Path) { + this._documentPath = documentPath; + this._firestore = firestore; + } + + get firestore(): Firestore { + return this._firestore; + } + + get id(): string | null { + return this._documentPath.id; + } + + get parent(): CollectionReference { + const parentPath = this._documentPath.parent(); + // $FlowExpectedError: parentPath can never be null + return new CollectionReference(this._firestore, parentPath); + } + + get path(): string { + return this._documentPath.relativeName; + } + + collection(collectionPath: string): CollectionReference { + const path = this._documentPath.child(collectionPath); + if (!path.isCollection) { + throw new Error('Argument "collectionPath" must point to a collection.'); + } + + return new CollectionReference(this._firestore, path); + } + + delete(): Promise { + return getNativeModule(this._firestore).documentDelete(this.path); + } + + get(): Promise { + return getNativeModule(this._firestore) + .documentGet(this.path) + .then(result => new DocumentSnapshot(this._firestore, result)); + } + + onSnapshot( + optionsOrObserverOrOnNext: + | DocumentListenOptions + | Observer + | ObserverOnNext, + observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError, + onError?: ObserverOnError + ) { + let observer: Observer; + let docListenOptions = {}; + // Called with: onNext, ?onError + if (isFunction(optionsOrObserverOrOnNext)) { + if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) { + throw new Error( + 'DocumentReference.onSnapshot failed: Second argument must be a valid function.' + ); + } + // $FlowExpectedError: Not coping with the overloaded method signature + observer = { + next: optionsOrObserverOrOnNext, + error: observerOrOnNextOrOnError, + }; + } else if ( + optionsOrObserverOrOnNext && + isObject(optionsOrObserverOrOnNext) + ) { + // Called with: Observer + if (optionsOrObserverOrOnNext.next) { + if (isFunction(optionsOrObserverOrOnNext.next)) { + if ( + optionsOrObserverOrOnNext.error && + !isFunction(optionsOrObserverOrOnNext.error) + ) { + throw new Error( + 'DocumentReference.onSnapshot failed: Observer.error must be a valid function.' + ); + } + // $FlowExpectedError: Not coping with the overloaded method signature + observer = { + next: optionsOrObserverOrOnNext.next, + error: optionsOrObserverOrOnNext.error, + }; + } else { + throw new Error( + 'DocumentReference.onSnapshot failed: Observer.next must be a valid function.' + ); + } + } else if ( + Object.prototype.hasOwnProperty.call( + optionsOrObserverOrOnNext, + 'includeMetadataChanges' + ) + ) { + docListenOptions = optionsOrObserverOrOnNext; + // Called with: Options, onNext, ?onError + if (isFunction(observerOrOnNextOrOnError)) { + if (onError && !isFunction(onError)) { + throw new Error( + 'DocumentReference.onSnapshot failed: Third argument must be a valid function.' + ); + } + // $FlowExpectedError: Not coping with the overloaded method signature + observer = { + next: observerOrOnNextOrOnError, + error: onError, + }; + // Called with Options, Observer + } else if ( + observerOrOnNextOrOnError && + isObject(observerOrOnNextOrOnError) && + observerOrOnNextOrOnError.next + ) { + if (isFunction(observerOrOnNextOrOnError.next)) { + if ( + observerOrOnNextOrOnError.error && + !isFunction(observerOrOnNextOrOnError.error) + ) { + throw new Error( + 'DocumentReference.onSnapshot failed: Observer.error must be a valid function.' + ); + } + observer = { + next: observerOrOnNextOrOnError.next, + error: observerOrOnNextOrOnError.error, + }; + } else { + throw new Error( + 'DocumentReference.onSnapshot failed: Observer.next must be a valid function.' + ); + } + } else { + throw new Error( + 'DocumentReference.onSnapshot failed: Second argument must be a function or observer.' + ); + } + } else { + throw new Error( + 'DocumentReference.onSnapshot failed: First argument must be a function, observer or options.' + ); + } + } else { + throw new Error( + 'DocumentReference.onSnapshot failed: Called with invalid arguments.' + ); + } + const listenerId = firestoreAutoId(); + + const listener = (nativeDocumentSnapshot: NativeDocumentSnapshot) => { + const documentSnapshot = new DocumentSnapshot( + this.firestore, + nativeDocumentSnapshot + ); + observer.next(documentSnapshot); + }; + + // Listen to snapshot events + SharedEventEmitter.addListener( + getAppEventName(this._firestore, `onDocumentSnapshot:${listenerId}`), + listener + ); + + // Listen for snapshot error events + if (observer.error) { + SharedEventEmitter.addListener( + getAppEventName( + this._firestore, + `onDocumentSnapshotError:${listenerId}` + ), + observer.error + ); + } + + // Add the native listener + getNativeModule(this._firestore).documentOnSnapshot( + this.path, + listenerId, + docListenOptions + ); + + // Return an unsubscribe method + return this._offDocumentSnapshot.bind(this, listenerId, listener); + } + + set(data: Object, options?: SetOptions): Promise { + const nativeData = buildNativeMap(data); + return getNativeModule(this._firestore).documentSet( + this.path, + nativeData, + options + ); + } + + update(...args: any[]): Promise { + const data = parseUpdateArgs(args, 'DocumentReference.update'); + const nativeData = buildNativeMap(data); + return getNativeModule(this._firestore).documentUpdate( + this.path, + nativeData + ); + } + + /** + * INTERNALS + */ + + /** + * Remove document snapshot listener + * @param listener + */ + _offDocumentSnapshot(listenerId: string, listener: Function) { + getLogger(this._firestore).info('Removing onDocumentSnapshot listener'); + SharedEventEmitter.removeListener( + getAppEventName(this._firestore, `onDocumentSnapshot:${listenerId}`), + listener + ); + SharedEventEmitter.removeListener( + getAppEventName(this._firestore, `onDocumentSnapshotError:${listenerId}`), + listener + ); + getNativeModule(this._firestore).documentOffSnapshot(this.path, listenerId); + } +} diff --git a/tests-new/firebase/modules/firestore/DocumentSnapshot.js b/tests-new/firebase/modules/firestore/DocumentSnapshot.js new file mode 100644 index 00000000..23e8ef10 --- /dev/null +++ b/tests-new/firebase/modules/firestore/DocumentSnapshot.js @@ -0,0 +1,68 @@ +/** + * @flow + * DocumentSnapshot representation wrapper + */ +import DocumentReference from './DocumentReference'; +import FieldPath from './FieldPath'; +import Path from './Path'; +import { isObject } from '../../utils'; +import { parseNativeMap } from './utils/serialize'; + +import type Firestore from './'; +import type { NativeDocumentSnapshot, SnapshotMetadata } from './types'; + +const extractFieldPathData = (data: Object | void, segments: string[]): any => { + if (!data || !isObject(data)) { + return undefined; + } + const pathValue = data[segments[0]]; + if (segments.length === 1) { + return pathValue; + } + return extractFieldPathData(pathValue, segments.slice(1)); +}; + +/** + * @class DocumentSnapshot + */ +export default class DocumentSnapshot { + _data: Object | void; + _metadata: SnapshotMetadata; + _ref: DocumentReference; + + constructor(firestore: Firestore, nativeData: NativeDocumentSnapshot) { + this._data = parseNativeMap(firestore, nativeData.data); + this._metadata = nativeData.metadata; + this._ref = new DocumentReference( + firestore, + Path.fromName(nativeData.path) + ); + } + + get exists(): boolean { + return this._data !== undefined; + } + + get id(): string | null { + return this._ref.id; + } + + get metadata(): SnapshotMetadata { + return this._metadata; + } + + get ref(): DocumentReference { + return this._ref; + } + + data(): Object | void { + return this._data; + } + + get(fieldPath: string | FieldPath): any { + if (fieldPath instanceof FieldPath) { + return extractFieldPathData(this._data, fieldPath._segments); + } + return this._data ? this._data[fieldPath] : undefined; + } +} diff --git a/tests-new/firebase/modules/firestore/FieldPath.js b/tests-new/firebase/modules/firestore/FieldPath.js new file mode 100644 index 00000000..202f3309 --- /dev/null +++ b/tests-new/firebase/modules/firestore/FieldPath.js @@ -0,0 +1,22 @@ +/** + * @flow + * FieldPath representation wrapper + */ + +/** + * @class FieldPath + */ +export default class FieldPath { + _segments: string[]; + + constructor(...segments: string[]) { + // TODO: Validation + this._segments = segments; + } + + static documentId(): FieldPath { + return DOCUMENT_ID; + } +} + +export const DOCUMENT_ID = new FieldPath('__name__'); diff --git a/tests-new/firebase/modules/firestore/FieldValue.js b/tests-new/firebase/modules/firestore/FieldValue.js new file mode 100644 index 00000000..14067038 --- /dev/null +++ b/tests-new/firebase/modules/firestore/FieldValue.js @@ -0,0 +1,17 @@ +/** + * @flow + * FieldValue representation wrapper + */ + +export default class FieldValue { + static delete(): FieldValue { + return DELETE_FIELD_VALUE; + } + + static serverTimestamp(): FieldValue { + return SERVER_TIMESTAMP_FIELD_VALUE; + } +} + +export const DELETE_FIELD_VALUE = new FieldValue(); +export const SERVER_TIMESTAMP_FIELD_VALUE = new FieldValue(); diff --git a/tests-new/firebase/modules/firestore/GeoPoint.js b/tests-new/firebase/modules/firestore/GeoPoint.js new file mode 100644 index 00000000..2e913c4d --- /dev/null +++ b/tests-new/firebase/modules/firestore/GeoPoint.js @@ -0,0 +1,29 @@ +/** + * @flow + * GeoPoint representation wrapper + */ + +/** + * @class GeoPoint + */ +export default class GeoPoint { + _latitude: number; + _longitude: number; + + constructor(latitude: number, longitude: number) { + // TODO: Validation + // validate.isNumber('latitude', latitude); + // validate.isNumber('longitude', longitude); + + this._latitude = latitude; + this._longitude = longitude; + } + + get latitude(): number { + return this._latitude; + } + + get longitude(): number { + return this._longitude; + } +} diff --git a/tests-new/firebase/modules/firestore/Path.js b/tests-new/firebase/modules/firestore/Path.js new file mode 100644 index 00000000..dd215cb1 --- /dev/null +++ b/tests-new/firebase/modules/firestore/Path.js @@ -0,0 +1,50 @@ +/** + * @flow + * Path representation wrapper + */ + +/** + * @class Path + */ +export default class Path { + _parts: string[]; + + constructor(pathComponents: string[]) { + this._parts = pathComponents; + } + + get id(): string | null { + return this._parts.length > 0 ? this._parts[this._parts.length - 1] : null; + } + + get isDocument(): boolean { + return this._parts.length > 0 && this._parts.length % 2 === 0; + } + + get isCollection(): boolean { + return this._parts.length % 2 === 1; + } + + get relativeName(): string { + return this._parts.join('/'); + } + + child(relativePath: string): Path { + return new Path(this._parts.concat(relativePath.split('/'))); + } + + parent(): Path | null { + return this._parts.length > 0 + ? new Path(this._parts.slice(0, this._parts.length - 1)) + : null; + } + + /** + * + * @package + */ + static fromName(name: string): Path { + const parts = name.split('/'); + return parts.length === 0 ? new Path([]) : new Path(parts); + } +} diff --git a/tests-new/firebase/modules/firestore/Query.js b/tests-new/firebase/modules/firestore/Query.js new file mode 100644 index 00000000..90ad3667 --- /dev/null +++ b/tests-new/firebase/modules/firestore/Query.js @@ -0,0 +1,459 @@ +/** + * @flow + * Query representation wrapper + */ +import DocumentSnapshot from './DocumentSnapshot'; +import FieldPath from './FieldPath'; +import QuerySnapshot from './QuerySnapshot'; +import { buildNativeArray, buildTypeMap } from './utils/serialize'; +import { getAppEventName, SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import { firestoreAutoId, isFunction, isObject } from '../../utils'; +import { getNativeModule } from '../../utils/native'; + +import type Firestore from './'; +import type Path from './Path'; +import type { + QueryDirection, + QueryOperator, + QueryListenOptions, +} from './types'; + +const DIRECTIONS: { [QueryDirection]: string } = { + ASC: 'ASCENDING', + asc: 'ASCENDING', + DESC: 'DESCENDING', + desc: 'DESCENDING', +}; + +const OPERATORS: { [QueryOperator]: string } = { + '=': 'EQUAL', + '==': 'EQUAL', + '>': 'GREATER_THAN', + '>=': 'GREATER_THAN_OR_EQUAL', + '<': 'LESS_THAN', + '<=': 'LESS_THAN_OR_EQUAL', +}; + +type NativeFieldPath = {| + elements?: string[], + string?: string, + type: 'fieldpath' | 'string', +|}; +type FieldFilter = {| + fieldPath: NativeFieldPath, + operator: string, + value: any, +|}; +type FieldOrder = {| + direction: string, + fieldPath: NativeFieldPath, +|}; +type QueryOptions = { + endAt?: any[], + endBefore?: any[], + limit?: number, + offset?: number, + selectFields?: string[], + startAfter?: any[], + startAt?: any[], +}; + +export type ObserverOnError = Object => void; +export type ObserverOnNext = QuerySnapshot => void; + +export type Observer = { + error?: ObserverOnError, + next: ObserverOnNext, +}; + +const buildNativeFieldPath = ( + fieldPath: string | FieldPath +): NativeFieldPath => { + if (fieldPath instanceof FieldPath) { + return { + elements: fieldPath._segments, + type: 'fieldpath', + }; + } + return { + string: fieldPath, + type: 'string', + }; +}; + +/** + * @class Query + */ +export default class Query { + _fieldFilters: FieldFilter[]; + _fieldOrders: FieldOrder[]; + _firestore: Firestore; + _iid: number; + _queryOptions: QueryOptions; + _referencePath: Path; + + constructor( + firestore: Firestore, + path: Path, + fieldFilters?: FieldFilter[], + fieldOrders?: FieldOrder[], + queryOptions?: QueryOptions + ) { + this._fieldFilters = fieldFilters || []; + this._fieldOrders = fieldOrders || []; + this._firestore = firestore; + this._queryOptions = queryOptions || {}; + this._referencePath = path; + } + + get firestore(): Firestore { + return this._firestore; + } + + endAt(...snapshotOrVarArgs: any[]): Query { + const options = { + ...this._queryOptions, + endAt: this._buildOrderByOption(snapshotOrVarArgs), + }; + + return new Query( + this.firestore, + this._referencePath, + this._fieldFilters, + this._fieldOrders, + options + ); + } + + endBefore(...snapshotOrVarArgs: any[]): Query { + const options = { + ...this._queryOptions, + endBefore: this._buildOrderByOption(snapshotOrVarArgs), + }; + + return new Query( + this.firestore, + this._referencePath, + this._fieldFilters, + this._fieldOrders, + options + ); + } + + get(): Promise { + return getNativeModule(this._firestore) + .collectionGet( + this._referencePath.relativeName, + this._fieldFilters, + this._fieldOrders, + this._queryOptions + ) + .then(nativeData => new QuerySnapshot(this._firestore, this, nativeData)); + } + + limit(limit: number): Query { + // TODO: Validation + // validate.isInteger('n', n); + + const options = { + ...this._queryOptions, + limit, + }; + return new Query( + this.firestore, + this._referencePath, + this._fieldFilters, + this._fieldOrders, + options + ); + } + + onSnapshot( + optionsOrObserverOrOnNext: QueryListenOptions | Observer | ObserverOnNext, + observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError, + onError?: ObserverOnError + ) { + let observer: Observer; + let queryListenOptions = {}; + // Called with: onNext, ?onError + if (isFunction(optionsOrObserverOrOnNext)) { + if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) { + throw new Error( + 'Query.onSnapshot failed: Second argument must be a valid function.' + ); + } + // $FlowExpectedError: Not coping with the overloaded method signature + observer = { + next: optionsOrObserverOrOnNext, + error: observerOrOnNextOrOnError, + }; + } else if ( + optionsOrObserverOrOnNext && + isObject(optionsOrObserverOrOnNext) + ) { + // Called with: Observer + if (optionsOrObserverOrOnNext.next) { + if (isFunction(optionsOrObserverOrOnNext.next)) { + if ( + optionsOrObserverOrOnNext.error && + !isFunction(optionsOrObserverOrOnNext.error) + ) { + throw new Error( + 'Query.onSnapshot failed: Observer.error must be a valid function.' + ); + } + // $FlowExpectedError: Not coping with the overloaded method signature + observer = { + next: optionsOrObserverOrOnNext.next, + error: optionsOrObserverOrOnNext.error, + }; + } else { + throw new Error( + 'Query.onSnapshot failed: Observer.next must be a valid function.' + ); + } + } else if ( + Object.prototype.hasOwnProperty.call( + optionsOrObserverOrOnNext, + 'includeDocumentMetadataChanges' + ) || + Object.prototype.hasOwnProperty.call( + optionsOrObserverOrOnNext, + 'includeQueryMetadataChanges' + ) + ) { + queryListenOptions = optionsOrObserverOrOnNext; + // Called with: Options, onNext, ?onError + if (isFunction(observerOrOnNextOrOnError)) { + if (onError && !isFunction(onError)) { + throw new Error( + 'Query.onSnapshot failed: Third argument must be a valid function.' + ); + } + // $FlowExpectedError: Not coping with the overloaded method signature + observer = { + next: observerOrOnNextOrOnError, + error: onError, + }; + // Called with Options, Observer + } else if ( + observerOrOnNextOrOnError && + isObject(observerOrOnNextOrOnError) && + observerOrOnNextOrOnError.next + ) { + if (isFunction(observerOrOnNextOrOnError.next)) { + if ( + observerOrOnNextOrOnError.error && + !isFunction(observerOrOnNextOrOnError.error) + ) { + throw new Error( + 'Query.onSnapshot failed: Observer.error must be a valid function.' + ); + } + observer = { + next: observerOrOnNextOrOnError.next, + error: observerOrOnNextOrOnError.error, + }; + } else { + throw new Error( + 'Query.onSnapshot failed: Observer.next must be a valid function.' + ); + } + } else { + throw new Error( + 'Query.onSnapshot failed: Second argument must be a function or observer.' + ); + } + } else { + throw new Error( + 'Query.onSnapshot failed: First argument must be a function, observer or options.' + ); + } + } else { + throw new Error( + 'Query.onSnapshot failed: Called with invalid arguments.' + ); + } + const listenerId = firestoreAutoId(); + + const listener = nativeQuerySnapshot => { + const querySnapshot = new QuerySnapshot( + this._firestore, + this, + nativeQuerySnapshot + ); + observer.next(querySnapshot); + }; + + // Listen to snapshot events + SharedEventEmitter.addListener( + getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`), + listener + ); + + // Listen for snapshot error events + if (observer.error) { + SharedEventEmitter.addListener( + getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`), + observer.error + ); + } + + // Add the native listener + getNativeModule(this._firestore).collectionOnSnapshot( + this._referencePath.relativeName, + this._fieldFilters, + this._fieldOrders, + this._queryOptions, + listenerId, + queryListenOptions + ); + + // Return an unsubscribe method + return this._offCollectionSnapshot.bind(this, listenerId, listener); + } + + orderBy( + fieldPath: string | FieldPath, + directionStr?: QueryDirection = 'asc' + ): Query { + // TODO: Validation + // validate.isFieldPath('fieldPath', fieldPath); + // validate.isOptionalFieldOrder('directionStr', directionStr); + + if ( + this._queryOptions.startAt || + this._queryOptions.startAfter || + this._queryOptions.endAt || + this._queryOptions.endBefore + ) { + throw new Error( + 'Cannot specify an orderBy() constraint after calling ' + + 'startAt(), startAfter(), endBefore() or endAt().' + ); + } + + const newOrder: FieldOrder = { + direction: DIRECTIONS[directionStr], + fieldPath: buildNativeFieldPath(fieldPath), + }; + const combinedOrders = this._fieldOrders.concat(newOrder); + return new Query( + this.firestore, + this._referencePath, + this._fieldFilters, + combinedOrders, + this._queryOptions + ); + } + + startAfter(...snapshotOrVarArgs: any[]): Query { + const options = { + ...this._queryOptions, + startAfter: this._buildOrderByOption(snapshotOrVarArgs), + }; + + return new Query( + this.firestore, + this._referencePath, + this._fieldFilters, + this._fieldOrders, + options + ); + } + + startAt(...snapshotOrVarArgs: any[]): Query { + const options = { + ...this._queryOptions, + startAt: this._buildOrderByOption(snapshotOrVarArgs), + }; + + return new Query( + this.firestore, + this._referencePath, + this._fieldFilters, + this._fieldOrders, + options + ); + } + + where( + fieldPath: string | FieldPath, + opStr: QueryOperator, + value: any + ): Query { + // TODO: Validation + // validate.isFieldPath('fieldPath', fieldPath); + // validate.isFieldFilter('fieldFilter', opStr, value); + const nativeValue = buildTypeMap(value); + const newFilter: FieldFilter = { + fieldPath: buildNativeFieldPath(fieldPath), + operator: OPERATORS[opStr], + value: nativeValue, + }; + const combinedFilters = this._fieldFilters.concat(newFilter); + return new Query( + this.firestore, + this._referencePath, + combinedFilters, + this._fieldOrders, + this._queryOptions + ); + } + + /** + * INTERNALS + */ + + _buildOrderByOption(snapshotOrVarArgs: any[]) { + // TODO: Validation + let values; + if ( + snapshotOrVarArgs.length === 1 && + snapshotOrVarArgs[0] instanceof DocumentSnapshot + ) { + const docSnapshot: DocumentSnapshot = snapshotOrVarArgs[0]; + values = []; + for (let i = 0; i < this._fieldOrders.length; i++) { + const fieldOrder = this._fieldOrders[i]; + if ( + fieldOrder.fieldPath.type === 'string' && + fieldOrder.fieldPath.string + ) { + values.push(docSnapshot.get(fieldOrder.fieldPath.string)); + } else if (fieldOrder.fieldPath.fieldpath) { + const fieldPath = new FieldPath(...fieldOrder.fieldPath.fieldpath); + values.push(docSnapshot.get(fieldPath)); + } + } + } else { + values = snapshotOrVarArgs; + } + + return buildNativeArray(values); + } + + /** + * Remove query snapshot listener + * @param listener + */ + _offCollectionSnapshot(listenerId: string, listener: Function) { + getLogger(this._firestore).info('Removing onQuerySnapshot listener'); + SharedEventEmitter.removeListener( + getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`), + listener + ); + SharedEventEmitter.removeListener( + getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`), + listener + ); + getNativeModule(this._firestore).collectionOffSnapshot( + this._referencePath.relativeName, + this._fieldFilters, + this._fieldOrders, + this._queryOptions, + listenerId + ); + } +} diff --git a/tests-new/firebase/modules/firestore/QuerySnapshot.js b/tests-new/firebase/modules/firestore/QuerySnapshot.js new file mode 100644 index 00000000..d7748cf8 --- /dev/null +++ b/tests-new/firebase/modules/firestore/QuerySnapshot.js @@ -0,0 +1,78 @@ +/** + * @flow + * QuerySnapshot representation wrapper + */ +import DocumentChange from './DocumentChange'; +import DocumentSnapshot from './DocumentSnapshot'; + +import type Firestore from './'; +import type { + NativeDocumentChange, + NativeDocumentSnapshot, + SnapshotMetadata, +} from './types'; +import type Query from './Query'; + +type NativeQuerySnapshot = { + changes: NativeDocumentChange[], + documents: NativeDocumentSnapshot[], + metadata: SnapshotMetadata, +}; + +/** + * @class QuerySnapshot + */ +export default class QuerySnapshot { + _changes: DocumentChange[]; + _docs: DocumentSnapshot[]; + _metadata: SnapshotMetadata; + _query: Query; + + constructor( + firestore: Firestore, + query: Query, + nativeData: NativeQuerySnapshot + ) { + this._changes = nativeData.changes.map( + change => new DocumentChange(firestore, change) + ); + this._docs = nativeData.documents.map( + doc => new DocumentSnapshot(firestore, doc) + ); + this._metadata = nativeData.metadata; + this._query = query; + } + + get docChanges(): DocumentChange[] { + return this._changes; + } + + get docs(): DocumentSnapshot[] { + return this._docs; + } + + get empty(): boolean { + return this._docs.length === 0; + } + + get metadata(): SnapshotMetadata { + return this._metadata; + } + + get query(): Query { + return this._query; + } + + get size(): number { + return this._docs.length; + } + + forEach(callback: DocumentSnapshot => any) { + // TODO: Validation + // validate.isFunction('callback', callback); + + this._docs.forEach(doc => { + callback(doc); + }); + } +} diff --git a/tests-new/firebase/modules/firestore/Transaction.js b/tests-new/firebase/modules/firestore/Transaction.js new file mode 100644 index 00000000..1fa0ce13 --- /dev/null +++ b/tests-new/firebase/modules/firestore/Transaction.js @@ -0,0 +1,151 @@ +/** + * @flow + * Firestore Transaction representation wrapper + */ +import { parseUpdateArgs } from './utils'; +import { buildNativeMap } from './utils/serialize'; + +import type Firestore from './'; +import type { TransactionMeta } from './TransactionHandler'; +import type DocumentReference from './DocumentReference'; +import DocumentSnapshot from './DocumentSnapshot'; +import { getNativeModule } from '../../utils/native'; + +type Command = { + type: 'set' | 'update' | 'delete', + path: string, + data?: { [string]: any }, + options?: SetOptions | {}, +}; + +type SetOptions = { + merge: boolean, +}; + +// TODO docs state all get requests must be made FIRST before any modifications +// TODO so need to validate that + +/** + * @class Transaction + */ +export default class Transaction { + _pendingResult: ?any; + _firestore: Firestore; + _meta: TransactionMeta; + _commandBuffer: Array; + + constructor(firestore: Firestore, meta: TransactionMeta) { + this._meta = meta; + this._commandBuffer = []; + this._firestore = firestore; + this._pendingResult = undefined; + } + + /** + * ------------- + * INTERNAL API + * ------------- + */ + + /** + * Clears the command buffer and any pending result in prep for + * the next transaction iteration attempt. + * + * @private + */ + _prepare() { + this._commandBuffer = []; + this._pendingResult = undefined; + } + + /** + * ------------- + * PUBLIC API + * ------------- + */ + + /** + * Reads the document referenced by the provided DocumentReference. + * + * @param documentRef DocumentReference A reference to the document to be retrieved. Value must not be null. + * + * @returns Promise + */ + get(documentRef: DocumentReference): Promise { + // todo validate doc ref + return getNativeModule(this._firestore) + .transactionGetDocument(this._meta.id, documentRef.path) + .then(result => new DocumentSnapshot(this._firestore, result)); + } + + /** + * Writes to the document referred to by the provided DocumentReference. + * If the document does not exist yet, it will be created. If you pass options, + * the provided data can be merged into the existing document. + * + * @param documentRef DocumentReference A reference to the document to be created. Value must not be null. + * @param data Object An object of the fields and values for the document. + * @param options SetOptions An object to configure the set behavior. + * Pass {merge: true} to only replace the values specified in the data argument. + * Fields omitted will remain untouched. + * + * @returns {Transaction} + */ + set( + documentRef: DocumentReference, + data: Object, + options?: SetOptions + ): Transaction { + // todo validate doc ref + // todo validate data is object + this._commandBuffer.push({ + type: 'set', + path: documentRef.path, + data: buildNativeMap(data), + options: options || {}, + }); + + return this; + } + + /** + * Updates fields in the document referred to by this DocumentReference. + * The update will fail if applied to a document that does not exist. Nested + * fields can be updated by providing dot-separated field path strings or by providing FieldPath objects. + * + * @param documentRef DocumentReference A reference to the document to be updated. Value must not be null. + * @param args any Either an object containing all of the fields and values to update, + * or a series of arguments alternating between fields (as string or FieldPath + * objects) and values. + * + * @returns {Transaction} + */ + update(documentRef: DocumentReference, ...args: Array): Transaction { + // todo validate doc ref + const data = parseUpdateArgs(args, 'Transaction.update'); + this._commandBuffer.push({ + type: 'update', + path: documentRef.path, + data: buildNativeMap(data), + }); + + return this; + } + + /** + * Deletes the document referred to by the provided DocumentReference. + * + * @param documentRef DocumentReference A reference to the document to be deleted. Value must not be null. + * + * @returns {Transaction} + */ + delete(documentRef: DocumentReference): Transaction { + // todo validate doc ref + this._commandBuffer.push({ + type: 'delete', + path: documentRef.path, + }); + + return this; + } +} diff --git a/tests-new/firebase/modules/firestore/TransactionHandler.js b/tests-new/firebase/modules/firestore/TransactionHandler.js new file mode 100644 index 00000000..1f424a80 --- /dev/null +++ b/tests-new/firebase/modules/firestore/TransactionHandler.js @@ -0,0 +1,241 @@ +/** + * @flow + * Firestore Transaction representation wrapper + */ +import { getAppEventName, SharedEventEmitter } from '../../utils/events'; +import { getNativeModule } from '../../utils/native'; +import Transaction from './Transaction'; +import type Firestore from './'; + +let transactionId = 0; + +/** + * Uses the push id generator to create a transaction id + * @returns {number} + * @private + */ +const generateTransactionId = (): number => transactionId++; + +export type TransactionMeta = { + id: number, + stack: string[], + reject?: Function, + resolve?: Function, + transaction: Transaction, + updateFunction: (transaction: Transaction) => Promise, +}; + +type TransactionEvent = { + id: number, + type: 'update' | 'error' | 'complete', + error: ?{ code: string, message: string }, +}; + +/** + * @class TransactionHandler + */ +export default class TransactionHandler { + _firestore: Firestore; + _pending: { + [number]: { + meta: TransactionMeta, + transaction: Transaction, + }, + }; + + constructor(firestore: Firestore) { + this._pending = {}; + this._firestore = firestore; + SharedEventEmitter.addListener( + getAppEventName(this._firestore, 'firestore_transaction_event'), + this._handleTransactionEvent.bind(this) + ); + } + + /** + * ------------- + * INTERNAL API + * ------------- + */ + + /** + * Add a new transaction and start it natively. + * @param updateFunction + */ + _add( + updateFunction: (transaction: Transaction) => Promise + ): Promise { + const id = generateTransactionId(); + // $FlowExpectedError: Transaction has to be populated + const meta: TransactionMeta = { + id, + updateFunction, + stack: new Error().stack + .split('\n') + .slice(4) + .join('\n'), + }; + + this._pending[id] = { + meta, + transaction: new Transaction(this._firestore, meta), + }; + + // deferred promise + return new Promise((resolve, reject) => { + getNativeModule(this._firestore).transactionBegin(id); + meta.resolve = r => { + resolve(r); + this._remove(id); + }; + meta.reject = e => { + reject(e); + this._remove(id); + }; + }); + } + + /** + * Destroys a local instance of a transaction meta + * + * @param id + * @private + */ + _remove(id) { + getNativeModule(this._firestore).transactionDispose(id); + delete this._pending[id]; + } + + /** + * ------------- + * EVENTS + * ------------- + */ + + /** + * Handles incoming native transaction events and distributes to correct + * internal handler by event.type + * + * @param event + * @returns {*} + * @private + */ + _handleTransactionEvent(event: TransactionEvent) { + // eslint-disable-next-line default-case + switch (event.type) { + case 'update': + this._handleUpdate(event); + break; + case 'error': + this._handleError(event); + break; + case 'complete': + this._handleComplete(event); + break; + } + } + + /** + * Handles incoming native transaction update events + * + * @param event + * @private + */ + async _handleUpdate(event: TransactionEvent) { + const { id } = event; + // abort if no longer exists js side + if (!this._pending[id]) return this._remove(id); + + const { meta, transaction } = this._pending[id]; + const { updateFunction, reject } = meta; + + // clear any saved state from previous transaction runs + transaction._prepare(); + + let finalError; + let updateFailed; + let pendingResult; + + // run the users custom update functionality + try { + const possiblePromise = updateFunction(transaction); + + // validate user has returned a promise in their update function + // TODO must it actually return a promise? Can't find any usages of it without one... + if (!possiblePromise || !possiblePromise.then) { + finalError = new Error( + 'Update function for `firestore.runTransaction(updateFunction)` must return a Promise.' + ); + } else { + pendingResult = await possiblePromise; + } + } catch (exception) { + // exception can still be falsey if user `Promise.reject();` 's with no args + // so we track the exception with a updateFailed boolean to ensure no fall-through + updateFailed = true; + finalError = exception; + } + + // reject the final promise and remove from native + // update is failed when either the users updateFunction + // throws an error or rejects a promise + if (updateFailed) { + // $FlowExpectedError: Reject will always be present + return reject(finalError); + } + + // capture the resolved result as we'll need this + // to resolve the runTransaction() promise when + // native emits that the transaction is final + transaction._pendingResult = pendingResult; + + // send the buffered update/set/delete commands for native to process + return getNativeModule(this._firestore).transactionApplyBuffer( + id, + transaction._commandBuffer + ); + } + + /** + * Handles incoming native transaction error events + * + * @param event + * @private + */ + _handleError(event: TransactionEvent) { + const { id, error } = event; + const { meta } = this._pending[id]; + + if (meta && error) { + const { code, message } = error; + // build a JS error and replace its stack + // with the captured one at start of transaction + // so it's actually relevant to the user + const errorWithStack = new Error(message); + // $FlowExpectedError: code is needed for Firebase errors + errorWithStack.code = code; + // $FlowExpectedError: stack should be a stack trace + errorWithStack.stack = meta.stack; + + // $FlowExpectedError: Reject will always be present + meta.reject(errorWithStack); + } + } + + /** + * Handles incoming native transaction complete events + * + * @param event + * @private + */ + _handleComplete(event: TransactionEvent) { + const { id } = event; + const { meta, transaction } = this._pending[id]; + + if (meta) { + const pendingResult = transaction._pendingResult; + // $FlowExpectedError: Resolve will always be present + meta.resolve(pendingResult); + } + } +} diff --git a/tests-new/firebase/modules/firestore/WriteBatch.js b/tests-new/firebase/modules/firestore/WriteBatch.js new file mode 100644 index 00000000..5f4fd163 --- /dev/null +++ b/tests-new/firebase/modules/firestore/WriteBatch.js @@ -0,0 +1,76 @@ +/** + * @flow + * WriteBatch representation wrapper + */ +import { parseUpdateArgs } from './utils'; +import { buildNativeMap } from './utils/serialize'; +import { getNativeModule } from '../../utils/native'; + +import type DocumentReference from './DocumentReference'; +import type Firestore from './'; +import type { SetOptions } from './types'; + +type DocumentWrite = { + data?: Object, + options?: Object, + path: string, + type: 'DELETE' | 'SET' | 'UPDATE', +}; + +/** + * @class WriteBatch + */ +export default class WriteBatch { + _firestore: Firestore; + _writes: DocumentWrite[]; + + constructor(firestore: Firestore) { + this._firestore = firestore; + this._writes = []; + } + + commit(): Promise { + return getNativeModule(this._firestore).documentBatch(this._writes); + } + + delete(docRef: DocumentReference): WriteBatch { + // TODO: Validation + // validate.isDocumentReference('docRef', docRef); + // validate.isOptionalPrecondition('deleteOptions', deleteOptions); + this._writes.push({ + path: docRef.path, + type: 'DELETE', + }); + + return this; + } + + set(docRef: DocumentReference, data: Object, options?: SetOptions) { + // TODO: Validation + // validate.isDocumentReference('docRef', docRef); + // validate.isDocument('data', data); + // validate.isOptionalPrecondition('options', writeOptions); + const nativeData = buildNativeMap(data); + this._writes.push({ + data: nativeData, + options, + path: docRef.path, + type: 'SET', + }); + + return this; + } + + update(docRef: DocumentReference, ...args: any[]): WriteBatch { + // TODO: Validation + // validate.isDocumentReference('docRef', docRef); + const data = parseUpdateArgs(args, 'WriteBatch.update'); + this._writes.push({ + data: buildNativeMap(data), + path: docRef.path, + type: 'UPDATE', + }); + + return this; + } +} diff --git a/tests-new/firebase/modules/firestore/index.js b/tests-new/firebase/modules/firestore/index.js new file mode 100644 index 00000000..e8047894 --- /dev/null +++ b/tests-new/firebase/modules/firestore/index.js @@ -0,0 +1,247 @@ +/** + * @flow + * Firestore representation wrapper + */ +import { NativeModules } from 'react-native'; + +import { getAppEventName, SharedEventEmitter } from '../../utils/events'; +import ModuleBase from '../../utils/ModuleBase'; +import CollectionReference from './CollectionReference'; +import DocumentReference from './DocumentReference'; +import FieldPath from './FieldPath'; +import FieldValue from './FieldValue'; +import GeoPoint from './GeoPoint'; +import Path from './Path'; +import WriteBatch from './WriteBatch'; +import TransactionHandler from './TransactionHandler'; +import Transaction from './Transaction'; +import INTERNALS from '../../utils/internals'; + +import type DocumentSnapshot from './DocumentSnapshot'; +import type App from '../core/app'; +import type QuerySnapshot from './QuerySnapshot'; + +type CollectionSyncEvent = { + appName: string, + querySnapshot?: QuerySnapshot, + error?: Object, + listenerId: string, + path: string, +}; + +type DocumentSyncEvent = { + appName: string, + documentSnapshot?: DocumentSnapshot, + error?: Object, + listenerId: string, + path: string, +}; + +const NATIVE_EVENTS = [ + 'firestore_transaction_event', + 'firestore_document_sync_event', + 'firestore_collection_sync_event', +]; + +export const MODULE_NAME = 'RNFirebaseFirestore'; +export const NAMESPACE = 'firestore'; + +/** + * @class Firestore + */ +export default class Firestore extends ModuleBase { + _referencePath: Path; + _transactionHandler: TransactionHandler; + + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + moduleName: MODULE_NAME, + multiApp: true, + hasShards: false, + namespace: NAMESPACE, + }); + + this._referencePath = new Path([]); + this._transactionHandler = new TransactionHandler(this); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onCollectionSnapshot + getAppEventName(this, 'firestore_collection_sync_event'), + this._onCollectionSyncEvent.bind(this) + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onDocumentSnapshot + getAppEventName(this, 'firestore_document_sync_event'), + this._onDocumentSyncEvent.bind(this) + ); + } + + /** + * ------------- + * PUBLIC API + * ------------- + */ + + /** + * Creates a write batch, used for performing multiple writes as a single atomic operation. + * + * @returns {WriteBatch} + */ + batch(): WriteBatch { + return new WriteBatch(this); + } + + /** + * Gets a CollectionReference instance that refers to the collection at the specified path. + * + * @param collectionPath + * @returns {CollectionReference} + */ + collection(collectionPath: string): CollectionReference { + const path = this._referencePath.child(collectionPath); + if (!path.isCollection) { + throw new Error('Argument "collectionPath" must point to a collection.'); + } + + return new CollectionReference(this, path); + } + + /** + * Gets a DocumentReference instance that refers to the document at the specified path. + * + * @param documentPath + * @returns {DocumentReference} + */ + doc(documentPath: string): DocumentReference { + const path = this._referencePath.child(documentPath); + if (!path.isDocument) { + throw new Error('Argument "documentPath" must point to a document.'); + } + + return new DocumentReference(this, path); + } + + /** + * Executes the given updateFunction and then attempts to commit the + * changes applied within the transaction. If any document read within + * the transaction has changed, Cloud Firestore retries the updateFunction. + * + * If it fails to commit after 5 attempts, the transaction fails. + * + * @param updateFunction + * @returns {void|Promise} + */ + runTransaction( + updateFunction: (transaction: Transaction) => Promise + ): Promise { + return this._transactionHandler._add(updateFunction); + } + + /** + * ------------- + * UNSUPPORTED + * ------------- + */ + + setLogLevel(): void { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'firestore', + 'setLogLevel' + ) + ); + } + + enableNetwork(): void { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'firestore', + 'enableNetwork' + ) + ); + } + + disableNetwork(): void { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'firestore', + 'disableNetwork' + ) + ); + } + + /** + * ------------- + * MISC + * ------------- + */ + + enablePersistence(): Promise { + throw new Error('Persistence is enabled by default on the Firestore SDKs'); + } + + settings(): void { + throw new Error('firebase.firestore().settings() coming soon'); + } + + /** + * ------------- + * INTERNALS + * ------------- + */ + + /** + * Internal collection sync listener + * + * @param event + * @private + */ + _onCollectionSyncEvent(event: CollectionSyncEvent) { + if (event.error) { + SharedEventEmitter.emit( + getAppEventName(this, `onQuerySnapshotError:${event.listenerId}`), + event.error + ); + } else { + SharedEventEmitter.emit( + getAppEventName(this, `onQuerySnapshot:${event.listenerId}`), + event.querySnapshot + ); + } + } + + /** + * Internal document sync listener + * + * @param event + * @private + */ + _onDocumentSyncEvent(event: DocumentSyncEvent) { + if (event.error) { + SharedEventEmitter.emit( + getAppEventName(this, `onDocumentSnapshotError:${event.listenerId}`), + event.error + ); + } else { + SharedEventEmitter.emit( + getAppEventName(this, `onDocumentSnapshot:${event.listenerId}`), + event.documentSnapshot + ); + } + } +} + +export const statics = { + FieldPath, + FieldValue, + GeoPoint, + enableLogging(enabled: boolean) { + if (NativeModules[MODULE_NAME]) { + NativeModules[MODULE_NAME].enableLogging(enabled); + } + }, +}; diff --git a/tests-new/firebase/modules/firestore/types.js b/tests-new/firebase/modules/firestore/types.js new file mode 100644 index 00000000..6ed935dc --- /dev/null +++ b/tests-new/firebase/modules/firestore/types.js @@ -0,0 +1,54 @@ +/* + * @flow + */ + +export type DocumentListenOptions = { + includeMetadataChanges: boolean, +}; + +export type QueryDirection = 'DESC' | 'desc' | 'ASC' | 'asc'; + +export type QueryListenOptions = {| + includeDocumentMetadataChanges: boolean, + includeQueryMetadataChanges: boolean, +|}; + +export type QueryOperator = '<' | '<=' | '=' | '==' | '>' | '>='; + +export type SetOptions = { + merge?: boolean, +}; + +export type SnapshotMetadata = { + fromCache: boolean, + hasPendingWrites: boolean, +}; + +export type NativeDocumentChange = { + document: NativeDocumentSnapshot, + newIndex: number, + oldIndex: number, + type: string, +}; + +export type NativeDocumentSnapshot = { + data: { [string]: NativeTypeMap }, + metadata: SnapshotMetadata, + path: string, +}; + +export type NativeTypeMap = { + type: + | 'array' + | 'boolean' + | 'date' + | 'documentid' + | 'fieldvalue' + | 'geopoint' + | 'null' + | 'number' + | 'object' + | 'reference' + | 'string', + value: any, +}; diff --git a/tests-new/firebase/modules/firestore/utils/index.js b/tests-new/firebase/modules/firestore/utils/index.js new file mode 100644 index 00000000..6aabeeaf --- /dev/null +++ b/tests-new/firebase/modules/firestore/utils/index.js @@ -0,0 +1,74 @@ +/** + * @flow + */ +import FieldPath from '../FieldPath'; +import { isObject, isString } from '../../../utils'; + +const buildFieldPathData = (segments: string[], value: any): Object => { + if (segments.length === 1) { + return { + [segments[0]]: value, + }; + } + return { + [segments[0]]: buildFieldPathData(segments.slice(1), value), + }; +}; + +// eslint-disable-next-line import/prefer-default-export +export const mergeFieldPathData = ( + data: Object, + segments: string[], + value: any +): Object => { + if (segments.length === 1) { + return { + ...data, + [segments[0]]: value, + }; + } else if (data[segments[0]]) { + return { + ...data, + [segments[0]]: mergeFieldPathData( + data[segments[0]], + segments.slice(1), + value + ), + }; + } + return { + ...data, + [segments[0]]: buildFieldPathData(segments.slice(1), value), + }; +}; + +export const parseUpdateArgs = (args: any[], methodName: string) => { + let data = {}; + if (args.length === 1) { + if (!isObject(args[0])) { + throw new Error( + `${methodName} failed: If using a single update argument, it must be an object.` + ); + } + [data] = args; + } else if (args.length % 2 === 1) { + throw new Error( + `${methodName} failed: The update arguments must be either a single object argument, or equal numbers of key/value pairs.` + ); + } else { + for (let i = 0; i < args.length; i += 2) { + const key = args[i]; + const value = args[i + 1]; + if (isString(key)) { + data[key] = value; + } else if (key instanceof FieldPath) { + data = mergeFieldPathData(data, key._segments, value); + } else { + throw new Error( + `${methodName} failed: Argument at index ${i} must be a string or FieldPath` + ); + } + } + } + return data; +}; diff --git a/tests-new/firebase/modules/firestore/utils/serialize.js b/tests-new/firebase/modules/firestore/utils/serialize.js new file mode 100644 index 00000000..8ea8a5d7 --- /dev/null +++ b/tests-new/firebase/modules/firestore/utils/serialize.js @@ -0,0 +1,162 @@ +/** + * @flow + */ + +import DocumentReference from '../DocumentReference'; +import { DOCUMENT_ID } from '../FieldPath'; +import { + DELETE_FIELD_VALUE, + SERVER_TIMESTAMP_FIELD_VALUE, +} from '../FieldValue'; +import GeoPoint from '../GeoPoint'; +import Path from '../Path'; +import { typeOf } from '../../../utils'; + +import type Firestore from '../'; +import type { NativeTypeMap } from '../types'; + +/* + * Functions that build up the data needed to represent + * the different types available within Firestore + * for transmission to the native side + */ + +export const buildNativeMap = (data: Object): { [string]: NativeTypeMap } => { + const nativeData = {}; + if (data) { + Object.keys(data).forEach(key => { + const typeMap = buildTypeMap(data[key]); + if (typeMap) { + nativeData[key] = typeMap; + } + }); + } + return nativeData; +}; + +export const buildNativeArray = (array: Object[]): NativeTypeMap[] => { + const nativeArray = []; + if (array) { + array.forEach(value => { + const typeMap = buildTypeMap(value); + if (typeMap) { + nativeArray.push(typeMap); + } + }); + } + return nativeArray; +}; + +export const buildTypeMap = (value: any): NativeTypeMap | null => { + const type = typeOf(value); + if (value === null || value === undefined || Number.isNaN(value)) { + return { + type: 'null', + value: null, + }; + } else if (value === DELETE_FIELD_VALUE) { + return { + type: 'fieldvalue', + value: 'delete', + }; + } else if (value === SERVER_TIMESTAMP_FIELD_VALUE) { + return { + type: 'fieldvalue', + value: 'timestamp', + }; + } else if (value === DOCUMENT_ID) { + return { + type: 'documentid', + value: null, + }; + } else if (type === 'boolean' || type === 'number' || type === 'string') { + return { + type, + value, + }; + } else if (type === 'array') { + return { + type, + value: buildNativeArray(value), + }; + } else if (type === 'object') { + if (value instanceof DocumentReference) { + return { + type: 'reference', + value: value.path, + }; + } else if (value instanceof GeoPoint) { + return { + type: 'geopoint', + value: { + latitude: value.latitude, + longitude: value.longitude, + }, + }; + } else if (value instanceof Date) { + return { + type: 'date', + value: value.getTime(), + }; + } + return { + type: 'object', + value: buildNativeMap(value), + }; + } + console.warn(`Unknown data type received ${type}`); + return null; +}; + +/* + * Functions that parse the received from the native + * side and converts to the correct Firestore JS types + */ + +export const parseNativeMap = ( + firestore: Firestore, + nativeData: { [string]: NativeTypeMap } +): Object | void => { + let data; + if (nativeData) { + data = {}; + Object.keys(nativeData).forEach(key => { + data[key] = parseTypeMap(firestore, nativeData[key]); + }); + } + return data; +}; + +const parseNativeArray = ( + firestore: Firestore, + nativeArray: NativeTypeMap[] +): any[] => { + const array = []; + if (nativeArray) { + nativeArray.forEach(typeMap => { + array.push(parseTypeMap(firestore, typeMap)); + }); + } + return array; +}; + +const parseTypeMap = (firestore: Firestore, typeMap: NativeTypeMap): any => { + const { type, value } = typeMap; + if (type === 'null') { + return null; + } else if (type === 'boolean' || type === 'number' || type === 'string') { + return value; + } else if (type === 'array') { + return parseNativeArray(firestore, value); + } else if (type === 'object') { + return parseNativeMap(firestore, value); + } else if (type === 'reference') { + return new DocumentReference(firestore, Path.fromName(value)); + } else if (type === 'geopoint') { + return new GeoPoint(value.latitude, value.longitude); + } else if (type === 'date') { + return new Date(value); + } + console.warn(`Unknown data type received ${type}`); + return value; +}; diff --git a/tests-new/firebase/modules/instanceid/index.js b/tests-new/firebase/modules/instanceid/index.js new file mode 100644 index 00000000..0cd6528d --- /dev/null +++ b/tests-new/firebase/modules/instanceid/index.js @@ -0,0 +1,32 @@ +/** + * @flow + * Instance ID representation wrapper + */ +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; + +import type App from '../core/app'; + +export const MODULE_NAME = 'RNFirebaseInstanceId'; +export const NAMESPACE = 'instanceid'; + +export default class InstanceId extends ModuleBase { + constructor(app: App) { + super(app, { + hasShards: false, + moduleName: MODULE_NAME, + multiApp: false, + namespace: NAMESPACE, + }); + } + + delete(): Promise { + return getNativeModule(this).delete(); + } + + get(): Promise { + return getNativeModule(this).get(); + } +} + +export const statics = {}; diff --git a/tests-new/firebase/modules/invites/AndroidInvitation.js b/tests-new/firebase/modules/invites/AndroidInvitation.js new file mode 100644 index 00000000..31a87d51 --- /dev/null +++ b/tests-new/firebase/modules/invites/AndroidInvitation.js @@ -0,0 +1,69 @@ +/** + * @flow + * AndroidInvitation representation wrapper + */ +import type Invitation from './Invitation'; +import type { NativeAndroidInvitation } from './types'; + +export default class AndroidInvitation { + _additionalReferralParameters: { [string]: string } | void; + _emailHtmlContent: string | void; + _emailSubject: string | void; + _googleAnalyticsTrackingId: string | void; + _invitation: Invitation; + + constructor(invitation: Invitation) { + this._invitation = invitation; + } + + /** + * + * @param additionalReferralParameters + * @returns {Invitation} + */ + setAdditionalReferralParameters(additionalReferralParameters: { + [string]: string, + }): Invitation { + this._additionalReferralParameters = additionalReferralParameters; + return this._invitation; + } + + /** + * + * @param emailHtmlContent + * @returns {Invitation} + */ + setEmailHtmlContent(emailHtmlContent: string): Invitation { + this._emailHtmlContent = emailHtmlContent; + return this._invitation; + } + + /** + * + * @param emailSubject + * @returns {Invitation} + */ + setEmailSubject(emailSubject: string): Invitation { + this._emailSubject = emailSubject; + return this._invitation; + } + + /** + * + * @param googleAnalyticsTrackingId + * @returns {Invitation} + */ + setGoogleAnalyticsTrackingId(googleAnalyticsTrackingId: string): Invitation { + this._googleAnalyticsTrackingId = googleAnalyticsTrackingId; + return this._invitation; + } + + build(): NativeAndroidInvitation { + return { + additionalReferralParameters: this._additionalReferralParameters, + emailHtmlContent: this._emailHtmlContent, + emailSubject: this._emailSubject, + googleAnalyticsTrackingId: this._googleAnalyticsTrackingId, + }; + } +} diff --git a/tests-new/firebase/modules/invites/Invitation.js b/tests-new/firebase/modules/invites/Invitation.js new file mode 100644 index 00000000..04c0b0aa --- /dev/null +++ b/tests-new/firebase/modules/invites/Invitation.js @@ -0,0 +1,110 @@ +/** + * @flow + * Invitation representation wrapper + */ +import { Platform } from 'react-native'; +import AndroidInvitation from './AndroidInvitation'; + +import type { NativeInvitation } from './types'; + +export default class Invitation { + _android: AndroidInvitation; + _androidClientId: string | void; + _androidMinimumVersionCode: number | void; + _callToActionText: string | void; + _customImage: string | void; + _deepLink: string | void; + _iosClientId: string | void; + _message: string; + _title: string; + + constructor(title: string, message: string) { + this._android = new AndroidInvitation(this); + this._message = message; + this._title = title; + } + + get android(): AndroidInvitation { + return this._android; + } + + /** + * + * @param androidClientId + * @returns {Invitation} + */ + setAndroidClientId(androidClientId: string): Invitation { + this._androidClientId = androidClientId; + return this; + } + + /** + * + * @param androidMinimumVersionCode + * @returns {Invitation} + */ + setAndroidMinimumVersionCode(androidMinimumVersionCode: number): Invitation { + this._androidMinimumVersionCode = androidMinimumVersionCode; + return this; + } + + /** + * + * @param callToActionText + * @returns {Invitation} + */ + setCallToActionText(callToActionText: string): Invitation { + this._callToActionText = callToActionText; + return this; + } + + /** + * + * @param customImage + * @returns {Invitation} + */ + setCustomImage(customImage: string): Invitation { + this._customImage = customImage; + return this; + } + + /** + * + * @param deepLink + * @returns {Invitation} + */ + setDeepLink(deepLink: string): Invitation { + this._deepLink = deepLink; + return this; + } + + /** + * + * @param iosClientId + * @returns {Invitation} + */ + setIOSClientId(iosClientId: string): Invitation { + this._iosClientId = iosClientId; + return this; + } + + build(): NativeInvitation { + if (!this._message) { + throw new Error('Invitation: Missing required `message` property'); + } else if (!this._title) { + throw new Error('Invitation: Missing required `title` property'); + } + + return { + android: Platform.OS === 'android' ? this._android.build() : undefined, + androidClientId: this._androidClientId, + androidMinimumVersionCode: this._androidMinimumVersionCode, + callToActionText: this._callToActionText, + customImage: this._customImage, + deepLink: this._deepLink, + iosClientId: this._iosClientId, + message: this._message, + title: this._title, + }; + } +} diff --git a/tests-new/firebase/modules/invites/index.js b/tests-new/firebase/modules/invites/index.js new file mode 100644 index 00000000..3ba53fae --- /dev/null +++ b/tests-new/firebase/modules/invites/index.js @@ -0,0 +1,78 @@ +/** + * @flow + * Invites representation wrapper + */ +import { SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; +import Invitation from './Invitation'; + +import type App from '../core/app'; + +export const MODULE_NAME = 'RNFirebaseInvites'; +export const NAMESPACE = 'invites'; +const NATIVE_EVENTS = ['invites_invitation_received']; + +type InvitationOpen = { + deepLink: string, + invitationId: string, +}; + +export default class Invites extends ModuleBase { + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + hasShards: false, + moduleName: MODULE_NAME, + multiApp: false, + namespace: NAMESPACE, + }); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onMessage + 'invites_invitation_received', + (invitation: InvitationOpen) => { + SharedEventEmitter.emit('onInvitation', invitation); + } + ); + } + + /** + * Returns the invitation that triggered application open + * @returns {Promise.} + */ + getInitialInvitation(): Promise { + return getNativeModule(this).getInitialInvitation(); + } + + /** + * Subscribe to invites + * @param listener + * @returns {Function} + */ + onInvitation(listener: InvitationOpen => any) { + getLogger(this).info('Creating onInvitation listener'); + + SharedEventEmitter.addListener('onInvitation', listener); + + return () => { + getLogger(this).info('Removing onInvitation listener'); + SharedEventEmitter.removeListener('onInvitation', listener); + }; + } + + sendInvitation(invitation: Invitation): Promise { + if (!(invitation instanceof Invitation)) { + throw new Error( + `Invites:sendInvitation expects an 'Invitation' but got type ${typeof invitation}` + ); + } + return getNativeModule(this).sendInvitation(invitation.build()); + } +} + +export const statics = { + Invitation, +}; diff --git a/tests-new/firebase/modules/invites/types.js b/tests-new/firebase/modules/invites/types.js new file mode 100644 index 00000000..45e24727 --- /dev/null +++ b/tests-new/firebase/modules/invites/types.js @@ -0,0 +1,21 @@ +/** + * @flow + */ +export type NativeAndroidInvitation = {| + additionalReferralParameters?: { [string]: string }, + emailHtmlContent?: string, + emailSubject?: string, + googleAnalyticsTrackingId?: string, +|}; + +export type NativeInvitation = {| + android?: NativeAndroidInvitation, + androidClientId?: string, + androidMinimumVersionCode?: number, + callToActionText?: string, + customImage?: string, + deepLink?: string, + iosClientId?: string, + message: string, + title: string, +|}; diff --git a/tests-new/firebase/modules/links/AnalyticsParameters.js b/tests-new/firebase/modules/links/AnalyticsParameters.js new file mode 100644 index 00000000..36e77209 --- /dev/null +++ b/tests-new/firebase/modules/links/AnalyticsParameters.js @@ -0,0 +1,79 @@ +/** + * @flow + * AnalyticsParameters representation wrapper + */ +import type DynamicLink from './DynamicLink'; +import type { NativeAnalyticsParameters } from './types'; + +export default class AnalyticsParameters { + _campaign: string | void; + _content: string | void; + _link: DynamicLink; + _medium: string | void; + _source: string | void; + _term: string | void; + + constructor(link: DynamicLink) { + this._link = link; + } + + /** + * + * @param campaign + * @returns {DynamicLink} + */ + setCampaign(campaign: string): DynamicLink { + this._campaign = campaign; + return this._link; + } + + /** + * + * @param content + * @returns {DynamicLink} + */ + setContent(content: string): DynamicLink { + this._content = content; + return this._link; + } + + /** + * + * @param medium + * @returns {DynamicLink} + */ + setMedium(medium: string): DynamicLink { + this._medium = medium; + return this._link; + } + + /** + * + * @param source + * @returns {DynamicLink} + */ + setSource(source: string): DynamicLink { + this._source = source; + return this._link; + } + + /** + * + * @param term + * @returns {DynamicLink} + */ + setTerm(term: string): DynamicLink { + this._term = term; + return this._link; + } + + build(): NativeAnalyticsParameters { + return { + campaign: this._campaign, + content: this._content, + medium: this._medium, + source: this._source, + term: this._term, + }; + } +} diff --git a/tests-new/firebase/modules/links/AndroidParameters.js b/tests-new/firebase/modules/links/AndroidParameters.js new file mode 100644 index 00000000..985bfd4b --- /dev/null +++ b/tests-new/firebase/modules/links/AndroidParameters.js @@ -0,0 +1,60 @@ +/** + * @flow + * AndroidParameters representation wrapper + */ +import type DynamicLink from './DynamicLink'; +import type { NativeAndroidParameters } from './types'; + +export default class AndroidParameters { + _fallbackUrl: string | void; + _link: DynamicLink; + _minimumVersion: number | void; + _packageName: string | void; + + constructor(link: DynamicLink) { + this._link = link; + } + + /** + * + * @param fallbackUrl + * @returns {DynamicLink} + */ + setFallbackUrl(fallbackUrl: string): DynamicLink { + this._fallbackUrl = fallbackUrl; + return this._link; + } + + /** + * + * @param minimumVersion + * @returns {DynamicLink} + */ + setMinimumVersion(minimumVersion: number): DynamicLink { + this._minimumVersion = minimumVersion; + return this._link; + } + + /** + * + * @param packageName + * @returns {DynamicLink} + */ + setPackageName(packageName: string): DynamicLink { + this._packageName = packageName; + return this._link; + } + + build(): NativeAndroidParameters { + if ((this._fallbackUrl || this._minimumVersion) && !this._packageName) { + throw new Error( + 'AndroidParameters: Missing required `packageName` property' + ); + } + return { + fallbackUrl: this._fallbackUrl, + minimumVersion: this._minimumVersion, + packageName: this._packageName, + }; + } +} diff --git a/tests-new/firebase/modules/links/DynamicLink.js b/tests-new/firebase/modules/links/DynamicLink.js new file mode 100644 index 00000000..2930a3d0 --- /dev/null +++ b/tests-new/firebase/modules/links/DynamicLink.js @@ -0,0 +1,79 @@ +/** + * @flow + * DynamicLink representation wrapper + */ +import AnalyticsParameters from './AnalyticsParameters'; +import AndroidParameters from './AndroidParameters'; +import IOSParameters from './IOSParameters'; +import ITunesParameters from './ITunesParameters'; +import NavigationParameters from './NavigationParameters'; +import SocialParameters from './SocialParameters'; + +import type { NativeDynamicLink } from './types'; + +export default class DynamicLink { + _analytics: AnalyticsParameters; + _android: AndroidParameters; + _dynamicLinkDomain: string; + _ios: IOSParameters; + _itunes: ITunesParameters; + _link: string; + _navigation: NavigationParameters; + _social: SocialParameters; + + constructor(link: string, dynamicLinkDomain: string) { + this._analytics = new AnalyticsParameters(this); + this._android = new AndroidParameters(this); + this._dynamicLinkDomain = dynamicLinkDomain; + this._ios = new IOSParameters(this); + this._itunes = new ITunesParameters(this); + this._link = link; + this._navigation = new NavigationParameters(this); + this._social = new SocialParameters(this); + } + + get analytics(): AnalyticsParameters { + return this._analytics; + } + + get android(): AndroidParameters { + return this._android; + } + + get ios(): IOSParameters { + return this._ios; + } + + get itunes(): ITunesParameters { + return this._itunes; + } + + get navigation(): NavigationParameters { + return this._navigation; + } + + get social(): SocialParameters { + return this._social; + } + + build(): NativeDynamicLink { + if (!this._link) { + throw new Error('DynamicLink: Missing required `link` property'); + } else if (!this._dynamicLinkDomain) { + throw new Error( + 'DynamicLink: Missing required `dynamicLinkDomain` property' + ); + } + + return { + analytics: this._analytics.build(), + android: this._android.build(), + dynamicLinkDomain: this._dynamicLinkDomain, + ios: this._ios.build(), + itunes: this._itunes.build(), + link: this._link, + navigation: this._navigation.build(), + social: this._social.build(), + }; + } +} diff --git a/tests-new/firebase/modules/links/IOSParameters.js b/tests-new/firebase/modules/links/IOSParameters.js new file mode 100644 index 00000000..1ac46631 --- /dev/null +++ b/tests-new/firebase/modules/links/IOSParameters.js @@ -0,0 +1,114 @@ +/** + * @flow + * IOSParameters representation wrapper + */ +import type DynamicLink from './DynamicLink'; +import type { NativeIOSParameters } from './types'; + +export default class IOSParameters { + _appStoreId: string | void; + _bundleId: string | void; + _customScheme: string | void; + _fallbackUrl: string | void; + _iPadBundleId: string | void; + _iPadFallbackUrl: string | void; + _link: DynamicLink; + _minimumVersion: string | void; + + constructor(link: DynamicLink) { + this._link = link; + } + + /** + * + * @param appStoreId + * @returns {DynamicLink} + */ + setAppStoreId(appStoreId: string): DynamicLink { + this._appStoreId = appStoreId; + return this._link; + } + + /** + * + * @param bundleId + * @returns {DynamicLink} + */ + setBundleId(bundleId: string): DynamicLink { + this._bundleId = bundleId; + return this._link; + } + + /** + * + * @param customScheme + * @returns {DynamicLink} + */ + setCustomScheme(customScheme: string): DynamicLink { + this._customScheme = customScheme; + return this._link; + } + + /** + * + * @param fallbackUrl + * @returns {DynamicLink} + */ + setFallbackUrl(fallbackUrl: string): DynamicLink { + this._fallbackUrl = fallbackUrl; + return this._link; + } + + /** + * + * @param iPadBundleId + * @returns {DynamicLink} + */ + setIPadBundleId(iPadBundleId: string): DynamicLink { + this._iPadBundleId = iPadBundleId; + return this._link; + } + + /** + * + * @param iPadFallbackUrl + * @returns {DynamicLink} + */ + setIPadFallbackUrl(iPadFallbackUrl: string): DynamicLink { + this._iPadFallbackUrl = iPadFallbackUrl; + return this._link; + } + + /** + * + * @param minimumVersion + * @returns {DynamicLink} + */ + setMinimumVersion(minimumVersion: string): DynamicLink { + this._minimumVersion = minimumVersion; + return this._link; + } + + build(): NativeIOSParameters { + if ( + (this._appStoreId || + this._customScheme || + this._fallbackUrl || + this._iPadBundleId || + this._iPadFallbackUrl || + this._minimumVersion) && + !this._bundleId + ) { + throw new Error('IOSParameters: Missing required `bundleId` property'); + } + return { + appStoreId: this._appStoreId, + bundleId: this._bundleId, + customScheme: this._customScheme, + fallbackUrl: this._fallbackUrl, + iPadBundleId: this._iPadBundleId, + iPadFallbackUrl: this._iPadFallbackUrl, + minimumVersion: this._minimumVersion, + }; + } +} diff --git a/tests-new/firebase/modules/links/ITunesParameters.js b/tests-new/firebase/modules/links/ITunesParameters.js new file mode 100644 index 00000000..784a99ff --- /dev/null +++ b/tests-new/firebase/modules/links/ITunesParameters.js @@ -0,0 +1,55 @@ +/** + * @flow + * ITunesParameters representation wrapper + */ +import type DynamicLink from './DynamicLink'; +import type { NativeITunesParameters } from './types'; + +export default class ITunesParameters { + _affiliateToken: string | void; + _campaignToken: string | void; + _link: DynamicLink; + _providerToken: string | void; + + constructor(link: DynamicLink) { + this._link = link; + } + + /** + * + * @param affiliateToken + * @returns {DynamicLink} + */ + setAffiliateToken(affiliateToken: string): DynamicLink { + this._affiliateToken = affiliateToken; + return this._link; + } + + /** + * + * @param campaignToken + * @returns {DynamicLink} + */ + setCampaignToken(campaignToken: string): DynamicLink { + this._campaignToken = campaignToken; + return this._link; + } + + /** + * + * @param providerToken + * @returns {DynamicLink} + */ + setProviderToken(providerToken: string): DynamicLink { + this._providerToken = providerToken; + return this._link; + } + + build(): NativeITunesParameters { + return { + affiliateToken: this._affiliateToken, + campaignToken: this._campaignToken, + providerToken: this._providerToken, + }; + } +} diff --git a/tests-new/firebase/modules/links/NavigationParameters.js b/tests-new/firebase/modules/links/NavigationParameters.js new file mode 100644 index 00000000..dbb408fb --- /dev/null +++ b/tests-new/firebase/modules/links/NavigationParameters.js @@ -0,0 +1,31 @@ +/** + * @flow + * NavigationParameters representation wrapper + */ +import type DynamicLink from './DynamicLink'; +import type { NativeNavigationParameters } from './types'; + +export default class NavigationParameters { + _forcedRedirectEnabled: string | void; + _link: DynamicLink; + + constructor(link: DynamicLink) { + this._link = link; + } + + /** + * + * @param forcedRedirectEnabled + * @returns {DynamicLink} + */ + setForcedRedirectEnabled(forcedRedirectEnabled: string): DynamicLink { + this._forcedRedirectEnabled = forcedRedirectEnabled; + return this._link; + } + + build(): NativeNavigationParameters { + return { + forcedRedirectEnabled: this._forcedRedirectEnabled, + }; + } +} diff --git a/tests-new/firebase/modules/links/SocialParameters.js b/tests-new/firebase/modules/links/SocialParameters.js new file mode 100644 index 00000000..be81386b --- /dev/null +++ b/tests-new/firebase/modules/links/SocialParameters.js @@ -0,0 +1,55 @@ +/** + * @flow + * SocialParameters representation wrapper + */ +import type DynamicLink from './DynamicLink'; +import type { NativeSocialParameters } from './types'; + +export default class SocialParameters { + _descriptionText: string | void; + _imageUrl: string | void; + _link: DynamicLink; + _title: string | void; + + constructor(link: DynamicLink) { + this._link = link; + } + + /** + * + * @param descriptionText + * @returns {DynamicLink} + */ + setDescriptionText(descriptionText: string): DynamicLink { + this._descriptionText = descriptionText; + return this._link; + } + + /** + * + * @param imageUrl + * @returns {DynamicLink} + */ + setImageUrl(imageUrl: string): DynamicLink { + this._imageUrl = imageUrl; + return this._link; + } + + /** + * + * @param title + * @returns {DynamicLink} + */ + setTitle(title: string): DynamicLink { + this._title = title; + return this._link; + } + + build(): NativeSocialParameters { + return { + descriptionText: this._descriptionText, + imageUrl: this._imageUrl, + title: this._title, + }; + } +} diff --git a/tests-new/firebase/modules/links/index.js b/tests-new/firebase/modules/links/index.js new file mode 100644 index 00000000..a926b182 --- /dev/null +++ b/tests-new/firebase/modules/links/index.js @@ -0,0 +1,97 @@ +/** + * @flow + * Dynamic Links representation wrapper + */ +import DynamicLink from './DynamicLink'; +import { SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; + +import type App from '../core/app'; + +const NATIVE_EVENTS = ['links_link_received']; + +export const MODULE_NAME = 'RNFirebaseLinks'; +export const NAMESPACE = 'links'; + +/** + * @class Links + */ +export default class Links extends ModuleBase { + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onMessage + 'links_link_received', + (link: string) => { + SharedEventEmitter.emit('onLink', link); + } + ); + } + + /** + * Create long Dynamic Link from parameters + * @param parameters + * @returns {Promise.} + */ + createDynamicLink(link: DynamicLink): Promise { + if (!(link instanceof DynamicLink)) { + throw new Error( + `Links:createDynamicLink expects a 'DynamicLink' but got type ${typeof link}` + ); + } + return getNativeModule(this).createDynamicLink(link.build()); + } + + /** + * Create short Dynamic Link from parameters + * @param parameters + * @returns {Promise.} + */ + createShortDynamicLink( + link: DynamicLink, + type?: 'SHORT' | 'UNGUESSABLE' + ): Promise { + if (!(link instanceof DynamicLink)) { + throw new Error( + `Links:createShortDynamicLink expects a 'DynamicLink' but got type ${typeof link}` + ); + } + return getNativeModule(this).createShortDynamicLink(link.build(), type); + } + + /** + * Returns the link that triggered application open + * @returns {Promise.} + */ + getInitialLink(): Promise { + return getNativeModule(this).getInitialLink(); + } + + /** + * Subscribe to dynamic links + * @param listener + * @returns {Function} + */ + onLink(listener: string => any): () => any { + getLogger(this).info('Creating onLink listener'); + + SharedEventEmitter.addListener('onLink', listener); + + return () => { + getLogger(this).info('Removing onLink listener'); + SharedEventEmitter.removeListener('onLink', listener); + }; + } +} + +export const statics = {}; diff --git a/tests-new/firebase/modules/links/types.js b/tests-new/firebase/modules/links/types.js new file mode 100644 index 00000000..27c947a4 --- /dev/null +++ b/tests-new/firebase/modules/links/types.js @@ -0,0 +1,53 @@ +/** + * @flow + */ +export type NativeAnalyticsParameters = {| + campaign?: string, + content?: string, + medium?: string, + source?: string, + term?: string, +|}; + +export type NativeAndroidParameters = {| + fallbackUrl?: string, + minimumVersion?: number, + packageName?: string, +|}; + +export type NativeIOSParameters = {| + appStoreId?: string, + bundleId?: string, + customScheme?: string, + fallbackUrl?: string, + iPadBundleId?: string, + iPadFallbackUrl?: string, + minimumVersion?: string, +|}; + +export type NativeITunesParameters = {| + affiliateToken?: string, + campaignToken?: string, + providerToken?: string, +|}; + +export type NativeNavigationParameters = {| + forcedRedirectEnabled?: string, +|}; + +export type NativeSocialParameters = {| + descriptionText?: string, + imageUrl?: string, + title?: string, +|}; + +export type NativeDynamicLink = {| + analytics: NativeAnalyticsParameters, + android: NativeAndroidParameters, + dynamicLinkDomain: string, + ios: NativeIOSParameters, + itunes: NativeITunesParameters, + link: string, + navigation: NativeNavigationParameters, + social: NativeSocialParameters, +|}; diff --git a/tests-new/firebase/modules/messaging/RemoteMessage.js b/tests-new/firebase/modules/messaging/RemoteMessage.js new file mode 100644 index 00000000..c40b06ff --- /dev/null +++ b/tests-new/firebase/modules/messaging/RemoteMessage.js @@ -0,0 +1,155 @@ +/** + * @flow + * RemoteMessage representation wrapper + */ +import { isObject, generatePushID } from './../../utils'; + +import type { + NativeInboundRemoteMessage, + NativeOutboundRemoteMessage, +} from './types'; + +export default class RemoteMessage { + _collapseKey: string | void; + _data: { [string]: string }; + _from: string | void; + _messageId: string; + _messageType: string | void; + _sentTime: number | void; + _to: string; + _ttl: number; + + constructor(inboundMessage?: NativeInboundRemoteMessage) { + if (inboundMessage) { + this._collapseKey = inboundMessage.collapseKey; + this._data = inboundMessage.data; + this._from = inboundMessage.from; + this._messageId = inboundMessage.messageId; + this._messageType = inboundMessage.messageType; + this._sentTime = inboundMessage.sentTime; + } + // defaults + this._data = this._data || {}; + // TODO: Is this the best way to generate an ID? + this._messageId = this._messageId || generatePushID(); + this._ttl = 3600; + } + + get collapseKey(): ?string { + return this._collapseKey; + } + + get data(): { [string]: string } { + return this._data; + } + + get from(): ?string { + return this._from; + } + + get messageId(): ?string { + return this._messageId; + } + + get messageType(): ?string { + return this._messageType; + } + + get sentTime(): ?number { + return this._sentTime; + } + + get to(): ?string { + return this._to; + } + + get ttl(): ?number { + return this._ttl; + } + + /** + * + * @param collapseKey + * @returns {RemoteMessage} + */ + setCollapseKey(collapseKey: string): RemoteMessage { + this._collapseKey = collapseKey; + return this; + } + + /** + * + * @param data + * @returns {RemoteMessage} + */ + setData(data: { [string]: string } = {}) { + if (!isObject(data)) { + throw new Error( + `RemoteMessage:setData expects an object but got type '${typeof data}'.` + ); + } + this._data = data; + return this; + } + + /** + * + * @param messageId + * @returns {RemoteMessage} + */ + setMessageId(messageId: string): RemoteMessage { + this._messageId = messageId; + return this; + } + + /** + * + * @param messageType + * @returns {RemoteMessage} + */ + setMessageType(messageType: string): RemoteMessage { + this._messageType = messageType; + return this; + } + + /** + * + * @param to + * @returns {RemoteMessage} + */ + setTo(to: string): RemoteMessage { + this._to = to; + return this; + } + + /** + * + * @param ttl + * @returns {RemoteMessage} + */ + setTtl(ttl: number): RemoteMessage { + this._ttl = ttl; + return this; + } + + build(): NativeOutboundRemoteMessage { + if (!this._data) { + throw new Error('RemoteMessage: Missing required `data` property'); + } else if (!this._messageId) { + throw new Error('RemoteMessage: Missing required `messageId` property'); + } else if (!this._to) { + throw new Error('RemoteMessage: Missing required `to` property'); + } else if (!this._ttl) { + throw new Error('RemoteMessage: Missing required `ttl` property'); + } + + return { + collapseKey: this._collapseKey, + data: this._data, + messageId: this._messageId, + messageType: this._messageType, + to: this._to, + ttl: this._ttl, + }; + } +} diff --git a/tests-new/firebase/modules/messaging/index.js b/tests-new/firebase/modules/messaging/index.js new file mode 100644 index 00000000..b56e4978 --- /dev/null +++ b/tests-new/firebase/modules/messaging/index.js @@ -0,0 +1,181 @@ +/** + * @flow + * Messaging (FCM) representation wrapper + */ +import { SharedEventEmitter } from '../../utils/events'; +import INTERNALS from '../../utils/internals'; +import { getLogger } from '../../utils/log'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; +import { isFunction, isObject } from '../../utils'; +import RemoteMessage from './RemoteMessage'; + +import type App from '../core/app'; +import type { NativeInboundRemoteMessage } from './types'; + +type OnMessage = RemoteMessage => any; + +type OnMessageObserver = { + next: OnMessage, +}; + +type OnTokenRefresh = String => any; + +type OnTokenRefreshObserver = { + next: OnTokenRefresh, +}; + +const NATIVE_EVENTS = [ + 'messaging_message_received', + 'messaging_token_refreshed', +]; + +export const MODULE_NAME = 'RNFirebaseMessaging'; +export const NAMESPACE = 'messaging'; + +/** + * @class Messaging + */ +export default class Messaging extends ModuleBase { + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onMessage + 'messaging_message_received', + (message: NativeInboundRemoteMessage) => { + SharedEventEmitter.emit('onMessage', new RemoteMessage(message)); + } + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onMessage + 'messaging_token_refreshed', + (token: string) => { + SharedEventEmitter.emit('onTokenRefresh', token); + } + ); + } + + getToken(): Promise { + return getNativeModule(this).getToken(); + } + + onMessage(nextOrObserver: OnMessage | OnMessageObserver): () => any { + let listener: RemoteMessage => any; + if (isFunction(nextOrObserver)) { + // $FlowExpectedError: Not coping with the overloaded method signature + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Messaging.onMessage failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onMessage listener'); + + SharedEventEmitter.addListener('onMessage', listener); + + return () => { + getLogger(this).info('Removing onMessage listener'); + SharedEventEmitter.removeListener('onMessage', listener); + }; + } + + onTokenRefresh( + nextOrObserver: OnTokenRefresh | OnTokenRefreshObserver + ): () => any { + let listener: String => any; + if (isFunction(nextOrObserver)) { + // $FlowExpectedError: Not coping with the overloaded method signature + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Messaging.OnTokenRefresh failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onTokenRefresh listener'); + SharedEventEmitter.addListener('onTokenRefresh', listener); + + return () => { + getLogger(this).info('Removing onTokenRefresh listener'); + SharedEventEmitter.removeListener('onTokenRefresh', listener); + }; + } + + requestPermission(): Promise { + return getNativeModule(this).requestPermission(); + } + + /** + * NON WEB-SDK METHODS + */ + hasPermission(): Promise { + return getNativeModule(this).hasPermission(); + } + + sendMessage(remoteMessage: RemoteMessage): Promise { + if (!(remoteMessage instanceof RemoteMessage)) { + throw new Error( + `Messaging:sendMessage expects a 'RemoteMessage' but got type ${typeof remoteMessage}` + ); + } + return getNativeModule(this).sendMessage(remoteMessage.build()); + } + + subscribeToTopic(topic: string): void { + getNativeModule(this).subscribeToTopic(topic); + } + + unsubscribeFromTopic(topic: string): void { + getNativeModule(this).unsubscribeFromTopic(topic); + } + + /** + * KNOWN UNSUPPORTED METHODS + */ + + deleteToken() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'messaging', + 'deleteToken' + ) + ); + } + + setBackgroundMessageHandler() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'messaging', + 'setBackgroundMessageHandler' + ) + ); + } + + useServiceWorker() { + throw new Error( + INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD( + 'messaging', + 'useServiceWorker' + ) + ); + } +} + +export const statics = { + RemoteMessage, +}; diff --git a/tests-new/firebase/modules/messaging/types.js b/tests-new/firebase/modules/messaging/types.js new file mode 100644 index 00000000..e2cbe647 --- /dev/null +++ b/tests-new/firebase/modules/messaging/types.js @@ -0,0 +1,38 @@ +/** + * @flow + */ +export type Notification = { + body: string, + bodyLocalizationArgs?: string[], + bodyLocalizationKey?: string, + clickAction?: string, + color?: string, + icon?: string, + link?: string, + sound: string, + subtitle?: string, + tag?: string, + title: string, + titleLocalizationArgs?: string[], + titleLocalizationKey?: string, +}; + +export type NativeInboundRemoteMessage = { + collapseKey?: string, + data: { [string]: string }, + from?: string, + messageId: string, + messageType?: string, + sentTime?: number, + to?: string, + ttl?: number, +}; + +export type NativeOutboundRemoteMessage = { + collapseKey?: string, + data: { [string]: string }, + messageId: string, + messageType?: string, + to: string, + ttl: number, +}; diff --git a/tests-new/firebase/modules/notifications/AndroidAction.js b/tests-new/firebase/modules/notifications/AndroidAction.js new file mode 100644 index 00000000..ed2a940c --- /dev/null +++ b/tests-new/firebase/modules/notifications/AndroidAction.js @@ -0,0 +1,150 @@ +/** + * @flow + * AndroidAction representation wrapper + */ +import RemoteInput, { + fromNativeAndroidRemoteInput, +} from './AndroidRemoteInput'; +import { SemanticAction } from './types'; +import type { NativeAndroidAction, SemanticActionType } from './types'; + +export default class AndroidAction { + _action: string; + _allowGeneratedReplies: boolean | void; + _icon: string; + _remoteInputs: RemoteInput[]; + _semanticAction: SemanticActionType | void; + _showUserInterface: boolean | void; + _title: string; + + constructor(action: string, icon: string, title: string) { + this._action = action; + this._icon = icon; + this._remoteInputs = []; + this._title = title; + } + + get action(): string { + return this._action; + } + + get allowGeneratedReplies(): ?boolean { + return this._allowGeneratedReplies; + } + + get icon(): string { + return this._icon; + } + + get remoteInputs(): RemoteInput[] { + return this._remoteInputs; + } + + get semanticAction(): ?SemanticActionType { + return this._semanticAction; + } + + get showUserInterface(): ?boolean { + return this._showUserInterface; + } + + get title(): string { + return this._title; + } + + /** + * + * @param remoteInput + * @returns {AndroidAction} + */ + addRemoteInput(remoteInput: RemoteInput): AndroidAction { + if (!(remoteInput instanceof RemoteInput)) { + throw new Error( + `AndroidAction:addRemoteInput expects an 'RemoteInput' but got type ${typeof remoteInput}` + ); + } + this._remoteInputs.push(remoteInput); + return this; + } + + /** + * + * @param allowGeneratedReplies + * @returns {AndroidAction} + */ + setAllowGenerateReplies(allowGeneratedReplies: boolean): AndroidAction { + this._allowGeneratedReplies = allowGeneratedReplies; + return this; + } + + /** + * + * @param semanticAction + * @returns {AndroidAction} + */ + setSemanticAction(semanticAction: SemanticActionType): AndroidAction { + if (!Object.values(SemanticAction).includes(semanticAction)) { + throw new Error( + `AndroidAction:setSemanticAction Invalid Semantic Action: ${semanticAction}` + ); + } + this._semanticAction = semanticAction; + return this; + } + + /** + * + * @param showUserInterface + * @returns {AndroidAction} + */ + setShowUserInterface(showUserInterface: boolean): AndroidAction { + this._showUserInterface = showUserInterface; + return this; + } + + build(): NativeAndroidAction { + if (!this._action) { + throw new Error('AndroidAction: Missing required `action` property'); + } else if (!this._icon) { + throw new Error('AndroidAction: Missing required `icon` property'); + } else if (!this._title) { + throw new Error('AndroidAction: Missing required `title` property'); + } + + return { + action: this._action, + allowGeneratedReplies: this._allowGeneratedReplies, + icon: this._icon, + remoteInputs: this._remoteInputs.map(remoteInput => remoteInput.build()), + semanticAction: this._semanticAction, + showUserInterface: this._showUserInterface, + title: this._title, + }; + } +} + +export const fromNativeAndroidAction = ( + nativeAction: NativeAndroidAction +): AndroidAction => { + const action = new AndroidAction( + nativeAction.action, + nativeAction.icon, + nativeAction.title + ); + if (nativeAction.allowGeneratedReplies) { + action.setAllowGenerateReplies(nativeAction.allowGeneratedReplies); + } + if (nativeAction.remoteInputs) { + nativeAction.remoteInputs.forEach(remoteInput => { + action.addRemoteInput(fromNativeAndroidRemoteInput(remoteInput)); + }); + } + if (nativeAction.semanticAction) { + action.setSemanticAction(nativeAction.semanticAction); + } + if (nativeAction.showUserInterface) { + action.setShowUserInterface(nativeAction.showUserInterface); + } + + return action; +}; diff --git a/tests-new/firebase/modules/notifications/AndroidChannel.js b/tests-new/firebase/modules/notifications/AndroidChannel.js new file mode 100644 index 00000000..f64cbf9e --- /dev/null +++ b/tests-new/firebase/modules/notifications/AndroidChannel.js @@ -0,0 +1,198 @@ +/** + * @flow + * AndroidChannel representation wrapper + */ +import { Importance, Visibility } from './types'; +import type { ImportanceType, VisibilityType } from './types'; + +type NativeAndroidChannel = {| + bypassDnd?: boolean, + channelId: string, + description?: string, + group?: string, + importance: ImportanceType, + lightColor?: string, + lockScreenVisibility?: VisibilityType, + name: string, + showBadge?: boolean, + sound?: string, + vibrationPattern?: number[], +|}; + +export default class AndroidChannel { + _bypassDnd: boolean | void; + _channelId: string; + _description: string | void; + _group: string | void; + _importance: ImportanceType; + _lightColor: string | void; + _lockScreenVisibility: VisibilityType; + _name: string; + _showBadge: boolean | void; + _sound: string | void; + _vibrationPattern: number[] | void; + + constructor(channelId: string, name: string, importance: ImportanceType) { + if (!Object.values(Importance).includes(importance)) { + throw new Error(`AndroidChannel() Invalid Importance: ${importance}`); + } + this._channelId = channelId; + this._name = name; + this._importance = importance; + } + + get bypassDnd(): ?boolean { + return this._bypassDnd; + } + + get channelId(): string { + return this._channelId; + } + + get description(): ?string { + return this._description; + } + + get group(): ?string { + return this._group; + } + + get importance(): ImportanceType { + return this._importance; + } + + get lightColor(): ?string { + return this._lightColor; + } + + get lockScreenVisibility(): ?VisibilityType { + return this._lockScreenVisibility; + } + + get name(): string { + return this._name; + } + + get showBadge(): ?boolean { + return this._showBadge; + } + + get sound(): ?string { + return this._sound; + } + + get vibrationPattern(): ?(number[]) { + return this._vibrationPattern; + } + + /** + * + * @param bypassDnd + * @returns {AndroidChannel} + */ + setBypassDnd(bypassDnd: boolean): AndroidChannel { + this._bypassDnd = bypassDnd; + return this; + } + + /** + * + * @param description + * @returns {AndroidChannel} + */ + setDescription(description: string): AndroidChannel { + this._description = description; + return this; + } + + /** + * + * @param group + * @returns {AndroidChannel} + */ + setGroup(groupId: string): AndroidChannel { + this._group = groupId; + return this; + } + + /** + * + * @param lightColor + * @returns {AndroidChannel} + */ + setLightColor(lightColor: string): AndroidChannel { + this._lightColor = lightColor; + return this; + } + + /** + * + * @param lockScreenVisibility + * @returns {AndroidChannel} + */ + setLockScreenVisibility( + lockScreenVisibility: VisibilityType + ): AndroidChannel { + if (!Object.values(Visibility).includes(lockScreenVisibility)) { + throw new Error( + `AndroidChannel:setLockScreenVisibility Invalid Visibility: ${lockScreenVisibility}` + ); + } + this._lockScreenVisibility = lockScreenVisibility; + return this; + } + + /** + * + * @param showBadge + * @returns {AndroidChannel} + */ + setShowBadge(showBadge: boolean): AndroidChannel { + this._showBadge = showBadge; + return this; + } + + /** + * + * @param sound + * @returns {AndroidChannel} + */ + setSound(sound: string): AndroidChannel { + this._sound = sound; + return this; + } + + /** + * + * @param vibrationPattern + * @returns {AndroidChannel} + */ + setVibrationPattern(vibrationPattern: number[]): AndroidChannel { + this._vibrationPattern = vibrationPattern; + return this; + } + + build(): NativeAndroidChannel { + if (!this._channelId) { + throw new Error('AndroidChannel: Missing required `channelId` property'); + } else if (!this._importance) { + throw new Error('AndroidChannel: Missing required `importance` property'); + } else if (!this._name) { + throw new Error('AndroidChannel: Missing required `name` property'); + } + + return { + bypassDnd: this._bypassDnd, + channelId: this._channelId, + description: this._description, + group: this._group, + importance: this._importance, + lightColor: this._lightColor, + lockScreenVisibility: this._lockScreenVisibility, + name: this._name, + showBadge: this._showBadge, + sound: this._sound, + vibrationPattern: this._vibrationPattern, + }; + } +} diff --git a/tests-new/firebase/modules/notifications/AndroidChannelGroup.js b/tests-new/firebase/modules/notifications/AndroidChannelGroup.js new file mode 100644 index 00000000..3485c276 --- /dev/null +++ b/tests-new/firebase/modules/notifications/AndroidChannelGroup.js @@ -0,0 +1,42 @@ +/** + * @flow + * AndroidChannelGroup representation wrapper + */ + +type NativeAndroidChannelGroup = {| + groupId: string, + name: string, +|}; + +export default class AndroidChannelGroup { + _groupId: string; + _name: string; + + constructor(groupId: string, name: string) { + this._groupId = groupId; + this._name = name; + } + + get groupId(): string { + return this._groupId; + } + + get name(): string { + return this._name; + } + + build(): NativeAndroidChannelGroup { + if (!this._groupId) { + throw new Error( + 'AndroidChannelGroup: Missing required `groupId` property' + ); + } else if (!this._name) { + throw new Error('AndroidChannelGroup: Missing required `name` property'); + } + + return { + groupId: this._groupId, + name: this._name, + }; + } +} diff --git a/tests-new/firebase/modules/notifications/AndroidNotification.js b/tests-new/firebase/modules/notifications/AndroidNotification.js new file mode 100644 index 00000000..f67b3d3f --- /dev/null +++ b/tests-new/firebase/modules/notifications/AndroidNotification.js @@ -0,0 +1,676 @@ +/** + * @flow + * AndroidNotification representation wrapper + */ +import AndroidAction, { fromNativeAndroidAction } from './AndroidAction'; +import { BadgeIconType, Category, GroupAlert, Priority } from './types'; +import type Notification from './Notification'; +import type { + BadgeIconTypeType, + CategoryType, + DefaultsType, + GroupAlertType, + Lights, + NativeAndroidNotification, + PriorityType, + Progress, + SmallIcon, + VisibilityType, +} from './types'; + +export default class AndroidNotification { + _actions: AndroidAction[]; + _autoCancel: boolean | void; + _badgeIconType: BadgeIconTypeType | void; + _category: CategoryType | void; + _channelId: string; + _clickAction: string | void; + _color: string | void; + _colorized: boolean | void; + _contentInfo: string | void; + _defaults: DefaultsType[] | void; + _group: string | void; + _groupAlertBehaviour: GroupAlertType | void; + _groupSummary: boolean | void; + _largeIcon: string | void; + _lights: Lights | void; + _localOnly: boolean | void; + _notification: Notification; + _number: number | void; + _ongoing: boolean | void; + _onlyAlertOnce: boolean | void; + _people: string[]; + _priority: PriorityType | void; + _progress: Progress | void; + // _publicVersion: Notification; + _remoteInputHistory: string[] | void; + _shortcutId: string | void; + _showWhen: boolean | void; + _smallIcon: SmallIcon; + _sortKey: string | void; + // TODO: style: Style; // Need to figure out if this can work + _ticker: string | void; + _timeoutAfter: number | void; + _usesChronometer: boolean | void; + _vibrate: number[] | void; + _visibility: VisibilityType | void; + _when: number | void; + + // android unsupported + // content: RemoteViews + // contentIntent: PendingIntent - need to look at what this is + // customBigContentView: RemoteViews + // customContentView: RemoteViews + // customHeadsUpContentView: RemoteViews + // deleteIntent: PendingIntent + // fullScreenIntent: PendingIntent + // sound.streamType + + constructor(notification: Notification, data?: NativeAndroidNotification) { + this._notification = notification; + + if (data) { + this._actions = data.actions + ? data.actions.map(action => fromNativeAndroidAction(action)) + : []; + this._autoCancel = data.autoCancel; + this._badgeIconType = data.badgeIconType; + this._category = data.category; + this._channelId = data.channelId; + this._clickAction = data.clickAction; + this._color = data.color; + this._colorized = data.colorized; + this._contentInfo = data.contentInfo; + this._defaults = data.defaults; + this._group = data.group; + this._groupAlertBehaviour = data.groupAlertBehaviour; + this._groupSummary = data.groupSummary; + this._largeIcon = data.largeIcon; + this._lights = data.lights; + this._localOnly = data.localOnly; + this._number = data.number; + this._ongoing = data.ongoing; + this._onlyAlertOnce = data.onlyAlertOnce; + this._people = data.people; + this._priority = data.priority; + this._progress = data.progress; + // _publicVersion: Notification; + this._remoteInputHistory = data.remoteInputHistory; + this._shortcutId = data.shortcutId; + this._showWhen = data.showWhen; + this._smallIcon = data.smallIcon; + this._sortKey = data.sortKey; + this._ticker = data.ticker; + this._timeoutAfter = data.timeoutAfter; + this._usesChronometer = data.usesChronometer; + this._vibrate = data.vibrate; + this._visibility = data.visibility; + this._when = data.when; + } + + // Defaults + this._actions = this._actions || []; + this._people = this._people || []; + this._smallIcon = this._smallIcon || { + icon: 'ic_launcher', + }; + } + + get actions(): AndroidAction[] { + return this._actions; + } + + get autoCancel(): ?boolean { + return this._autoCancel; + } + + get badgeIconType(): ?BadgeIconTypeType { + return this._badgeIconType; + } + + get category(): ?CategoryType { + return this._category; + } + + get channelId(): string { + return this._channelId; + } + + get clickAction(): ?string { + return this._clickAction; + } + + get color(): ?string { + return this._color; + } + + get colorized(): ?boolean { + return this._colorized; + } + + get contentInfo(): ?string { + return this._contentInfo; + } + + get defaults(): ?(DefaultsType[]) { + return this._defaults; + } + + get group(): ?string { + return this._group; + } + + get groupAlertBehaviour(): ?GroupAlertType { + return this._groupAlertBehaviour; + } + + get groupSummary(): ?boolean { + return this._groupSummary; + } + + get largeIcon(): ?string { + return this._largeIcon; + } + + get lights(): ?Lights { + return this._lights; + } + + get localOnly(): ?boolean { + return this._localOnly; + } + + get number(): ?number { + return this._number; + } + + get ongoing(): ?boolean { + return this._ongoing; + } + + get onlyAlertOnce(): ?boolean { + return this._onlyAlertOnce; + } + + get people(): string[] { + return this._people; + } + + get priority(): ?PriorityType { + return this._priority; + } + + get progress(): ?Progress { + return this._progress; + } + + get remoteInputHistory(): ?(string[]) { + return this._remoteInputHistory; + } + + get shortcutId(): ?string { + return this._shortcutId; + } + + get showWhen(): ?boolean { + return this._showWhen; + } + + get smallIcon(): SmallIcon { + return this._smallIcon; + } + + get sortKey(): ?string { + return this._sortKey; + } + + get ticker(): ?string { + return this._ticker; + } + + get timeoutAfter(): ?number { + return this._timeoutAfter; + } + + get usesChronometer(): ?boolean { + return this._usesChronometer; + } + + get vibrate(): ?(number[]) { + return this._vibrate; + } + + get visibility(): ?VisibilityType { + return this._visibility; + } + + get when(): ?number { + return this._when; + } + + /** + * + * @param action + * @returns {Notification} + */ + addAction(action: AndroidAction): Notification { + if (!(action instanceof AndroidAction)) { + throw new Error( + `AndroidNotification:addAction expects an 'AndroidAction' but got type ${typeof action}` + ); + } + this._actions.push(action); + return this._notification; + } + + /** + * + * @param person + * @returns {Notification} + */ + addPerson(person: string): Notification { + this._people.push(person); + return this._notification; + } + + /** + * + * @param autoCancel + * @returns {Notification} + */ + setAutoCancel(autoCancel: boolean): Notification { + this._autoCancel = autoCancel; + return this._notification; + } + + /** + * + * @param badgeIconType + * @returns {Notification} + */ + setBadgeIconType(badgeIconType: BadgeIconTypeType): Notification { + if (!Object.values(BadgeIconType).includes(badgeIconType)) { + throw new Error( + `AndroidNotification:setBadgeIconType Invalid BadgeIconType: ${badgeIconType}` + ); + } + this._badgeIconType = badgeIconType; + return this._notification; + } + + /** + * + * @param category + * @returns {Notification} + */ + setCategory(category: CategoryType): Notification { + if (!Object.values(Category).includes(category)) { + throw new Error( + `AndroidNotification:setCategory Invalid Category: ${category}` + ); + } + this._category = category; + return this._notification; + } + + /** + * + * @param channelId + * @returns {Notification} + */ + setChannelId(channelId: string): Notification { + this._channelId = channelId; + return this._notification; + } + + /** + * + * @param clickAction + * @returns {Notification} + */ + setClickAction(clickAction: string): Notification { + this._clickAction = clickAction; + return this._notification; + } + + /** + * + * @param color + * @returns {Notification} + */ + setColor(color: string): Notification { + this._color = color; + return this._notification; + } + + /** + * + * @param colorized + * @returns {Notification} + */ + setColorized(colorized: boolean): Notification { + this._colorized = colorized; + return this._notification; + } + + /** + * + * @param contentInfo + * @returns {Notification} + */ + setContentInfo(contentInfo: string): Notification { + this._contentInfo = contentInfo; + return this._notification; + } + + /** + * + * @param defaults + * @returns {Notification} + */ + setDefaults(defaults: DefaultsType[]): Notification { + this._defaults = defaults; + return this._notification; + } + + /** + * + * @param group + * @returns {Notification} + */ + setGroup(group: string): Notification { + this._group = group; + return this._notification; + } + + /** + * + * @param groupAlertBehaviour + * @returns {Notification} + */ + setGroupAlertBehaviour(groupAlertBehaviour: GroupAlertType): Notification { + if (!Object.values(GroupAlert).includes(groupAlertBehaviour)) { + throw new Error( + `AndroidNotification:setGroupAlertBehaviour Invalid GroupAlert: ${groupAlertBehaviour}` + ); + } + this._groupAlertBehaviour = groupAlertBehaviour; + return this._notification; + } + + /** + * + * @param groupSummary + * @returns {Notification} + */ + setGroupSummary(groupSummary: boolean): Notification { + this._groupSummary = groupSummary; + return this._notification; + } + + /** + * + * @param largeIcon + * @returns {Notification} + */ + setLargeIcon(largeIcon: string): Notification { + this._largeIcon = largeIcon; + return this._notification; + } + + /** + * + * @param argb + * @param onMs + * @param offMs + * @returns {Notification} + */ + setLights(argb: number, onMs: number, offMs: number): Notification { + this._lights = { + argb, + onMs, + offMs, + }; + return this._notification; + } + + /** + * + * @param localOnly + * @returns {Notification} + */ + setLocalOnly(localOnly: boolean): Notification { + this._localOnly = localOnly; + return this._notification; + } + + /** + * + * @param number + * @returns {Notification} + */ + setNumber(number: number): Notification { + this._number = number; + return this._notification; + } + + /** + * + * @param ongoing + * @returns {Notification} + */ + setOngoing(ongoing: boolean): Notification { + this._ongoing = ongoing; + return this._notification; + } + + /** + * + * @param onlyAlertOnce + * @returns {Notification} + */ + setOnlyAlertOnce(onlyAlertOnce: boolean): Notification { + this._onlyAlertOnce = onlyAlertOnce; + return this._notification; + } + + /** + * + * @param priority + * @returns {Notification} + */ + setPriority(priority: PriorityType): Notification { + if (!Object.values(Priority).includes(priority)) { + throw new Error( + `AndroidNotification:setPriority Invalid Priority: ${priority}` + ); + } + this._priority = priority; + return this._notification; + } + + /** + * + * @param max + * @param progress + * @param indeterminate + * @returns {Notification} + */ + setProgress( + max: number, + progress: number, + indeterminate: boolean + ): Notification { + this._progress = { + max, + progress, + indeterminate, + }; + return this._notification; + } + + /** + * + * @param publicVersion + * @returns {Notification} + */ + /* setPublicVersion(publicVersion: Notification): Notification { + this._publicVersion = publicVersion; + return this._notification; + } */ + + /** + * + * @param remoteInputHistory + * @returns {Notification} + */ + setRemoteInputHistory(remoteInputHistory: string[]): Notification { + this._remoteInputHistory = remoteInputHistory; + return this._notification; + } + + /** + * + * @param shortcutId + * @returns {Notification} + */ + setShortcutId(shortcutId: string): Notification { + this._shortcutId = shortcutId; + return this._notification; + } + + /** + * + * @param showWhen + * @returns {Notification} + */ + setShowWhen(showWhen: boolean): Notification { + this._showWhen = showWhen; + return this._notification; + } + + /** + * + * @param icon + * @param level + * @returns {Notification} + */ + setSmallIcon(icon: string, level?: number): Notification { + this._smallIcon = { + icon, + level, + }; + return this._notification; + } + + /** + * + * @param sortKey + * @returns {Notification} + */ + setSortKey(sortKey: string): Notification { + this._sortKey = sortKey; + return this._notification; + } + + /** + * + * @param ticker + * @returns {Notification} + */ + setTicker(ticker: string): Notification { + this._ticker = ticker; + return this._notification; + } + + /** + * + * @param timeoutAfter + * @returns {Notification} + */ + setTimeoutAfter(timeoutAfter: number): Notification { + this._timeoutAfter = timeoutAfter; + return this._notification; + } + + /** + * + * @param usesChronometer + * @returns {Notification} + */ + setUsesChronometer(usesChronometer: boolean): Notification { + this._usesChronometer = usesChronometer; + return this._notification; + } + + /** + * + * @param vibrate + * @returns {Notification} + */ + setVibrate(vibrate: number[]): Notification { + this._vibrate = vibrate; + return this._notification; + } + + /** + * + * @param when + * @returns {Notification} + */ + setWhen(when: number): Notification { + this._when = when; + return this._notification; + } + + build(): NativeAndroidNotification { + // TODO: Validation of required fields + if (!this._channelId) { + throw new Error( + 'AndroidNotification: Missing required `channelId` property' + ); + } else if (!this._smallIcon) { + throw new Error( + 'AndroidNotification: Missing required `smallIcon` property' + ); + } + + return { + actions: this._actions.map(action => action.build()), + autoCancel: this._autoCancel, + badgeIconType: this._badgeIconType, + category: this._category, + channelId: this._channelId, + clickAction: this._clickAction, + color: this._color, + colorized: this._colorized, + contentInfo: this._contentInfo, + defaults: this._defaults, + group: this._group, + groupAlertBehaviour: this._groupAlertBehaviour, + groupSummary: this._groupSummary, + largeIcon: this._largeIcon, + lights: this._lights, + localOnly: this._localOnly, + number: this._number, + ongoing: this._ongoing, + onlyAlertOnce: this._onlyAlertOnce, + people: this._people, + priority: this._priority, + progress: this._progress, + // publicVersion: this._publicVersion, + remoteInputHistory: this._remoteInputHistory, + shortcutId: this._shortcutId, + showWhen: this._showWhen, + smallIcon: this._smallIcon, + sortKey: this._sortKey, + // TODO: style: Style, + ticker: this._ticker, + timeoutAfter: this._timeoutAfter, + usesChronometer: this._usesChronometer, + vibrate: this._vibrate, + visibility: this._visibility, + when: this._when, + }; + } +} diff --git a/tests-new/firebase/modules/notifications/AndroidNotifications.js b/tests-new/firebase/modules/notifications/AndroidNotifications.js new file mode 100644 index 00000000..bd644a60 --- /dev/null +++ b/tests-new/firebase/modules/notifications/AndroidNotifications.js @@ -0,0 +1,94 @@ +/** + * @flow + * AndroidNotifications representation wrapper + */ +import { Platform } from 'react-native'; +import AndroidChannel from './AndroidChannel'; +import AndroidChannelGroup from './AndroidChannelGroup'; +import { getNativeModule } from '../../utils/native'; + +import type Notifications from './'; + +export default class AndroidNotifications { + _notifications: Notifications; + + constructor(notifications: Notifications) { + this._notifications = notifications; + } + + createChannel(channel: AndroidChannel): Promise { + if (Platform.OS === 'android') { + if (!(channel instanceof AndroidChannel)) { + throw new Error( + `AndroidNotifications:createChannel expects an 'AndroidChannel' but got type ${typeof channel}` + ); + } + return getNativeModule(this._notifications).createChannel( + channel.build() + ); + } + return Promise.resolve(); + } + + createChannelGroup(channelGroup: AndroidChannelGroup): Promise { + if (Platform.OS === 'android') { + if (!(channelGroup instanceof AndroidChannelGroup)) { + throw new Error( + `AndroidNotifications:createChannelGroup expects an 'AndroidChannelGroup' but got type ${typeof channelGroup}` + ); + } + return getNativeModule(this._notifications).createChannelGroup( + channelGroup.build() + ); + } + return Promise.resolve(); + } + + createChannelGroups(channelGroups: AndroidChannelGroup[]): Promise { + if (Platform.OS === 'android') { + if (!Array.isArray(channelGroups)) { + throw new Error( + `AndroidNotifications:createChannelGroups expects an 'Array' but got type ${typeof channelGroups}` + ); + } + const nativeChannelGroups = []; + for (let i = 0; i < channelGroups.length; i++) { + const channelGroup = channelGroups[i]; + if (!(channelGroup instanceof AndroidChannelGroup)) { + throw new Error( + `AndroidNotifications:createChannelGroups expects array items of type 'AndroidChannelGroup' but got type ${typeof channelGroup}` + ); + } + nativeChannelGroups.push(channelGroup.build()); + } + return getNativeModule(this._notifications).createChannelGroups( + nativeChannelGroups + ); + } + return Promise.resolve(); + } + + createChannels(channels: AndroidChannel[]): Promise { + if (Platform.OS === 'android') { + if (!Array.isArray(channels)) { + throw new Error( + `AndroidNotifications:createChannels expects an 'Array' but got type ${typeof channels}` + ); + } + const nativeChannels = []; + for (let i = 0; i < channels.length; i++) { + const channel = channels[i]; + if (!(channel instanceof AndroidChannel)) { + throw new Error( + `AndroidNotifications:createChannels expects array items of type 'AndroidChannel' but got type ${typeof channel}` + ); + } + nativeChannels.push(channel.build()); + } + return getNativeModule(this._notifications).createChannels( + nativeChannels + ); + } + return Promise.resolve(); + } +} diff --git a/tests-new/firebase/modules/notifications/AndroidRemoteInput.js b/tests-new/firebase/modules/notifications/AndroidRemoteInput.js new file mode 100644 index 00000000..29b29fbb --- /dev/null +++ b/tests-new/firebase/modules/notifications/AndroidRemoteInput.js @@ -0,0 +1,123 @@ +/** + * @flow + * AndroidRemoteInput representation wrapper + */ + +import type { AndroidAllowDataType, NativeAndroidRemoteInput } from './types'; + +export default class AndroidRemoteInput { + _allowedDataTypes: AndroidAllowDataType[]; + _allowFreeFormInput: boolean | void; + _choices: string[]; + _label: string | void; + _resultKey: string; + + constructor(resultKey: string) { + this._allowedDataTypes = []; + this._choices = []; + this._resultKey = resultKey; + } + + get allowedDataTypes(): AndroidAllowDataType[] { + return this._allowedDataTypes; + } + + get allowFreeFormInput(): ?boolean { + return this._allowFreeFormInput; + } + + get choices(): string[] { + return this._choices; + } + + get label(): ?string { + return this._label; + } + + get resultKey(): string { + return this._resultKey; + } + + /** + * + * @param mimeType + * @param allow + * @returns {AndroidRemoteInput} + */ + setAllowDataType(mimeType: string, allow: boolean): AndroidRemoteInput { + this._allowedDataTypes.push({ + allow, + mimeType, + }); + return this; + } + + /** + * + * @param allowFreeFormInput + * @returns {AndroidRemoteInput} + */ + setAllowFreeFormInput(allowFreeFormInput: boolean): AndroidRemoteInput { + this._allowFreeFormInput = allowFreeFormInput; + return this; + } + + /** + * + * @param choices + * @returns {AndroidRemoteInput} + */ + setChoices(choices: string[]): AndroidRemoteInput { + this._choices = choices; + return this; + } + + /** + * + * @param label + * @returns {AndroidRemoteInput} + */ + setLabel(label: string): AndroidRemoteInput { + this._label = label; + return this; + } + + build(): NativeAndroidRemoteInput { + if (!this._resultKey) { + throw new Error( + 'AndroidRemoteInput: Missing required `resultKey` property' + ); + } + + return { + allowedDataTypes: this._allowedDataTypes, + allowFreeFormInput: this._allowFreeFormInput, + choices: this._choices, + label: this._label, + resultKey: this._resultKey, + }; + } +} + +export const fromNativeAndroidRemoteInput = ( + nativeRemoteInput: NativeAndroidRemoteInput +): AndroidRemoteInput => { + const remoteInput = new AndroidRemoteInput(nativeRemoteInput.resultKey); + if (nativeRemoteInput.allowDataType) { + for (let i = 0; i < nativeRemoteInput.allowDataType.length; i++) { + const allowDataType = nativeRemoteInput.allowDataType[i]; + remoteInput.setAllowDataType(allowDataType.mimeType, allowDataType.allow); + } + } + if (nativeRemoteInput.allowFreeFormInput) { + remoteInput.setAllowFreeFormInput(nativeRemoteInput.allowFreeFormInput); + } + if (nativeRemoteInput.choices) { + remoteInput.setChoices(nativeRemoteInput.choices); + } + if (nativeRemoteInput.label) { + remoteInput.setLabel(nativeRemoteInput.label); + } + + return remoteInput; +}; diff --git a/tests-new/firebase/modules/notifications/IOSNotification.js b/tests-new/firebase/modules/notifications/IOSNotification.js new file mode 100644 index 00000000..cd1acaae --- /dev/null +++ b/tests-new/firebase/modules/notifications/IOSNotification.js @@ -0,0 +1,160 @@ +/** + * @flow + * IOSNotification representation wrapper + */ +import type Notification from './Notification'; +import type { + IOSAttachment, + IOSAttachmentOptions, + NativeIOSNotification, +} from './types'; + +export default class IOSNotification { + _alertAction: string | void; // alertAction | N/A + _attachments: IOSAttachment[]; // N/A | attachments + _badge: number | void; // applicationIconBadgeNumber | badge + _category: string | void; + _hasAction: boolean | void; // hasAction | N/A + _launchImage: string | void; // alertLaunchImage | launchImageName + _notification: Notification; + _threadIdentifier: string | void; // N/A | threadIdentifier + + constructor(notification: Notification, data?: NativeIOSNotification) { + this._notification = notification; + + if (data) { + this._alertAction = data.alertAction; + this._attachments = data.attachments; + this._badge = data.badge; + this._category = data.category; + this._hasAction = data.hasAction; + this._launchImage = data.launchImage; + this._threadIdentifier = data.threadIdentifier; + } + + // Defaults + this._attachments = this._attachments || []; + } + + get alertAction(): ?string { + return this._alertAction; + } + + get attachments(): IOSAttachment[] { + return this._attachments; + } + + get badge(): ?number { + return this._badge; + } + + get category(): ?string { + return this._category; + } + + get hasAction(): ?boolean { + return this._hasAction; + } + + get launchImage(): ?string { + return this._launchImage; + } + + get threadIdentifier(): ?string { + return this._threadIdentifier; + } + + /** + * + * @param identifier + * @param url + * @param options + * @returns {Notification} + */ + addAttachment( + identifier: string, + url: string, + options?: IOSAttachmentOptions + ): Notification { + this._attachments.push({ + identifier, + options, + url, + }); + return this._notification; + } + + /** + * + * @param alertAction + * @returns {Notification} + */ + setAlertAction(alertAction: string): Notification { + this._alertAction = alertAction; + return this._notification; + } + + /** + * + * @param badge + * @returns {Notification} + */ + setBadge(badge: number): Notification { + this._badge = badge; + return this._notification; + } + + /** + * + * @param category + * @returns {Notification} + */ + setCategory(category: string): Notification { + this._category = category; + return this._notification; + } + + /** + * + * @param hasAction + * @returns {Notification} + */ + setHasAction(hasAction: boolean): Notification { + this._hasAction = hasAction; + return this._notification; + } + + /** + * + * @param launchImage + * @returns {Notification} + */ + setLaunchImage(launchImage: string): Notification { + this._launchImage = launchImage; + return this._notification; + } + + /** + * + * @param threadIdentifier + * @returns {Notification} + */ + setThreadIdentifier(threadIdentifier: string): Notification { + this._threadIdentifier = threadIdentifier; + return this._notification; + } + + build(): NativeIOSNotification { + // TODO: Validation of required fields + + return { + alertAction: this._alertAction, + attachments: this._attachments, + badge: this._badge, + category: this._category, + hasAction: this._hasAction, + launchImage: this._launchImage, + threadIdentifier: this._threadIdentifier, + }; + } +} diff --git a/tests-new/firebase/modules/notifications/Notification.js b/tests-new/firebase/modules/notifications/Notification.js new file mode 100644 index 00000000..9385c27e --- /dev/null +++ b/tests-new/firebase/modules/notifications/Notification.js @@ -0,0 +1,169 @@ +/** + * @flow + * Notification representation wrapper + */ +import { Platform } from 'react-native'; +import AndroidNotification from './AndroidNotification'; +import IOSNotification from './IOSNotification'; +import { generatePushID, isObject } from '../../utils'; + +import type { NativeNotification } from './types'; + +export type NotificationOpen = {| + action: string, + notification: Notification, + results?: { [string]: string }, +|}; + +export default class Notification { + // iOS 8/9 | 10+ | Android + _android: AndroidNotification; + _body: string; // alertBody | body | contentText + _data: { [string]: string }; // userInfo | userInfo | extras + _ios: IOSNotification; + _notificationId: string; + _sound: string | void; // soundName | sound | sound + _subtitle: string | void; // N/A | subtitle | subText + _title: string; // alertTitle | title | contentTitle + + constructor(data?: NativeNotification) { + this._android = new AndroidNotification(this, data && data.android); + this._ios = new IOSNotification(this, data && data.ios); + + if (data) { + this._body = data.body; + this._data = data.data; + this._notificationId = data.notificationId; + this._sound = data.sound; + this._subtitle = data.subtitle; + this._title = data.title; + } + + // Defaults + this._data = this._data || {}; + // TODO: Is this the best way to generate an ID? + this._notificationId = this._notificationId || generatePushID(); + } + + get android(): AndroidNotification { + return this._android; + } + + get body(): string { + return this._body; + } + + get data(): { [string]: string } { + return this._data; + } + + get ios(): IOSNotification { + return this._ios; + } + + get notificationId(): string { + return this._notificationId; + } + + get sound(): ?string { + return this._sound; + } + + get subtitle(): ?string { + return this._subtitle; + } + + get title(): string { + return this._title; + } + + /** + * + * @param body + * @returns {Notification} + */ + setBody(body: string): Notification { + this._body = body; + return this; + } + + /** + * + * @param data + * @returns {Notification} + */ + setData(data: Object = {}): Notification { + if (!isObject(data)) { + throw new Error( + `Notification:withData expects an object but got type '${typeof data}'.` + ); + } + this._data = data; + return this; + } + + /** + * + * @param notificationId + * @returns {Notification} + */ + setNotificationId(notificationId: string): Notification { + this._notificationId = notificationId; + return this; + } + + /** + * + * @param sound + * @returns {Notification} + */ + setSound(sound: string): Notification { + this._sound = sound; + return this; + } + + /** + * + * @param subtitle + * @returns {Notification} + */ + setSubtitle(subtitle: string): Notification { + this._subtitle = subtitle; + return this; + } + + /** + * + * @param title + * @returns {Notification} + */ + setTitle(title: string): Notification { + this._title = title; + return this; + } + + build(): NativeNotification { + // Android required fields: body, title, smallicon + // iOS required fields: TODO + if (!this._body) { + throw new Error('Notification: Missing required `body` property'); + } else if (!this._notificationId) { + throw new Error( + 'Notification: Missing required `notificationId` property' + ); + } else if (!this._title) { + throw new Error('Notification: Missing required `title` property'); + } + + return { + android: Platform.OS === 'android' ? this._android.build() : undefined, + body: this._body, + data: this._data, + ios: Platform.OS === 'ios' ? this._ios.build() : undefined, + notificationId: this._notificationId, + sound: this._sound, + subtitle: this._subtitle, + title: this._title, + }; + } +} diff --git a/tests-new/firebase/modules/notifications/index.js b/tests-new/firebase/modules/notifications/index.js new file mode 100644 index 00000000..504bd9e0 --- /dev/null +++ b/tests-new/firebase/modules/notifications/index.js @@ -0,0 +1,318 @@ +/** + * @flow + * Notifications representation wrapper + */ +import { SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; +import { isFunction, isObject } from '../../utils'; +import AndroidAction from './AndroidAction'; +import AndroidChannel from './AndroidChannel'; +import AndroidChannelGroup from './AndroidChannelGroup'; +import AndroidNotifications from './AndroidNotifications'; +import AndroidRemoteInput from './AndroidRemoteInput'; +import Notification from './Notification'; +import { + BadgeIconType, + Category, + Defaults, + GroupAlert, + Importance, + Priority, + SemanticAction, + Visibility, +} from './types'; + +import type App from '../core/app'; +import type { NotificationOpen } from './Notification'; +import type { + NativeNotification, + NativeNotificationOpen, + Schedule, +} from './types'; + +type OnNotification = Notification => any; + +type OnNotificationObserver = { + next: OnNotification, +}; + +type OnNotificationOpened = NotificationOpen => any; + +type OnNotificationOpenedObserver = { + next: NotificationOpen, +}; + +const NATIVE_EVENTS = [ + 'notifications_notification_displayed', + 'notifications_notification_opened', + 'notifications_notification_received', +]; + +export const MODULE_NAME = 'RNFirebaseNotifications'; +export const NAMESPACE = 'notifications'; + +// iOS 8/9 scheduling +// fireDate: Date; +// timeZone: TimeZone; +// repeatInterval: NSCalendar.Unit; +// repeatCalendar: Calendar; +// region: CLRegion; +// regionTriggersOnce: boolean; + +// iOS 10 scheduling +// TODO + +// Android scheduling +// TODO + +/** + * @class Notifications + */ +export default class Notifications extends ModuleBase { + _android: AndroidNotifications; + + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + hasShards: false, + moduleName: MODULE_NAME, + multiApp: false, + namespace: NAMESPACE, + }); + this._android = new AndroidNotifications(this); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onNotificationDisplayed + 'notifications_notification_displayed', + (notification: NativeNotification) => { + SharedEventEmitter.emit( + 'onNotificationDisplayed', + new Notification(notification) + ); + } + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onNotificationOpened + 'notifications_notification_opened', + (notificationOpen: NativeNotificationOpen) => { + SharedEventEmitter.emit('onNotificationOpened', { + action: notificationOpen.action, + notification: new Notification(notificationOpen.notification), + results: notificationOpen.results, + }); + } + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onNotification + 'notifications_notification_received', + (notification: NativeNotification) => { + SharedEventEmitter.emit( + 'onNotification', + new Notification(notification) + ); + } + ); + } + + get android(): AndroidNotifications { + return this._android; + } + + /** + * Cancel all notifications + */ + cancelAllNotifications(): void { + getNativeModule(this).cancelAllNotifications(); + } + + /** + * Cancel a notification by id. + * @param notificationId + */ + cancelNotification(notificationId: string): void { + if (!notificationId) { + throw new Error( + 'Notifications: cancelNotification expects a `notificationId`' + ); + } + getNativeModule(this).cancelNotification(notificationId); + } + + /** + * Display a notification + * @param notification + * @returns {*} + */ + displayNotification(notification: Notification): Promise { + if (!(notification instanceof Notification)) { + throw new Error( + `Notifications:displayNotification expects a 'Notification' but got type ${typeof notification}` + ); + } + return getNativeModule(this).displayNotification(notification.build()); + } + + getBadge(): Promise { + return getNativeModule(this).getBadge(); + } + + getInitialNotification(): Promise { + return getNativeModule(this) + .getInitialNotification() + .then((notificationOpen: NativeNotificationOpen) => { + if (notificationOpen) { + return { + action: notificationOpen.action, + notification: new Notification(notificationOpen.notification), + results: notificationOpen.results, + }; + } + return null; + }); + } + + /** + * Returns an array of all scheduled notifications + * @returns {Promise.} + */ + getScheduledNotifications(): Promise { + return getNativeModule(this).getScheduledNotifications(); + } + + onNotification( + nextOrObserver: OnNotification | OnNotificationObserver + ): () => any { + let listener; + if (isFunction(nextOrObserver)) { + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Notifications.onNotification failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onNotification listener'); + SharedEventEmitter.addListener('onNotification', listener); + + return () => { + getLogger(this).info('Removing onNotification listener'); + SharedEventEmitter.removeListener('onNotification', listener); + }; + } + + onNotificationDisplayed( + nextOrObserver: OnNotification | OnNotificationObserver + ): () => any { + let listener; + if (isFunction(nextOrObserver)) { + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Notifications.onNotificationDisplayed failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onNotificationDisplayed listener'); + SharedEventEmitter.addListener('onNotificationDisplayed', listener); + + return () => { + getLogger(this).info('Removing onNotificationDisplayed listener'); + SharedEventEmitter.removeListener('onNotificationDisplayed', listener); + }; + } + + onNotificationOpened( + nextOrObserver: OnNotificationOpened | OnNotificationOpenedObserver + ): () => any { + let listener; + if (isFunction(nextOrObserver)) { + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Notifications.onNotificationOpened failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onNotificationOpened listener'); + SharedEventEmitter.addListener('onNotificationOpened', listener); + + return () => { + getLogger(this).info('Removing onNotificationOpened listener'); + SharedEventEmitter.removeListener('onNotificationOpened', listener); + }; + } + + /** + * Remove all delivered notifications. + */ + removeAllDeliveredNotifications(): void { + getNativeModule(this).removeAllDeliveredNotifications(); + } + + /** + * Remove a delivered notification. + * @param notificationId + */ + removeDeliveredNotification(notificationId: string): void { + if (!notificationId) { + throw new Error( + 'Notifications: removeDeliveredNotification expects a `notificationId`' + ); + } + getNativeModule(this).removeDeliveredNotification(notificationId); + } + + /** + * Schedule a notification + * @param notification + * @returns {*} + */ + scheduleNotification( + notification: Notification, + schedule: Schedule + ): Promise { + if (!(notification instanceof Notification)) { + throw new Error( + `Notifications:scheduleNotification expects a 'Notification' but got type ${typeof notification}` + ); + } + const nativeNotification = notification.build(); + nativeNotification.schedule = schedule; + return getNativeModule(this).scheduleNotification(nativeNotification); + } + + setBadge(badge: number): void { + getNativeModule(this).setBadge(badge); + } +} + +export const statics = { + Android: { + Action: AndroidAction, + BadgeIconType, + Category, + Channel: AndroidChannel, + ChannelGroup: AndroidChannelGroup, + Defaults, + GroupAlert, + Importance, + Priority, + RemoteInput: AndroidRemoteInput, + SemanticAction, + Visibility, + }, + Notification, +}; diff --git a/tests-new/firebase/modules/notifications/types.js b/tests-new/firebase/modules/notifications/types.js new file mode 100644 index 00000000..1441d8e3 --- /dev/null +++ b/tests-new/firebase/modules/notifications/types.js @@ -0,0 +1,216 @@ +/** + * @flow + */ +export const BadgeIconType = { + Large: 2, + None: 0, + Small: 1, +}; + +export const Category = { + Alarm: 'alarm', + Call: 'call', + Email: 'email', + Error: 'err', + Event: 'event', + Message: 'msg', + Progress: 'progress', + Promo: 'promo', + Recommendation: 'recommendation', + Reminder: 'reminder', + Service: 'service', + Social: 'social', + Status: 'status', + System: 'system', + Transport: 'transport', +}; + +export const Defaults = { + All: -1, + Lights: 4, + Sound: 1, + Vibrate: 2, +}; + +export const GroupAlert = { + All: 0, + Children: 2, + Summary: 1, +}; + +export const Importance = { + Default: 3, + High: 4, + Low: 2, + Max: 5, + Min: 1, + None: 3, + Unspecified: -1000, +}; + +export const Priority = { + Default: 0, + High: 1, + Low: -1, + Max: 2, + Min: -2, +}; + +export const SemanticAction = { + Archive: 5, + Call: 10, + Delete: 4, + MarkAsRead: 2, + MarkAsUnread: 3, + Mute: 6, + None: 0, + Reply: 1, + ThumbsDown: 9, + ThumbsUp: 8, + Unmute: 7, +}; + +export const Visibility = { + Private: 0, + Public: 1, + Secret: -1, +}; + +export type BadgeIconTypeType = $Values; +export type CategoryType = $Values; +export type DefaultsType = $Values; +export type GroupAlertType = $Values; +export type ImportanceType = $Values; +export type PriorityType = $Values; +export type SemanticActionType = $Values; +export type VisibilityType = $Values; + +export type Lights = {| + argb: number, + onMs: number, + offMs: number, +|}; + +export type Progress = {| + max: number, + progress: number, + indeterminate: boolean, +|}; + +export type SmallIcon = {| + icon: string, + level?: number, +|}; + +export type AndroidAllowDataType = { + allow: boolean, + mimeType: string, +}; + +export type NativeAndroidRemoteInput = {| + allowedDataTypes: AndroidAllowDataType[], + allowFreeFormInput?: boolean, + choices: string[], + label?: string, + resultKey: string, +|}; + +export type NativeAndroidAction = {| + action: string, + allowGeneratedReplies?: boolean, + icon: string, + remoteInputs: NativeAndroidRemoteInput[], + semanticAction?: SemanticActionType, + showUserInterface?: boolean, + title: string, +|}; + +export type NativeAndroidNotification = {| + actions?: NativeAndroidAction[], + autoCancel?: boolean, + badgeIconType?: BadgeIconTypeType, + category?: CategoryType, + channelId: string, + clickAction?: string, + color?: string, + colorized?: boolean, + contentInfo?: string, + defaults?: DefaultsType[], + group?: string, + groupAlertBehaviour?: GroupAlertType, + groupSummary?: boolean, + largeIcon?: string, + lights?: Lights, + localOnly?: boolean, + number?: number, + ongoing?: boolean, + onlyAlertOnce?: boolean, + people: string[], + priority?: PriorityType, + progress?: Progress, + // publicVersion: Notification, + remoteInputHistory?: string[], + shortcutId?: string, + showWhen?: boolean, + smallIcon: SmallIcon, + sortKey?: string, + // TODO: style: Style, + ticker?: string, + timeoutAfter?: number, + usesChronometer?: boolean, + vibrate?: number[], + visibility?: VisibilityType, + when?: number, +|}; + +export type IOSAttachmentOptions = {| + typeHint: string, + thumbnailHidden: boolean, + thumbnailClippingRect: { + height: number, + width: number, + x: number, + y: number, + }, + thumbnailTime: number, +|}; + +export type IOSAttachment = {| + identifier: string, + options?: IOSAttachmentOptions, + url: string, +|}; + +export type NativeIOSNotification = {| + alertAction?: string, + attachments: IOSAttachment[], + badge?: number, + category?: string, + hasAction?: boolean, + launchImage?: string, + threadIdentifier?: string, +|}; + +export type Schedule = {| + exact?: boolean, + fireDate: number, + repeatInterval?: 'minute' | 'hour' | 'day' | 'week', +|}; + +export type NativeNotification = {| + android?: NativeAndroidNotification, + body: string, + data: { [string]: string }, + ios?: NativeIOSNotification, + notificationId: string, + schedule?: Schedule, + sound?: string, + subtitle?: string, + title: string, +|}; + +export type NativeNotificationOpen = {| + action: string, + notification: NativeNotification, + results?: { [string]: string }, +|}; diff --git a/tests-new/firebase/modules/perf/Trace.js b/tests-new/firebase/modules/perf/Trace.js new file mode 100644 index 00000000..5fd54608 --- /dev/null +++ b/tests-new/firebase/modules/perf/Trace.js @@ -0,0 +1,28 @@ +/** + * @flow + * Trace representation wrapper + */ +import { getNativeModule } from '../../utils/native'; +import type PerformanceMonitoring from './'; + +export default class Trace { + identifier: string; + _perf: PerformanceMonitoring; + + constructor(perf: PerformanceMonitoring, identifier: string) { + this._perf = perf; + this.identifier = identifier; + } + + start(): void { + getNativeModule(this._perf).start(this.identifier); + } + + stop(): void { + getNativeModule(this._perf).stop(this.identifier); + } + + incrementCounter(event: string): void { + getNativeModule(this._perf).incrementCounter(this.identifier, event); + } +} diff --git a/tests-new/firebase/modules/perf/index.js b/tests-new/firebase/modules/perf/index.js new file mode 100644 index 00000000..c22ed35e --- /dev/null +++ b/tests-new/firebase/modules/perf/index.js @@ -0,0 +1,42 @@ +/** + * @flow + * Performance monitoring representation wrapper + */ +import Trace from './Trace'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; + +import type App from '../core/app'; + +export const MODULE_NAME = 'RNFirebasePerformance'; +export const NAMESPACE = 'perf'; + +export default class PerformanceMonitoring extends ModuleBase { + constructor(app: App) { + super(app, { + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + } + + /** + * Globally enable or disable performance monitoring + * @param enabled + * @returns {*} + */ + setPerformanceCollectionEnabled(enabled: boolean): void { + getNativeModule(this).setPerformanceCollectionEnabled(enabled); + } + + /** + * Returns a new trace instance + * @param trace + */ + newTrace(trace: string): Trace { + return new Trace(this, trace); + } +} + +export const statics = {}; diff --git a/tests-new/firebase/modules/storage/index.js b/tests-new/firebase/modules/storage/index.js new file mode 100644 index 00000000..457de361 --- /dev/null +++ b/tests-new/firebase/modules/storage/index.js @@ -0,0 +1,164 @@ +/** + * @flow + * Storage representation wrapper + */ +import { NativeModules } from 'react-native'; + +import StorageRef from './reference'; +import { getAppEventName, SharedEventEmitter } from '../../utils/events'; +import { getLogger } from '../../utils/log'; +import ModuleBase from '../../utils/ModuleBase'; +import { getNativeModule } from '../../utils/native'; + +import type App from '../core/app'; + +const FirebaseStorage = NativeModules.RNFirebaseStorage; + +const NATIVE_EVENTS = ['storage_event', 'storage_error']; + +export const MODULE_NAME = 'RNFirebaseStorage'; +export const NAMESPACE = 'storage'; + +export default class Storage extends ModuleBase { + /** + * + * @param app + * @param options + */ + constructor(app: App) { + super(app, { + events: NATIVE_EVENTS, + moduleName: MODULE_NAME, + multiApp: true, + hasShards: false, + namespace: NAMESPACE, + }); + + SharedEventEmitter.addListener( + getAppEventName(this, 'storage_event'), + this._handleStorageEvent.bind(this) + ); + + SharedEventEmitter.addListener( + getAppEventName(this, 'storage_error'), + this._handleStorageEvent.bind(this) + ); + } + + /** + * Returns a reference for the given path in the default bucket. + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#ref + * @param path + * @returns {StorageReference} + */ + ref(path: string): StorageRef { + return new StorageRef(this, path); + } + + /** + * Returns a reference for the given absolute URL. + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#refFromURL + * @param url + * @returns {StorageReference} + */ + refFromURL(url: string): StorageRef { + // TODO don't think this is correct? + return new StorageRef(this, `url::${url}`); + } + + /** + * setMaxOperationRetryTime + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime + * @param time The new maximum operation retry time in milliseconds. + */ + setMaxOperationRetryTime(time: number): void { + getNativeModule(this).setMaxOperationRetryTime(time); + } + + /** + * setMaxUploadRetryTime + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime + * @param time The new maximum upload retry time in milliseconds. + */ + setMaxUploadRetryTime(time: number): void { + getNativeModule(this).setMaxUploadRetryTime(time); + } + + /** + * setMaxDownloadRetryTime + * @url N/A + * @param time The new maximum download retry time in milliseconds. + */ + setMaxDownloadRetryTime(time: number): void { + getNativeModule(this).setMaxDownloadRetryTime(time); + } + + /** + * INTERNALS + */ + _getSubEventName(path: string, eventName: string) { + return getAppEventName(this, `${path}-${eventName}`); + } + + _handleStorageEvent(event: Object) { + const { path, eventName } = event; + const body = event.body || {}; + + getLogger(this).debug('_handleStorageEvent: ', path, eventName, body); + SharedEventEmitter.emit(this._getSubEventName(path, eventName), body); + } + + _handleStorageError(err: Object) { + const { path, eventName } = err; + const body = err.body || {}; + + getLogger(this).debug('_handleStorageError ->', err); + SharedEventEmitter.emit(this._getSubEventName(path, eventName), body); + } + + _addListener( + path: string, + eventName: string, + cb: (evt: Object) => Object + ): void { + SharedEventEmitter.addListener(this._getSubEventName(path, eventName), cb); + } + + _removeListener( + path: string, + eventName: string, + origCB: (evt: Object) => Object + ): void { + SharedEventEmitter.removeListener( + this._getSubEventName(path, eventName), + origCB + ); + } +} + +export const statics = { + TaskEvent: { + STATE_CHANGED: 'state_changed', + }, + TaskState: { + RUNNING: 'running', + PAUSED: 'paused', + SUCCESS: 'success', + CANCELLED: 'cancelled', + ERROR: 'error', + }, + Native: FirebaseStorage + ? { + MAIN_BUNDLE_PATH: FirebaseStorage.MAIN_BUNDLE_PATH, + CACHES_DIRECTORY_PATH: FirebaseStorage.CACHES_DIRECTORY_PATH, + DOCUMENT_DIRECTORY_PATH: FirebaseStorage.DOCUMENT_DIRECTORY_PATH, + EXTERNAL_DIRECTORY_PATH: FirebaseStorage.EXTERNAL_DIRECTORY_PATH, + EXTERNAL_STORAGE_DIRECTORY_PATH: + FirebaseStorage.EXTERNAL_STORAGE_DIRECTORY_PATH, + TEMP_DIRECTORY_PATH: FirebaseStorage.TEMP_DIRECTORY_PATH, + LIBRARY_DIRECTORY_PATH: FirebaseStorage.LIBRARY_DIRECTORY_PATH, + FILETYPE_REGULAR: FirebaseStorage.FILETYPE_REGULAR, + FILETYPE_DIRECTORY: FirebaseStorage.FILETYPE_DIRECTORY, + } + : {}, +}; diff --git a/tests-new/firebase/modules/storage/reference.js b/tests-new/firebase/modules/storage/reference.js new file mode 100644 index 00000000..b58a3007 --- /dev/null +++ b/tests-new/firebase/modules/storage/reference.js @@ -0,0 +1,106 @@ +/** + * @flow + * StorageReference representation wrapper + */ +import ReferenceBase from '../../utils/ReferenceBase'; +import StorageTask, { UPLOAD_TASK, DOWNLOAD_TASK } from './task'; +import { getNativeModule } from '../../utils/native'; +import type Storage from './'; + +/** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference + */ +export default class StorageReference extends ReferenceBase { + _storage: Storage; + + constructor(storage: Storage, path: string) { + super(path); + this._storage = storage; + } + + get fullPath(): string { + return this.path; + } + + toString(): string { + return `gs://${this._storage.app.options.storageBucket}${this.path}`; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#child + * @param path + * @returns {StorageReference} + */ + child(path: string): StorageReference { + return new StorageReference(this._storage, `${this.path}/${path}`); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete + * @returns {Promise.|*} + */ + delete(): Promise { + return getNativeModule(this._storage).delete(this.path); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL + * @returns {Promise.|*} + */ + getDownloadURL(): Promise { + return getNativeModule(this._storage).getDownloadURL(this.path); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata + * @returns {Promise.|*} + */ + getMetadata(): Promise { + return getNativeModule(this._storage).getMetadata(this.path); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata + * @param metadata + * @returns {Promise.|*} + */ + updateMetadata(metadata: Object = {}): Promise { + return getNativeModule(this._storage).updateMetadata(this.path, metadata); + } + + /** + * Downloads a reference to the device + * @param {String} filePath Where to store the file + * @return {Promise} + */ + downloadFile(filePath: string): Promise { + return new StorageTask( + DOWNLOAD_TASK, + getNativeModule(this._storage).downloadFile(this.path, filePath), + this + ); + } + + /** + * Alias to putFile + * @returns {StorageReference.putFile} + */ + get put(): (Object, Object) => Promise { + return this.putFile; + } + + /** + * Upload a file path + * @param {string} filePath The local path of the file + * @param {object} metadata An object containing metadata + * @return {Promise} + */ + putFile(filePath: Object, metadata: Object = {}): Promise { + const _filePath = filePath.replace('file://', ''); + return new StorageTask( + UPLOAD_TASK, + getNativeModule(this._storage).putFile(this.path, _filePath, metadata), + this + ); + } +} diff --git a/tests-new/firebase/modules/storage/task.js b/tests-new/firebase/modules/storage/task.js new file mode 100644 index 00000000..0fdc6329 --- /dev/null +++ b/tests-new/firebase/modules/storage/task.js @@ -0,0 +1,215 @@ +/** + * @flow + * UploadTask representation wrapper + */ +import { statics as StorageStatics } from './'; +import { isFunction } from './../../utils'; +import type Storage from './'; +import type StorageReference from './reference'; + +export const UPLOAD_TASK = 'upload'; +export const DOWNLOAD_TASK = 'download'; + +declare type UploadTaskSnapshotType = { + bytesTransferred: number, + downloadURL: string | null, + metadata: Object, // TODO flow type def for https://firebase.google.com/docs/reference/js/firebase.storage.FullMetadata.html + ref: StorageReference, + state: + | typeof StorageStatics.TaskState.RUNNING + | typeof StorageStatics.TaskState.PAUSED + | typeof StorageStatics.TaskState.SUCCESS + | typeof StorageStatics.TaskState.CANCELLED + | typeof StorageStatics.TaskState.ERROR, + task: StorageTask, + totalBytes: number, +}; + +declare type FuncSnapshotType = + | null + | ((snapshot: UploadTaskSnapshotType) => any); + +declare type FuncErrorType = null | ((error: Error) => any); + +declare type NextOrObserverType = + | null + | { + next?: FuncSnapshotType, + error?: FuncErrorType, + complete?: FuncSnapshotType, + } + | FuncSnapshotType; + +/** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask + */ +export default class StorageTask { + type: typeof UPLOAD_TASK | typeof DOWNLOAD_TASK; + ref: StorageReference; + storage: Storage; + path: string; + then: () => Promise<*>; + catch: () => Promise<*>; + + constructor( + type: typeof UPLOAD_TASK | typeof DOWNLOAD_TASK, + promise: Promise<*>, + storageRef: StorageReference + ) { + this.type = type; + this.ref = storageRef; + this.storage = storageRef._storage; + this.path = storageRef.path; + + // 'proxy' original promise + this.then = promise.then.bind(promise); + this.catch = promise.catch.bind(promise); + } + + /** + * Intercepts a native snapshot result object attaches ref / task instances + * and calls the original function + * @returns {Promise.} + * @private + */ + _interceptSnapshotEvent(f: ?Function): null | (() => *) { + if (!isFunction(f)) return null; + return snapshot => { + const _snapshot = Object.assign({}, snapshot); + _snapshot.task = this; + _snapshot.ref = this.ref; + return f && f(_snapshot); + }; + } + + /** + * Intercepts a error object form native and converts to a JS Error + * @param f + * @returns {*} + * @private + */ + _interceptErrorEvent(f: ?Function): null | (Error => *) { + if (!isFunction(f)) return null; + return error => { + const _error = new Error(error.message); + // $FlowExpectedError + _error.code = error.code; + return f && f(_error); + }; + } + + /** + * + * @param nextOrObserver + * @param error + * @param complete + * @returns {function()} + * @private + */ + _subscribe( + nextOrObserver: NextOrObserverType, + error: FuncErrorType, + complete: FuncSnapshotType + ): Function { + let _error; + let _next; + let _complete; + + if (typeof nextOrObserver === 'function') { + _error = this._interceptErrorEvent(error); + _next = this._interceptSnapshotEvent(nextOrObserver); + _complete = this._interceptSnapshotEvent(complete); + } else if (nextOrObserver) { + _error = this._interceptErrorEvent(nextOrObserver.error); + _next = this._interceptSnapshotEvent(nextOrObserver.next); + _complete = this._interceptSnapshotEvent(nextOrObserver.complete); + } + + if (_next) { + this.storage._addListener( + this.path, + StorageStatics.TaskEvent.STATE_CHANGED, + _next + ); + } + if (_error) { + this.storage._addListener(this.path, `${this.type}_failure`, _error); + } + if (_complete) { + this.storage._addListener(this.path, `${this.type}_success`, _complete); + } + + return () => { + if (_next) + this.storage._removeListener( + this.path, + StorageStatics.TaskEvent.STATE_CHANGED, + _next + ); + if (_error) + this.storage._removeListener(this.path, `${this.type}_failure`, _error); + if (_complete) + this.storage._removeListener( + this.path, + `${this.type}_success`, + _complete + ); + }; + } + + /** + * + * @param event + * @param nextOrObserver + * @param error + * @param complete + * @returns {function()} + */ + on( + event: string = StorageStatics.TaskEvent.STATE_CHANGED, + nextOrObserver: NextOrObserverType, + error: FuncErrorType, + complete: FuncSnapshotType + ): Function { + if (!event) { + throw new Error( + "StorageTask.on listener is missing required string argument 'event'." + ); + } + + if (event !== StorageStatics.TaskEvent.STATE_CHANGED) { + throw new Error( + `StorageTask.on event argument must be a string with a value of '${ + StorageStatics.TaskEvent.STATE_CHANGED + }'` + ); + } + + // if only event provided return the subscriber function + if (!nextOrObserver && !error && !complete) { + return this._subscribe.bind(this); + } + + return this._subscribe(nextOrObserver, error, complete); + } + + pause() { + throw new Error( + '.pause() is not currently supported by react-native-firebase' + ); + } + + resume() { + // todo + throw new Error( + '.resume() is not currently supported by react-native-firebase' + ); + } + + cancel() { + // todo + throw new Error( + '.cancel() is not currently supported by react-native-firebase' + ); + } +} diff --git a/tests-new/firebase/modules/utils/index.js b/tests-new/firebase/modules/utils/index.js new file mode 100644 index 00000000..ce48a2ad --- /dev/null +++ b/tests-new/firebase/modules/utils/index.js @@ -0,0 +1,115 @@ +// @flow +import { NativeModules } from 'react-native'; +import INTERNALS from '../../utils/internals'; +import { isIOS } from '../../utils'; +import ModuleBase from '../../utils/ModuleBase'; +import type App from '../core/app'; + +const FirebaseCoreModule = NativeModules.RNFirebase; + +type GoogleApiAvailabilityType = { + status: number, + isAvailable: boolean, + isUserResolvableError?: boolean, + hasResolution?: boolean, + error?: string, +}; + +export const MODULE_NAME = 'RNFirebaseUtils'; +export const NAMESPACE = 'utils'; + +export default class RNFirebaseUtils extends ModuleBase { + constructor(app: App) { + super(app, { + moduleName: MODULE_NAME, + multiApp: false, + hasShards: false, + namespace: NAMESPACE, + }); + } + + /** + * + */ + checkPlayServicesAvailability() { + if (isIOS) return; + + const { status } = this.playServicesAvailability; + + if (!this.playServicesAvailability.isAvailable) { + if ( + INTERNALS.OPTIONS.promptOnMissingPlayServices && + this.playServicesAvailability.isUserResolvableError + ) { + this.promptForPlayServices(); + } else { + const error = INTERNALS.STRINGS.ERROR_PLAY_SERVICES(status); + if (INTERNALS.OPTIONS.errorOnMissingPlayServices) { + if (status === 2) + console.warn(error); // only warn if it exists but may need an update + else throw new Error(error); + } else { + console.warn(error); + } + } + } + } + + promptForPlayServices() { + if (isIOS) return null; + return FirebaseCoreModule.promptForPlayServices(); + } + + resolutionForPlayServices() { + if (isIOS) return null; + return FirebaseCoreModule.resolutionForPlayServices(); + } + + makePlayServicesAvailable() { + if (isIOS) return null; + return FirebaseCoreModule.makePlayServicesAvailable(); + } + + /** + * Set the global logging level for all logs. + * + * @param logLevel + */ + set logLevel(logLevel: string) { + INTERNALS.OPTIONS.logLevel = logLevel; + } + + /** + * Returns props from the android GoogleApiAvailability sdk + * @android + * @return {RNFirebase.GoogleApiAvailabilityType|{isAvailable: boolean, status: number}} + */ + get playServicesAvailability(): GoogleApiAvailabilityType { + return ( + FirebaseCoreModule.playServicesAvailability || { + isAvailable: true, + status: 0, + } + ); + } + + /** + * Enable/Disable throwing an error or warning on detecting a play services problem + * @android + * @param bool + */ + set errorOnMissingPlayServices(bool: boolean) { + INTERNALS.OPTIONS.errorOnMissingPlayServices = bool; + } + + /** + * Enable/Disable automatic prompting of the play services update dialog + * @android + * @param bool + */ + set promptOnMissingPlayServices(bool: boolean) { + INTERNALS.OPTIONS.promptOnMissingPlayServices = bool; + } +} + +export const statics = {}; diff --git a/tests-new/firebase/types/index.js b/tests-new/firebase/types/index.js new file mode 100644 index 00000000..c0ea162a --- /dev/null +++ b/tests-new/firebase/types/index.js @@ -0,0 +1,232 @@ +/* @flow */ +import type AdMob from '../modules/admob'; +import { typeof statics as AdMobStatics } from '../modules/admob'; +import type Analytics from '../modules/analytics'; +import { typeof statics as AnalyticsStatics } from '../modules/analytics'; +import type Auth from '../modules/auth'; +import { typeof statics as AuthStatics } from '../modules/auth'; +import type Config from '../modules/config'; +import { typeof statics as ConfigStatics } from '../modules/config'; +import type Crash from '../modules/crash'; +import { typeof statics as CrashStatics } from '../modules/crash'; +import type Crashlytics from '../modules/fabric/crashlytics'; +import { typeof statics as CrashlyticsStatics } from '../modules/fabric/crashlytics'; +import type Database from '../modules/database'; +import { typeof statics as DatabaseStatics } from '../modules/database'; +import type Firestore from '../modules/firestore'; +import { typeof statics as FirestoreStatics } from '../modules/firestore'; +import type InstanceId from '../modules/instanceid'; +import { typeof statics as InstanceIdStatics } from '../modules/instanceid'; +import type Invites from '../modules/invites'; +import { typeof statics as InvitesStatics } from '../modules/invites'; +import type Links from '../modules/links'; +import { typeof statics as LinksStatics } from '../modules/links'; +import type Messaging from '../modules/messaging'; +import { typeof statics as MessagingStatics } from '../modules/messaging'; +import type Notifications from '../modules/notifications'; +import { typeof statics as NotificationsStatics } from '../modules/notifications'; +import type ModuleBase from '../utils/ModuleBase'; +import type Performance from '../modules/perf'; +import { typeof statics as PerformanceStatics } from '../modules/perf'; +import type Storage from '../modules/storage'; +import { typeof statics as StorageStatics } from '../modules/storage'; +import type Utils from '../modules/utils'; +import { typeof statics as UtilsStatics } from '../modules/utils'; + +/* Core types */ +export type FirebaseError = { + message: string, + name: string, + code: string, + stack: string, + path: string, + details: string, + modifiers: string, +}; + +export type FirebaseModule = $Subtype; + +export type FirebaseModuleConfig = { + events?: string[], + moduleName: FirebaseModuleName, + multiApp: boolean, + hasShards: boolean, + namespace: FirebaseNamespace, +}; + +export type FirebaseModuleName = + | 'RNFirebaseAdMob' + | 'RNFirebaseAnalytics' + | 'RNFirebaseAuth' + | 'RNFirebaseRemoteConfig' + | 'RNFirebaseCrash' + | 'RNFirebaseCrashlytics' + | 'RNFirebaseDatabase' + | 'RNFirebaseFirestore' + | 'RNFirebaseInstanceId' + | 'RNFirebaseInvites' + | 'RNFirebaseLinks' + | 'RNFirebaseMessaging' + | 'RNFirebaseNotifications' + | 'RNFirebasePerformance' + | 'RNFirebaseStorage' + | 'RNFirebaseUtils'; + +export type FirebaseNamespace = + | 'admob' + | 'analytics' + | 'auth' + | 'config' + | 'crash' + | 'crashlytics' + | 'database' + | 'firestore' + | 'instanceid' + | 'invites' + | 'links' + | 'messaging' + | 'notifications' + | 'perf' + | 'storage' + | 'utils'; + +export type FirebaseOptions = { + apiKey: string, + appId: string, + databaseURL: string, + messagingSenderId: string, + projectId: string, + storageBucket: string, +}; + +export type FirebaseModuleAndStatics = { + (): M, + nativeModuleExists: boolean, +} & S; + +export type FirebaseStatics = $Subtype; + +/* Admob types */ + +export type AdMobModule = { + (): AdMob, + nativeModuleExists: boolean, +} & AdMobStatics; + +/* Analytics types */ + +export type AnalyticsModule = { + (): Analytics, + nativeModuleExists: boolean, +} & AnalyticsStatics; + +/* Remote Config types */ + +export type ConfigModule = { + (): Config, + nativeModuleExists: boolean, +} & ConfigStatics; + +/* Auth types */ + +export type AuthModule = { + (): Auth, + nativeModuleExists: boolean, +} & AuthStatics; + +/* Crash types */ + +export type CrashModule = { + (): Crash, + nativeModuleExists: boolean, +} & CrashStatics; + +/* Database types */ + +export type DatabaseModule = { + (): Database, + nativeModuleExists: boolean, +} & DatabaseStatics; + +export type DatabaseModifier = { + id: string, + type: 'orderBy' | 'limit' | 'filter', + name?: string, + key?: string, + limit?: number, + value?: any, + valueType?: string, +}; + +/* Fabric types */ +export type CrashlyticsModule = { + (): Crashlytics, + nativeModuleExists: boolean, +} & CrashlyticsStatics; + +export type FabricModule = { + crashlytics: CrashlyticsModule, +}; + +/* Firestore types */ + +export type FirestoreModule = { + (): Firestore, + nativeModuleExists: boolean, +} & FirestoreStatics; + +/* InstanceId types */ + +export type InstanceIdModule = { + (): InstanceId, + nativeModuleExists: boolean, +} & InstanceIdStatics; + +/* Invites types */ + +export type InvitesModule = { + (): Invites, + nativeModuleExists: boolean, +} & InvitesStatics; + +/* Links types */ + +export type LinksModule = { + (): Links, + nativeModuleExists: boolean, +} & LinksStatics; + +/* Messaging types */ + +export type MessagingModule = { + (): Messaging, + nativeModuleExists: boolean, +} & MessagingStatics; + +/* Notifications types */ + +export type NotificationsModule = { + (): Notifications, + nativeModuleExists: boolean, +} & NotificationsStatics; + +/* Performance types */ + +export type PerformanceModule = { + (): Performance, + nativeModuleExists: boolean, +} & PerformanceStatics; + +/* Storage types */ + +export type StorageModule = { + (): Storage, + nativeModuleExists: boolean, +} & StorageStatics; + +/* Utils types */ + +export type UtilsModule = { + (): Utils, + nativeModuleExists: boolean, +} & UtilsStatics; diff --git a/tests-new/firebase/utils/ModuleBase.js b/tests-new/firebase/utils/ModuleBase.js new file mode 100644 index 00000000..ffa78088 --- /dev/null +++ b/tests-new/firebase/utils/ModuleBase.js @@ -0,0 +1,47 @@ +/** + * @flow + */ +import { initialiseLogger } from './log'; +import { initialiseNativeModule } from './native'; + +import type App from '../modules/core/app'; +import type { FirebaseModuleConfig, FirebaseNamespace } from '../types'; + +export default class ModuleBase { + _app: App; + _serviceUrl: ?string; + namespace: FirebaseNamespace; + + /** + * + * @param app + * @param config + */ + constructor(app: App, config: FirebaseModuleConfig, serviceUrl: ?string) { + if (!config.moduleName) { + throw new Error('Missing module name'); + } + if (!config.namespace) { + throw new Error('Missing namespace'); + } + const { moduleName } = config; + this._app = app; + this._serviceUrl = serviceUrl; + this.namespace = config.namespace; + + // check if native module exists as all native + initialiseNativeModule(this, config, serviceUrl); + initialiseLogger( + this, + `${app.name}:${moduleName.replace('RNFirebase', '')}` + ); + } + + /** + * Returns the App instance for current module + * @return {*} + */ + get app(): App { + return this._app; + } +} diff --git a/tests-new/firebase/utils/ReferenceBase.js b/tests-new/firebase/utils/ReferenceBase.js new file mode 100644 index 00000000..32056422 --- /dev/null +++ b/tests-new/firebase/utils/ReferenceBase.js @@ -0,0 +1,29 @@ +/** + * @flow + */ +export default class ReferenceBase { + path: string; + + constructor(path: string) { + if (path) { + this.path = + path.length > 1 && path.endsWith('/') + ? path.substring(0, path.length - 1) + : path; + } else { + this.path = '/'; + } + } + + /** + * The last part of a Reference's path (after the last '/') + * The key of a root Reference is null. + * @type {String} + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#key} + */ + get key(): string | null { + return this.path === '/' + ? null + : this.path.substring(this.path.lastIndexOf('/') + 1); + } +} diff --git a/tests-new/firebase/utils/SyncTree.js b/tests-new/firebase/utils/SyncTree.js new file mode 100644 index 00000000..7c72b7b6 --- /dev/null +++ b/tests-new/firebase/utils/SyncTree.js @@ -0,0 +1,334 @@ +/** + * @flow + */ +import { NativeEventEmitter, NativeModules } from 'react-native'; + +import { SharedEventEmitter } from './events'; +import DataSnapshot from '../modules/database/DataSnapshot'; +import DatabaseReference from '../modules/database/Reference'; +import { isString, nativeToJSError } from '../utils'; + +type Listener = DataSnapshot => any; + +type Registration = { + key: string, + path: string, + once?: boolean, + appName: string, + eventType: string, + listener: Listener, + eventRegistrationKey: string, + ref: DatabaseReference, +}; + +/** + * Internally used to manage firebase database realtime event + * subscriptions and keep the listeners in sync in js vs native. + */ +class SyncTree { + _nativeEmitter: NativeEventEmitter; + _reverseLookup: { [string]: Registration }; + _tree: { [string]: { [string]: { [string]: Listener } } }; + + constructor() { + this._tree = {}; + this._reverseLookup = {}; + if (NativeModules.RNFirebaseDatabase) { + this._nativeEmitter = new NativeEventEmitter( + NativeModules.RNFirebaseDatabase + ); + this._nativeEmitter.addListener( + 'database_sync_event', + this._handleSyncEvent.bind(this) + ); + } + } + + /** + * + * @param event + * @private + */ + _handleSyncEvent(event) { + if (event.error) { + this._handleErrorEvent(event); + } else { + this._handleValueEvent(event); + } + } + + /** + * Routes native database 'on' events to their js equivalent counterpart. + * If there 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); + + if (!registration) { + // registration previously revoked + // notify native that the registration + // no longer exists so it can remove + // the native listeners + return NativeModules.RNFirebaseDatabase.off(key, eventRegistrationKey); + } + + const { snapshot, previousChildName } = event.data; + + // forward on to users .on(successCallback <-- listener + return SharedEventEmitter.emit( + eventRegistrationKey, + new DataSnapshot(registration.ref, snapshot), + previousChildName + ); + } + + /** + * Routes native database query listener cancellation events to their js counterparts. + * + * @param event + * @private + */ + _handleErrorEvent(event) { + // console.log('SyncTree.ERROR >>>', event); + const { code, message } = event.error; + 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 = nativeToJSError(code, message, { ref: registration.ref }); + + // 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); + } + } + + /** + * Returns registration information such as appName, ref, path and registration keys. + * + * @param registration + * @return {null} + */ + getRegistration(registration: string): Registration | null { + return this._reverseLookup[registration] + ? Object.assign({}, this._reverseLookup[registration]) + : null; + } + + /** + * Removes all listeners for the specified registration keys. + * + * @param registrations + * @return {number} + */ + removeListenersForRegistrations(registrations: string | string[]): number { + 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: () => any, registrations: string[]) { + 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: string): string[] { + 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: string, eventType: string): string[] { + 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: string, + eventType: string, + listener: Function + ): ?string { + 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 parameters + * @param listener + * @return {String} + */ + addRegistration(registration: Registration): string { + 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: string): boolean { + 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) { + NativeModules.RNFirebaseDatabase.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: any[]) => { + this.removeRegistration(registration); + listener(...args); + }; + } +} + +export default new SyncTree(); diff --git a/tests-new/firebase/utils/apps.js b/tests-new/firebase/utils/apps.js new file mode 100644 index 00000000..57ac16ea --- /dev/null +++ b/tests-new/firebase/utils/apps.js @@ -0,0 +1,203 @@ +/** + * @flow + */ +import { NativeModules } from 'react-native'; +import App from '../modules/core/app'; +import INTERNALS from './internals'; +import { isAndroid, isObject, isString } from './'; + +import type { + FirebaseModule, + FirebaseModuleAndStatics, + FirebaseModuleName, + FirebaseNamespace, + FirebaseOptions, + FirebaseStatics, +} from '../types'; + +const FirebaseCoreModule = NativeModules.RNFirebase; + +const APPS: { [string]: App } = {}; +const APP_MODULES: { [string]: { [string]: FirebaseModule } } = {}; +const DEFAULT_APP_NAME = '[DEFAULT]'; + +export default { + DEFAULT_APP_NAME, + + app(name?: string): App { + const _name = name ? name.toUpperCase() : DEFAULT_APP_NAME; + const app = APPS[_name]; + if (!app) throw new Error(INTERNALS.STRINGS.ERROR_APP_NOT_INIT(_name)); + return app; + }, + + apps(): Array { + // $FlowExpectedError: Object.values always returns mixed type: https://github.com/facebook/flow/issues/2221 + return Object.values(APPS); + }, + + /** + * + * @param app + * @param namespace + * @param InstanceClass + * @return {function()} + * @private + */ + appModule( + app: App, + namespace: FirebaseNamespace, + InstanceClass: Class + ): () => FirebaseModule { + return (serviceUrl: ?string = null): M => { + if (serviceUrl && namespace !== 'database') { + throw new Error( + INTERNALS.STRINGS.ERROR_INIT_SERVICE_URL_UNSUPPORTED(namespace) + ); + } + + const appOrShardName = serviceUrl || app.name; + if (!APP_MODULES[appOrShardName]) { + APP_MODULES[appOrShardName] = {}; + } + + if ( + isAndroid && + namespace !== 'utils' && + !INTERNALS.FLAGS.checkedPlayServices + ) { + INTERNALS.FLAGS.checkedPlayServices = true; + app.utils().checkPlayServicesAvailability(); + } + + if (!APP_MODULES[appOrShardName][namespace]) { + APP_MODULES[appOrShardName][namespace] = new InstanceClass( + serviceUrl || app, + app.options + ); + } + + return APP_MODULES[appOrShardName][namespace]; + }; + }, + + deleteApp(name: string): Promise { + const app = APPS[name]; + if (!app) return Promise.resolve(true); + + // https://firebase.google.com/docs/reference/js/firebase.app.App#delete + return app.delete().then(() => { + delete APPS[name]; + return true; + }); + }, + + /** + * Web SDK initializeApp + * + * @param options + * @param name + * @return {*} + */ + initializeApp(options: FirebaseOptions, name: string): App { + if (name && !isString(name)) { + throw new Error(INTERNALS.STRINGS.ERROR_INIT_STRING_NAME); + } + + const _name = (name || DEFAULT_APP_NAME).toUpperCase(); + + // return an existing app if found + // todo in v4 remove deprecation and throw an error + if (APPS[_name]) { + console.warn(INTERNALS.STRINGS.WARN_INITIALIZE_DEPRECATION); + return APPS[_name]; + } + + // only validate if app doesn't already exist + // to allow apps already initialized natively + // to still go through init without erroring (backwards compatibility) + if (!isObject(options)) { + throw new Error(INTERNALS.STRINGS.ERROR_INIT_OBJECT); + } + + if (!options.apiKey) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('apiKey')); + } + + if (!options.appId) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('appId')); + } + + if (!options.databaseURL) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('databaseURL')); + } + + if (!options.messagingSenderId) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('messagingSenderId')); + } + + if (!options.projectId) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('projectId')); + } + + if (!options.storageBucket) { + throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('storageBucket')); + } + + APPS[_name] = new App(_name, options); + + return APPS[_name]; + }, + + /** + * Bootstraps all native app instances that were discovered on boot + */ + initializeNativeApps() { + for (let i = 0, len = FirebaseCoreModule.apps.length; i < len; i++) { + const app = FirebaseCoreModule.apps[i]; + const options = Object.assign({}, app); + delete options.name; + APPS[app.name] = new App(app.name, options, true); + } + }, + + /** + * + * @param namespace + * @param statics + * @param moduleName + * @return {function(App=)} + */ + moduleAndStatics( + namespace: FirebaseNamespace, + statics: S, + moduleName: FirebaseModuleName + ): FirebaseModuleAndStatics { + const getModule = (appOrUrl?: App | string): FirebaseModule => { + let _app = appOrUrl; + let _serviceUrl: ?string = null; + if (typeof appOrUrl === 'string' && namespace === 'database') { + _app = null; + _serviceUrl = appOrUrl; + } + + // throw an error if it's not a valid app instance + if (_app && !(_app instanceof App)) + throw new Error(INTERNALS.STRINGS.ERROR_NOT_APP(namespace)); + else if (!_app) + // default to the 'DEFAULT' app if no arg provided - will throw an error + // if default app not initialized + _app = this.app(DEFAULT_APP_NAME); + if (namespace === 'crashlytics') { + return _app.fabric[namespace](); + } + // $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323 + const module = _app[namespace]; + return module(_serviceUrl); + }; + + return Object.assign(getModule, statics, { + nativeModuleExists: !!NativeModules[moduleName], + }); + }, +}; diff --git a/tests-new/firebase/utils/emitter/EmitterSubscription.js b/tests-new/firebase/utils/emitter/EmitterSubscription.js new file mode 100644 index 00000000..5d213b3d --- /dev/null +++ b/tests-new/firebase/utils/emitter/EmitterSubscription.js @@ -0,0 +1,61 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @noflow + */ +'use strict'; + +const EventSubscription = require('./EventSubscription'); + +import type EventEmitter from './EventEmitter'; +import type EventSubscriptionVendor from './EventSubscriptionVendor'; + +/** + * EmitterSubscription represents a subscription with listener and context data. + */ +class EmitterSubscription extends EventSubscription { + + emitter: EventEmitter; + listener: Function; + context: ?Object; + + /** + * @param {EventEmitter} emitter - The event emitter that registered this + * subscription + * @param {EventSubscriptionVendor} subscriber - The subscriber that controls + * this subscription + * @param {function} listener - Function to invoke when the specified event is + * emitted + * @param {*} context - Optional context object to use when invoking the + * listener + */ + constructor( + emitter: EventEmitter, + subscriber: EventSubscriptionVendor, + listener: Function, + context: ?Object + ) { + super(subscriber); + this.emitter = emitter; + this.listener = listener; + this.context = context; + } + + /** + * Removes this subscription from the emitter that registered it. + * Note: we're overriding the `remove()` method of EventSubscription here + * but deliberately not calling `super.remove()` as the responsibility + * for removing the subscription lies with the EventEmitter. + */ + remove() { + this.emitter.removeSubscription(this); + } +} + +module.exports = EmitterSubscription; diff --git a/tests-new/firebase/utils/emitter/EventEmitter.js b/tests-new/firebase/utils/emitter/EventEmitter.js new file mode 100644 index 00000000..96487b1e --- /dev/null +++ b/tests-new/firebase/utils/emitter/EventEmitter.js @@ -0,0 +1,220 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @noflow + * @typecheck + */ +'use strict'; + +const EmitterSubscription = require('./EmitterSubscription'); +const EventSubscriptionVendor = require('./EventSubscriptionVendor'); + +const emptyFunction = require('fbjs/lib/emptyFunction'); +const invariant = require('fbjs/lib/invariant'); + +/** + * @class EventEmitter + * @description + * An EventEmitter is responsible for managing a set of listeners and publishing + * events to them when it is told that such events happened. In addition to the + * data for the given event it also sends a event control object which allows + * the listeners/handlers to prevent the default behavior of the given event. + * + * The emitter is designed to be generic enough to support all the different + * contexts in which one might want to emit events. It is a simple multicast + * mechanism on top of which extra functionality can be composed. For example, a + * more advanced emitter may use an EventHolder and EventFactory. + */ +class EventEmitter { + + _subscriber: EventSubscriptionVendor; + _currentSubscription: ?EmitterSubscription; + + /** + * @constructor + * + * @param {EventSubscriptionVendor} subscriber - Optional subscriber instance + * to use. If omitted, a new subscriber will be created for the emitter. + */ + constructor(subscriber: ?EventSubscriptionVendor) { + this._subscriber = subscriber || new EventSubscriptionVendor(); + } + + /** + * Adds a listener to be invoked when events of the specified type are + * emitted. An optional calling context may be provided. The data arguments + * emitted will be passed to the listener function. + * + * TODO: Annotate the listener arg's type. This is tricky because listeners + * can be invoked with varargs. + * + * @param {string} eventType - Name of the event to listen to + * @param {function} listener - Function to invoke when the specified event is + * emitted + * @param {*} context - Optional context object to use when invoking the + * listener + */ + addListener( + eventType: string, listener: Function, context: ?Object): EmitterSubscription { + + return (this._subscriber.addSubscription( + eventType, + new EmitterSubscription(this, this._subscriber, listener, context) + ) : any); + } + + /** + * Similar to addListener, except that the listener is removed after it is + * invoked once. + * + * @param {string} eventType - Name of the event to listen to + * @param {function} listener - Function to invoke only once when the + * specified event is emitted + * @param {*} context - Optional context object to use when invoking the + * listener + */ + once(eventType: string, listener: Function, context: ?Object): EmitterSubscription { + return this.addListener(eventType, (...args) => { + this.removeCurrentListener(); + listener.apply(context, args); + }); + } + + /** + * Removes all of the registered listeners, including those registered as + * listener maps. + * + * @param {?string} eventType - Optional name of the event whose registered + * listeners to remove + */ + removeAllListeners(eventType: ?string) { + this._subscriber.removeAllSubscriptions(eventType); + } + + /** + * Provides an API that can be called during an eventing cycle to remove the + * last listener that was invoked. This allows a developer to provide an event + * object that can remove the listener (or listener map) during the + * invocation. + * + * If it is called when not inside of an emitting cycle it will throw. + * + * @throws {Error} When called not during an eventing cycle + * + * @example + * var subscription = emitter.addListenerMap({ + * someEvent: function(data, event) { + * console.log(data); + * emitter.removeCurrentListener(); + * } + * }); + * + * emitter.emit('someEvent', 'abc'); // logs 'abc' + * emitter.emit('someEvent', 'def'); // does not log anything + */ + removeCurrentListener() { + invariant( + !!this._currentSubscription, + 'Not in an emitting cycle; there is no current subscription' + ); + this.removeSubscription(this._currentSubscription); + } + + /** + * Removes a specific subscription. Called by the `remove()` method of the + * subscription itself to ensure any necessary cleanup is performed. + */ + removeSubscription(subscription: EmitterSubscription) { + invariant( + subscription.emitter === this, + 'Subscription does not belong to this emitter.' + ); + this._subscriber.removeSubscription(subscription); + } + + /** + * Returns an array of listeners that are currently registered for the given + * event. + * + * @param {string} eventType - Name of the event to query + * @returns {array} + */ + listeners(eventType: string): [EmitterSubscription] { + const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + return subscriptions + ? subscriptions.filter(emptyFunction.thatReturnsTrue).map( + function(subscription) { + return subscription.listener; + }) + : []; + } + + /** + * Emits an event of the given type with the given data. All handlers of that + * particular type will be notified. + * + * @param {string} eventType - Name of the event to emit + * @param {...*} Arbitrary arguments to be passed to each registered listener + * + * @example + * emitter.addListener('someEvent', function(message) { + * console.log(message); + * }); + * + * emitter.emit('someEvent', 'abc'); // logs 'abc' + */ + emit(eventType: string) { + const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + if (subscriptions) { + for (let i = 0, l = subscriptions.length; i < l; i++) { + const subscription = subscriptions[i]; + + // The subscription may have been removed during this event loop. + if (subscription) { + this._currentSubscription = subscription; + subscription.listener.apply( + subscription.context, + Array.prototype.slice.call(arguments, 1) + ); + } + } + this._currentSubscription = null; + } + } + + /** + * Removes the given listener for event of specific type. + * + * @param {string} eventType - Name of the event to emit + * @param {function} listener - Function to invoke when the specified event is + * emitted + * + * @example + * emitter.removeListener('someEvent', function(message) { + * console.log(message); + * }); // removes the listener if already registered + * + */ + removeListener(eventType: string, listener) { + const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + if (subscriptions) { + for (let i = 0, l = subscriptions.length; i < l; i++) { + const subscription = subscriptions[i]; + + // 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(); + } + } + } + } +} + +module.exports = EventEmitter; diff --git a/tests-new/firebase/utils/emitter/EventSubscription.js b/tests-new/firebase/utils/emitter/EventSubscription.js new file mode 100644 index 00000000..670d6231 --- /dev/null +++ b/tests-new/firebase/utils/emitter/EventSubscription.js @@ -0,0 +1,42 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +'use strict'; + +import type EventSubscriptionVendor from './EventSubscriptionVendor'; + +/** + * EventSubscription represents a subscription to a particular event. It can + * remove its own subscription. + */ +class EventSubscription { + + eventType: string; + key: number; + subscriber: EventSubscriptionVendor; + + /** + * @param {EventSubscriptionVendor} subscriber the subscriber that controls + * this subscription. + */ + constructor(subscriber: EventSubscriptionVendor) { + this.subscriber = subscriber; + } + + /** + * Removes this subscription from the subscriber that controls it. + */ + remove() { + this.subscriber.removeSubscription(this); + } +} + +module.exports = EventSubscription; diff --git a/tests-new/firebase/utils/emitter/EventSubscriptionVendor.js b/tests-new/firebase/utils/emitter/EventSubscriptionVendor.js new file mode 100644 index 00000000..9ca0ec49 --- /dev/null +++ b/tests-new/firebase/utils/emitter/EventSubscriptionVendor.js @@ -0,0 +1,100 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +'use strict'; + +const invariant = require('fbjs/lib/invariant'); + +import type EventSubscription from './EventSubscription'; + +/** + * EventSubscriptionVendor stores a set of EventSubscriptions that are + * subscribed to a particular event type. + */ +class EventSubscriptionVendor { + + _subscriptionsForType: Object; + _currentSubscription: ?EventSubscription; + + constructor() { + this._subscriptionsForType = {}; + this._currentSubscription = null; + } + + /** + * Adds a subscription keyed by an event type. + * + * @param {string} eventType + * @param {EventSubscription} subscription + */ + addSubscription( + eventType: string, subscription: EventSubscription): EventSubscription { + invariant( + subscription.subscriber === this, + 'The subscriber of the subscription is incorrectly set.'); + if (!this._subscriptionsForType[eventType]) { + this._subscriptionsForType[eventType] = []; + } + const key = this._subscriptionsForType[eventType].length; + this._subscriptionsForType[eventType].push(subscription); + subscription.eventType = eventType; + subscription.key = key; + return subscription; + } + + /** + * Removes a bulk set of the subscriptions. + * + * @param {?string} eventType - Optional name of the event type whose + * registered supscriptions to remove, if null remove all subscriptions. + */ + removeAllSubscriptions(eventType: ?string) { + if (eventType === undefined) { + this._subscriptionsForType = {}; + } else { + delete this._subscriptionsForType[eventType]; + } + } + + /** + * Removes a specific subscription. Instead of calling this function, call + * `subscription.remove()` directly. + * + * @param {object} subscription + */ + removeSubscription(subscription: Object) { + const eventType = subscription.eventType; + const key = subscription.key; + + const subscriptionsForType = this._subscriptionsForType[eventType]; + if (subscriptionsForType) { + delete subscriptionsForType[key]; + } + } + + /** + * Returns the array of subscriptions that are currently registered for the + * given event type. + * + * Note: This array can be potentially sparse as subscriptions are deleted + * from it when they are removed. + * + * TODO: This returns a nullable array. wat? + * + * @param {string} eventType + * @returns {?array} + */ + getSubscriptionsForType(eventType: string): ?[EventSubscription] { + return this._subscriptionsForType[eventType]; + } +} + +module.exports = EventSubscriptionVendor; diff --git a/tests-new/firebase/utils/events.js b/tests-new/firebase/utils/events.js new file mode 100644 index 00000000..1d53798e --- /dev/null +++ b/tests-new/firebase/utils/events.js @@ -0,0 +1,73 @@ +/** + * @flow + */ +import { NativeEventEmitter, NativeModules } from 'react-native'; +import EventEmitter from './emitter/EventEmitter'; + +import type ModuleBase from './ModuleBase'; +import type { FirebaseModuleConfig, FirebaseModuleName } from '../types'; + +const NATIVE_EMITTERS: { [string]: NativeEventEmitter } = {}; +const NATIVE_SUBSCRIPTIONS: { [string]: boolean } = {}; + +export const SharedEventEmitter = new EventEmitter(); + +export const getAppEventName = ( + module: ModuleBase, + eventName: string +): string => `${module.app.name}-${eventName}`; + +const getNativeEmitter = ( + moduleName: FirebaseModuleName, + module: ModuleBase +): NativeEventEmitter => { + const name = `${module.app.name}-${moduleName}`; + const nativeModule = NativeModules[moduleName]; + if (!NATIVE_EMITTERS[name]) { + NATIVE_EMITTERS[name] = new NativeEventEmitter(nativeModule); + } + return NATIVE_EMITTERS[name]; +}; + +/** + * Subscribe to a native event for js side distribution by appName + * React Native events are hard set at compile - cant do dynamic event names + * so we use a single event send it to js and js then internally can prefix it + * and distribute dynamically. + * + * @param module + * @param eventName + * @private + */ +const subscribeToNativeModuleEvents = ( + moduleName: FirebaseModuleName, + module: ModuleBase, + eventName: string +): void => { + if (!NATIVE_SUBSCRIPTIONS[eventName]) { + const nativeEmitter = getNativeEmitter(moduleName, module); + nativeEmitter.addListener(eventName, event => { + if (event.appName) { + // native event has an appName property - auto prefix and internally emit + SharedEventEmitter.emit(`${event.appName}-${eventName}`, event); + } else { + // standard event - no need to prefix + SharedEventEmitter.emit(eventName, event); + } + }); + + NATIVE_SUBSCRIPTIONS[eventName] = true; + } +}; + +export const initialiseNativeModuleEventEmitter = ( + module: ModuleBase, + config: FirebaseModuleConfig +): void => { + const { events, moduleName } = config; + if (events && events.length) { + for (let i = 0, len = events.length; i < len; i++) { + subscribeToNativeModuleEvents(moduleName, module, events[i]); + } + } +}; diff --git a/tests-new/firebase/utils/index.js b/tests-new/firebase/utils/index.js new file mode 100644 index 00000000..bbe1031e --- /dev/null +++ b/tests-new/firebase/utils/index.js @@ -0,0 +1,426 @@ +// @flow +import { Platform } from 'react-native'; + +// todo cleanup unused utilities from legacy code + +// modeled after base64 web-safe chars, but ordered by ASCII +const PUSH_CHARS = + '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; +const AUTO_ID_CHARS = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +const { hasOwnProperty } = Object; + +// const DEFAULT_CHUNK_SIZE = 50; + +/** + * Deep get a value from an object. + * @website https://github.com/Salakar/deeps + * @param object + * @param path + * @param joiner + * @returns {*} + */ +export function deepGet( + object: Object, + path: string, + joiner?: string = '/' +): any { + const keys = path.split(joiner); + + let i = 0; + let tmp = object; + const len = keys.length; + + while (i < len) { + const key = keys[i++]; + 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 {*} + */ +export function deepExists( + object: Object, + path: string, + joiner?: string = '/' +): boolean { + const keys = path.split(joiner); + + let i = 0; + let tmp = object; + const len = keys.length; + + while (i < len) { + const key = keys[i++]; + if (!tmp || !hasOwnProperty.call(tmp, key)) return false; + tmp = tmp[key]; + } + + return tmp !== undefined; +} + +/** + * Deep Check if obj1 keys are contained in obj2 + * @param obj1 + * @param obj2 + * @returns {boolean} + */ +export function areObjectKeysContainedInOther( + obj1: Object, + obj2: Object +): boolean { + if (!isObject(obj1) || !isObject(obj2)) { + return false; + } + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + if (isArrayContainedInOther(keys1, keys2)) { + return keys1 + .filter(key => isObject(obj1[key])) + .reduce( + (acc, cur) => + acc && areObjectKeysContainedInOther(obj1[cur], obj2[cur]), + true + ); + } + return false; +} + +/** + * Check if arr1 is contained in arr2 + * @param arr1 + * @param arr2 + * @returns {boolean} + */ +export function isArrayContainedInOther( + arr1: Array<*>, + arr2: Array<*> +): boolean { + if (!Array.isArray(arr1) || !Array.isArray(arr2)) { + return false; + } + return arr1.reduce((acc, cur) => acc && arr2.includes(cur), true); +} + +/** + * Simple is object check. + * @param item + * @returns {boolean} + */ +export function isObject(item: mixed): boolean %checks { + return item + ? typeof item === 'object' && !Array.isArray(item) && item !== null + : false; +} + +/** + * Simple is function check + * @param item + * @returns {*|boolean} + */ +export function isFunction(item?: mixed): boolean %checks { + return item ? typeof item === 'function' : false; +} + +/** + * Simple is string check + * @param value + * @return {boolean} + */ +export function isString(value: mixed): boolean %checks { + return typeof value === 'string'; +} + +// platform checks +export const isIOS = Platform.OS === 'ios'; +export const isAndroid = Platform.OS === 'android'; + +/** + * + * @param string + * @returns {*} + */ +export function tryJSONParse(string: string | null): any { + try { + return string && JSON.parse(string); + } catch (jsonError) { + return string; + } +} + +/** + * + * @param data + * @returns {*} + */ +export function tryJSONStringify(data: mixed): string | null { + try { + return JSON.stringify(data); + } catch (jsonError) { + return null; + } +} + +/** + * No operation func + */ +export function noop(): void {} + +// /** +// * Delays chunks based on sizes per event loop. +// * @param collection +// * @param chunkSize +// * @param operation +// * @param callback +// * @private +// */ +// function _delayChunk(collection: Array<*>, +// chunkSize: number, +// operation: Function, +// callback: Function): void { +// 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 | Function, +// iterator: Function, +// cb?: Function): void { +// if (typeof chunkSize === 'function') { +// cb = iterator; +// iterator = chunkSize; +// chunkSize = DEFAULT_CHUNK_SIZE; +// } +// +// if (cb) { +// _delayChunk(array, chunkSize, (slice, start) => { +// for (let ii = 0, jj = slice.length; ii < jj; ii += 1) { +// iterator(slice[ii], start + ii); +// } +// }, cb); +// } +// } + +/** + * Returns a string typeof that's valid for Firebase usage + * @param value + * @return {*} + */ +export function typeOf(value: any): string { + if (value === null) return 'null'; + if (Array.isArray(value)) return 'array'; + return typeof value; +} + +// /** +// * Async map with optional chunk size limit +// * @param array +// * @param chunkSize +// * @param iterator +// * @param cb +// * @returns {*} +// */ +// export function map(array: Array<*>, +// chunkSize: number | Function, +// 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 && cb(result)); +// } + +// /** +// * +// * @param string +// * @return {string} +// */ +// export function capitalizeFirstLetter(string: string) { +// return `${string.charAt(0).toUpperCase()}${string.slice(1)}`; +// } + +// 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; +} + +/** + * Converts a code and message from a native event to a JS Error + * @param code + * @param message + * @param additionalProps + * @returns {Error} + */ +export function nativeToJSError( + code: string, + message: string, + additionalProps?: Object = {} +) { + const error: Object = new Error(message); + error.code = code; + Object.assign(error, additionalProps); + // exclude this function from the stack + const _stackArray = error.stack.split('\n'); + error.stack = _stackArray.splice(1, _stackArray.length).join('\n'); + return error; +} + +/** + * + * @param object + * @return {string} + */ +export function objectToUniqueId(object: Object): string { + if (!isObject(object) || object === null) return JSON.stringify(object); + + const keys = Object.keys(object).sort(); + + let key = '{'; + for (let i = 0; i < keys.length; i++) { + if (i !== 0) key += ','; + key += JSON.stringify(keys[i]); + key += ':'; + key += objectToUniqueId(object[keys[i]]); + } + + key += '}'; + return key; +} + +/** + * Return the existing promise if no callback provided or + * exec the promise and callback if optionalCallback is valid. + * + * @param promise + * @param optionalCallback + * @return {Promise} + */ +export function promiseOrCallback( + promise: Promise<*>, + optionalCallback?: Function +): Promise<*> { + if (!isFunction(optionalCallback)) return promise; + + return promise + .then(result => { + // some of firebase internal tests & methods only check/return one arg + // see https://github.com/firebase/firebase-js-sdk/blob/master/src/utils/promise.ts#L62 + if (optionalCallback && optionalCallback.length === 1) { + optionalCallback(null); + } else if (optionalCallback) { + optionalCallback(null, result); + } + + return Promise.resolve(result); + }) + .catch(error => { + if (optionalCallback) optionalCallback(error); + return Promise.reject(error); + }); +} + +/** + * Generate a firestore auto id for use with collection/document .add() + * @return {string} + */ +export function firestoreAutoId(): 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; +} diff --git a/tests-new/firebase/utils/internals.js b/tests-new/firebase/utils/internals.js new file mode 100644 index 00000000..fc656c04 --- /dev/null +++ b/tests-new/firebase/utils/internals.js @@ -0,0 +1,245 @@ +/** + * @flow + */ +import { Platform } from 'react-native'; + +const NAMESPACE_PODS = { + admob: 'Firebase/AdMob', + analytics: 'Firebase/Analytics', + auth: 'Firebase/Auth', + config: 'Firebase/RemoteConfig', + crash: 'Firebase/Crash', + database: 'Firebase/Database', + links: 'Firebase/DynamicLinks', + messaging: 'Firebase/Messaging', + perf: 'Firebase/Performance', + storage: 'Firebase/Storage', +}; + +const GRADLE_DEPS = { + admob: 'ads', +}; + +const PLAY_SERVICES_CODES = { + // $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380 + 1: { + code: 'SERVICE_MISSING', + message: 'Google Play services is missing on this device.', + }, + // $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380 + 2: { + code: 'SERVICE_VERSION_UPDATE_REQUIRED', + message: + 'The installed version of Google Play services on this device is out of date.', + }, + // $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380 + 3: { + code: 'SERVICE_DISABLED', + message: + 'The installed version of Google Play services has been disabled on this device.', + }, + // $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380 + 9: { + code: 'SERVICE_INVALID', + message: + 'The version of the Google Play services installed on this device is not authentic.', + }, + // $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380 + 18: { + code: 'SERVICE_UPDATING', + message: 'Google Play services is currently being updated on this device.', + }, + // $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380 + 19: { + code: 'SERVICE_MISSING_PERMISSION', + message: + "Google Play service doesn't have one or more required permissions.", + }, +}; + +export default { + // default options + OPTIONS: { + logLevel: 'warn', + errorOnMissingPlayServices: true, + promptOnMissingPlayServices: true, + }, + + FLAGS: { + checkedPlayServices: false, + }, + + STRINGS: { + WARN_INITIALIZE_DEPRECATION: + "Deprecation: Calling 'initializeApp()' for apps that are already initialised natively " + + "is unnecessary, use 'firebase.app()' instead to access the already initialized default app instance.", + + /** + * @return {string} + */ + get ERROR_MISSING_CORE() { + if (Platform.OS === 'ios') { + return ( + 'RNFirebase core module was not found natively on iOS, ensure you have ' + + 'correctly included the RNFirebase pod in your projects `Podfile` and have run `pod install`.' + + '\r\n\r\n See http://invertase.link/ios for the ios setup guide.' + ); + } + + return ( + 'RNFirebase core module was not found natively on Android, ensure you have ' + + 'correctly added the RNFirebase and Firebase gradle dependencies to your `android/app/build.gradle` file.' + + '\r\n\r\n See http://invertase.link/android for the android setup guide.' + ); + }, + + ERROR_INIT_OBJECT: + 'Firebase.initializeApp(options <-- requires a valid configuration object.', + ERROR_INIT_STRING_NAME: + 'Firebase.initializeApp(options, name <-- requires a valid string value.', + + /** + * @return {string} + */ + ERROR_INIT_SERVICE_URL_UNSUPPORTED(namespace: string) { + return `${namespace} does not support URL as a param, please pass in an app.`; + }, + + /** + * @return {string} + */ + ERROR_MISSING_CB(method: string) { + return `Missing required callback for method ${method}().`; + }, + + /** + * @return {string} + */ + ERROR_MISSING_ARG(type: string, method: string) { + return `Missing required argument of type '${type}' for method '${method}()'.`; + }, + + /** + * @return {string} + */ + ERROR_MISSING_ARG_NAMED(name: string, type: string, method: string) { + return `Missing required argument '${name}' of type '${type}' for method '${method}()'.`; + }, + + /** + * @return {string} + */ + ERROR_ARG_INVALID_VALUE(name: string, expected: string, got: string) { + return `Invalid value for argument '${name}' expected value '${expected}' but got '${got}'.`; + }, + + /** + * @return {string} + */ + ERROR_PROTECTED_PROP(name: string) { + return `Property '${name}' is protected and can not be overridden by extendApp.`; + }, + + /** + * @return {string} + * @param namespace + * @param nativeModule + */ + ERROR_MISSING_MODULE(namespace: string, nativeModule: string) { + const snippet = `firebase.${namespace}()`; + if (Platform.OS === 'ios') { + return ( + `You attempted to use a firebase module that's not installed natively on your iOS project by calling ${snippet}.` + + '\r\n\r\nEnsure you have the required Firebase iOS SDK pod for this module included in your Podfile, in this instance ' + + `confirm you've added "pod '${ + NAMESPACE_PODS[namespace] + }'" to your Podfile` + + '\r\n\r\nSee http://invertase.link/ios for full setup instructions.' + ); + } + + const fbSDKDep = `'com.google.firebase:firebase-${GRADLE_DEPS[ + namespace + ] || namespace}'`; + const rnFirebasePackage = `'io.invertase.firebase.${namespace}.${nativeModule}Package'`; + const newInstance = `'new ${nativeModule}Package()'`; + return ( + `You attempted to use a firebase module that's not installed on your Android project by calling ${snippet}.` + + `\r\n\r\nEnsure you have:\r\n\r\n1) Installed the required Firebase Android SDK dependency ${fbSDKDep} in your 'android/app/build.gradle' ` + + `file.\r\n\r\n2) Imported the ${rnFirebasePackage} module in your 'MainApplication.java' file.\r\n\r\n3) Added the ` + + `${newInstance} line inside of the RN 'getPackages()' method list.` + + '\r\n\r\nSee http://invertase.link/android for full setup instructions.' + ); + }, + + /** + * @return {string} + */ + ERROR_APP_NOT_INIT(appName: string) { + return `The [${appName}] firebase app has not been initialized!`; + }, + + /** + * @param optName + * @return {string} + * @constructor + */ + ERROR_MISSING_OPT(optName: string) { + return `Failed to initialize app. FirebaseOptions missing or invalid '${optName}' property.`; + }, + + /** + * @return {string} + */ + ERROR_NOT_APP(namespace: string) { + return `Invalid App instance passed to firebase.${namespace}(app <--).`; + }, + + /** + * @return {string} + */ + ERROR_UNSUPPORTED_CLASS_METHOD(className: string, method: string) { + return `${className}.${method}() is unsupported by the native Firebase SDKs.`; + }, + + /** + * @return {string} + */ + ERROR_UNSUPPORTED_CLASS_PROPERTY(className: string, property: string) { + return `${className}.${property} is unsupported by the native Firebase SDKs.`; + }, + + /** + * @return {string} + */ + ERROR_UNSUPPORTED_MODULE_METHOD(namespace: string, method: string) { + return `firebase.${namespace}().${method}() is unsupported by the native Firebase SDKs.`; + }, + + /** + * @return {string} + */ + ERROR_PLAY_SERVICES(statusCode: number) { + const knownError = PLAY_SERVICES_CODES[statusCode]; + let start = + 'Google Play Services is required to run firebase services on android but a valid installation was not found on this device.'; + + if (statusCode === 2) { + start = + 'Google Play Services is out of date and may cause some firebase services like authentication to hang when used. It is recommended that you update it.'; + } + + // eslint-disable-next-line prefer-template + return ( + `${`${start}\r\n\r\n-------------------------\r\n`}${ + knownError + ? `${knownError.code}: ${knownError.message} (code ${statusCode})` + : `A specific play store availability reason reason was not available (unknown code: ${statusCode})` + }\r\n-------------------------` + + `\r\n\r\n` + + `For more information on how to resolve this issue, configure Play Services checks or for guides on how to validate Play Services on your users devices see the link below:` + + `\r\n\r\nhttp://invertase.link/play-services` + ); + }, + }, +}; diff --git a/tests-new/firebase/utils/log.js b/tests-new/firebase/utils/log.js new file mode 100644 index 00000000..c94da3dc --- /dev/null +++ b/tests-new/firebase/utils/log.js @@ -0,0 +1,47 @@ +/* + * @flow + */ + +import INTERNALS from './internals'; +import type ModuleBase from './ModuleBase'; + +const NATIVE_LOGGERS: { [string]: Object } = {}; + +const getModuleKey = (module: ModuleBase): string => + `${module.app.name}:${module.namespace}`; + +export const getLogger = (module: ModuleBase) => { + const key = getModuleKey(module); + return NATIVE_LOGGERS[key]; +}; + +export const LEVELS = { + debug: 0, + info: 1, + warn: 2, + error: 3, +}; + +export const initialiseLogger = (module: ModuleBase, logNamespace: string) => { + const key = getModuleKey(module); + if (!NATIVE_LOGGERS[key]) { + const prefix = `🔥 ${logNamespace.toUpperCase()}`; + NATIVE_LOGGERS[key] = { + debug(...args) { + if (__DEV__ && LEVELS.debug >= LEVELS[INTERNALS.OPTIONS.logLevel]) + console.log(...[prefix, ...args]); + }, + info(...args) { + if (__DEV__ && LEVELS.info >= LEVELS[INTERNALS.OPTIONS.logLevel]) + console.log(...[prefix, ...args]); + }, + warn(...args) { + if (__DEV__ && LEVELS.warn >= LEVELS[INTERNALS.OPTIONS.logLevel]) + console.warn(...args); + }, + error(...args) { + console.error(...args); + }, + }; + } +}; diff --git a/tests-new/firebase/utils/native.js b/tests-new/firebase/utils/native.js new file mode 100644 index 00000000..1b8ddd66 --- /dev/null +++ b/tests-new/firebase/utils/native.js @@ -0,0 +1,74 @@ +/* + * @flow + */ +import { NativeModules } from 'react-native'; +import { initialiseNativeModuleEventEmitter } from './events'; +import INTERNALS from './internals'; + +import type ModuleBase from './ModuleBase'; +import type { FirebaseModuleConfig } from '../types'; + +const NATIVE_MODULES: { [string]: Object } = {}; + +/** + * Prepends all arguments in prependArgs to all native method calls + * @param NativeModule + * @param argToPrepend + */ +const nativeWithArgs = ( + NativeModule: Object, + argToPrepend: Array +): Object => { + const native = {}; + const methods = Object.keys(NativeModule); + + for (let i = 0, len = methods.length; i < len; i++) { + const method = methods[i]; + native[method] = (...args) => + NativeModule[method](...[...argToPrepend, ...args]); + } + + return native; +}; + +const nativeModuleKey = (module: ModuleBase): string => + `${module._serviceUrl || module.app.name}:${module.namespace}`; + +export const getNativeModule = (module: ModuleBase): Object => + NATIVE_MODULES[nativeModuleKey(module)]; + +export const initialiseNativeModule = ( + module: ModuleBase, + config: FirebaseModuleConfig, + serviceUrl: ?string +): Object => { + const { moduleName, multiApp, hasShards, namespace } = config; + const nativeModule = NativeModules[moduleName]; + const key = nativeModuleKey(module); + + if (!nativeModule && namespace !== 'utils') { + throw new Error( + INTERNALS.STRINGS.ERROR_MISSING_MODULE(namespace, moduleName) + ); + } + + // used by the modules that extend ModuleBase + // to access their native module counterpart + const argToPrepend = []; + if (multiApp) { + argToPrepend.push(module.app.name); + } + if (hasShards) { + argToPrepend.push(serviceUrl); + } + + if (argToPrepend.length) { + NATIVE_MODULES[key] = nativeWithArgs(nativeModule, argToPrepend); + } else { + NATIVE_MODULES[key] = nativeModule; + } + + initialiseNativeModuleEventEmitter(module, config); + + return NATIVE_MODULES[key]; +}; diff --git a/tests-new/package.json b/tests-new/package.json index e96d0b19..ae7a911a 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -1,18 +1,35 @@ { - "name": "example", + "name": "rnfirebase-tests", "version": "7.2.0", "private": true, "scripts": { - "start": "react-native start" + "start": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android", + "android:dev": "react-native run-android", + "android:prod": "react-native run-android --configuration=release", + "ios:dev": "react-native run-ios", + "ios:prod": "react-native run-ios --configuration=release", + "ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .." }, "dependencies": { - "react": "16.0.0-beta.5", - "react-native": "0.49.3" - }, - "devDependencies": { + "react": "^16.2.0", + "react-native": "^0.52.3", "detox": "^7.2.0", "mocha": "^4.0.1" }, + "devDependencies": { + "babel-cli": "^6.24.0", + "babel-eslint": "^7.1.1", + "babel-jest": "19.0.0", + "babel-plugin-flow-react-proptypes": "^0.21.0", + "babel-plugin-istanbul": "^4.1.5", + "babel-preset-react-native": "1.9.1", + "eslint": "^3.16.1", + "eslint-config-airbnb": "^14.1.0", + "eslint-plugin-flowtype": "^2.46.1", + "eslint-plugin-import": "^2.9.0", + "eslint-plugin-jsx-a11y": "^4.0.0", + "eslint-plugin-react": "^6.10.0" + }, "detox": { "test-runner": "mocha", "specs": "e2e", diff --git a/tests/android/app/build.gradle b/tests/android/app/build.gradle index 38943153..fae14028 100644 --- a/tests/android/app/build.gradle +++ b/tests/android/app/build.gradle @@ -4,76 +4,6 @@ apply plugin: 'io.fabric' import com.android.build.OutputFile -/** - * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets - * and bundleReleaseJsAndAssets). - * These basically call `react-native bundle` with the correct arguments during the Android build - * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the - * bundle directly from the development server. Below you can see all the possible configurations - * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "../../node_modules/react-native/react.gradle"` line. - * - * project.ext.react = [ - * // the name of the generated asset file containing your JS bundle - * bundleAssetName: "index.android.bundle", - * - * // the entry file for bundle generation - * entryFile: "index.android.js", - * - * // whether to bundle JS and assets in debug mode - * bundleInDebug: false, - * - * // whether to bundle JS and assets in release mode - * bundleInRelease: true, - * - * // whether to bundle JS and assets in another build variant (if configured). - * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants - * // The configuration property can be in the following formats - * // 'bundleIn${productFlavor}${buildType}' - * // 'bundleIn${buildType}' - * // bundleInFreeDebug: true, - * // bundleInPaidRelease: true, - * // bundleInBeta: true, - * - * // whether to disable dev mode in custom build variants (by default only disabled in release) - * // for example: to disable dev mode in the staging build type (if configured) - * devDisabledInStaging: true, - * // The configuration property can be in the following formats - * // 'devDisabledIn${productFlavor}${buildType}' - * // 'devDisabledIn${buildType}' - * - * // the root of your project, i.e. where "package.json" lives - * root: "../../", - * - * // where to put the JS bundle asset in debug mode - * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", - * - * // where to put the JS bundle asset in release mode - * jsBundleDirRelease: "$buildDir/intermediates/assets/release", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in debug mode - * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in release mode - * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", - * - * // by default the gradle tasks are skipped if none of the JS files or assets change; this means - * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to - * // date; if you have any other folders that you want to ignore for performance reasons (gradle - * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ - * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"], - * - * // override which node gets called and with what additional arguments - * nodeExecutableAndArgs: ["node"], - * - * // supply additional arguments to the packager - * extraPackagerArgs: [] - * ] - */ - project.ext.react = [ entryFile: "index.js" ] From 9811a7cbefe95e07fcc7858be449696e7d394503 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 23 Mar 2018 19:38:17 +0000 Subject: [PATCH 04/40] [android] update gradle tool versions --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 0eac7760..ab92cb57 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,8 +7,8 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' - classpath 'io.fabric.tools:gradle:1.24.4' + classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'io.fabric.tools:gradle:1.25.1' } } From b386b2e5216e87e621544a7594772c46b7c26883 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 23 Mar 2018 22:17:51 +0000 Subject: [PATCH 05/40] [android] gradle version updates --- android/build.gradle | 15 +++++++++++---- .../io/invertase/firebase/RNFirebasePackage.java | 1 - .../storage/RNFirebaseStoragePackage.java | 1 - 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index ab92cb57..26685402 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,6 +2,10 @@ buildscript { ext.firebaseVersion = '11.8.0' repositories { jcenter() + mavenLocal() + maven { + url "https://maven.google.com" + } maven { url 'https://maven.fabric.io/public' } @@ -19,10 +23,10 @@ android { buildToolsVersion "27.0.2" defaultConfig { minSdkVersion 16 - targetSdkVersion 26 + targetSdkVersion 27 versionCode 1 versionName "1.0" - // multiDexEnabled true + multiDexEnabled true } buildTypes { release { @@ -44,6 +48,7 @@ allprojects { } } + rootProject.gradle.buildFinished { buildResult -> if (buildResult.getFailure() != null) { try { @@ -74,15 +79,17 @@ rootProject.gradle.buildFinished { buildResult -> logger.log(LogLevel.ERROR, "| |") logger.log(LogLevel.ERROR, " ----------------------------------------------------------- ") } - } catch (Exception exception) {} + } catch (Exception exception) { + } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') + //noinspection GradleDynamicVersion compile "com.facebook.react:react-native:+" // From node_modules - compile "com.android.support:support-v4:27.0.2" + compile "com.android.support:support-v4:27.1.0" compile 'me.leolin:ShortcutBadger:1.1.21@aar' compile "com.google.android.gms:play-services-base:$firebaseVersion" compile "com.google.firebase:firebase-core:$firebaseVersion" diff --git a/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java b/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java index 8bf7fcbd..4c4ea50c 100644 --- a/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java +++ b/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java @@ -3,7 +3,6 @@ package io.invertase.firebase; import android.content.Context; import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.UIManagerModule; diff --git a/android/src/main/java/io/invertase/firebase/storage/RNFirebaseStoragePackage.java b/android/src/main/java/io/invertase/firebase/storage/RNFirebaseStoragePackage.java index a14cc738..56241059 100644 --- a/android/src/main/java/io/invertase/firebase/storage/RNFirebaseStoragePackage.java +++ b/android/src/main/java/io/invertase/firebase/storage/RNFirebaseStoragePackage.java @@ -3,7 +3,6 @@ package io.invertase.firebase.storage; import android.support.annotation.RequiresPermission; import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.UIManagerModule; From ed769fe8777fbaf4f074c661a6501b0419e2ccd1 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 23 Mar 2018 22:18:19 +0000 Subject: [PATCH 06/40] [tests] wip test infra --- tests-new/README.md | 22 + tests-new/android/.editorconfig | 10 + tests-new/android/app/build.gradle | 159 +- tests-new/android/app/google-services.json | 95 +- .../com/{example => testing}/DetoxTest.java | 2 +- .../android/app/src/main/AndroidManifest.xml | 80 +- .../main/java/com/example/MainActivity.java | 15 - .../java/com/example/MainApplication.java | 40 - .../main/java/com/testing/MainActivity.java | 80 + .../java/com/testing/MainApplication.java | 73 + .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 5271 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 3055 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 7309 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 11703 bytes .../main/res/drawable-xxxhdpi/ic_launcher.png | Bin 0 -> 16380 bytes .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3418 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2206 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4842 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7718 -> 0 bytes .../app/src/main/res/values/strings.xml | 2 +- tests-new/android/build.gradle | 73 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 2 +- tests-new/android/settings.gradle | 2 +- tests-new/app.js | 57 +- tests-new/e2e/example.spec.js | 42 +- tests-new/e2e/mocha.opts | 4 +- tests-new/package-lock.json | 8903 +++++++++++++++++ tests-new/package.json | 7 +- 29 files changed, 9418 insertions(+), 250 deletions(-) create mode 100644 tests-new/android/.editorconfig rename tests-new/android/app/src/androidTest/java/com/{example => testing}/DetoxTest.java (96%) delete mode 100755 tests-new/android/app/src/main/java/com/example/MainActivity.java delete mode 100755 tests-new/android/app/src/main/java/com/example/MainApplication.java create mode 100755 tests-new/android/app/src/main/java/com/testing/MainActivity.java create mode 100755 tests-new/android/app/src/main/java/com/testing/MainApplication.java create mode 100755 tests-new/android/app/src/main/res/drawable-hdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/drawable-mdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100755 tests-new/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png delete mode 100755 tests-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100755 tests-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100755 tests-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100755 tests-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png mode change 100755 => 100644 tests-new/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 tests-new/package-lock.json diff --git a/tests-new/README.md b/tests-new/README.md index 08c06e27..b7421b50 100755 --- a/tests-new/README.md +++ b/tests-new/README.md @@ -59,3 +59,25 @@ detox test --configuration ios.sim.debug ``` This action will open a new simulator and run the tests on it. + +### TODO + +Gradle issues... https://stackoverflow.com/questions/46917365/error-could-not-initialize-class-com-android-sdklib-repository-androidsdkhandle?rq=1 + +mac: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"` +windows `"C://Program Files/Java/jdk_1.x_"` + +android sdk root `export ANDROID_SDK_ROOT="/Users/mike/Library/Android/sdk"` + +Add platform-tools to your path + +echo 'export ANDROID_HOME=/Users/$USER/Library/Android/sdk' >> ~/.bash_profile +echo 'export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools' >> ~/.bash_profile + +Name: TestingAVD + +CPU/ABI: null (null) + +Path: /Users/mike/.android/avd/Actually_THIS_one.avd + +Error: Failed to parse properties from /Users/mike/.android/avd/Actually_THIS_one.avd/config.ini diff --git a/tests-new/android/.editorconfig b/tests-new/android/.editorconfig new file mode 100644 index 00000000..0f099897 --- /dev/null +++ b/tests-new/android/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/tests-new/android/app/build.gradle b/tests-new/android/app/build.gradle index 3a257572..2f7f3bab 100755 --- a/tests-new/android/app/build.gradle +++ b/tests-new/android/app/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'io.fabric' import com.android.build.OutputFile project.ext.react = [ - entryFile: "index.js" + entryFile: "index.js" ] apply from: "../../node_modules/react-native/react.gradle" @@ -14,89 +14,108 @@ def enableSeparateBuildPerCPUArchitecture = false def enableProguardInReleaseBuilds = false android { - compileSdkVersion 27 - buildToolsVersion '27.0.2' + compileSdkVersion 27 + buildToolsVersion '27.0.2' - defaultConfig { - applicationId "com.detox.rn.example" - minSdkVersion 18 - targetSdkVersion 26 - versionCode 1 - versionName "1.0" - ndk { - abiFilters "armeabi-v7a", "x86" - } - - testBuildType System.getProperty('testBuildType', 'debug') - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - missingDimensionStrategy "minReactNative", "minReactNative46" - multiDexEnabled true - } - splits { - abi { - reset() - enable enableSeparateBuildPerCPUArchitecture - universalApk false // If true, also generate a universal APK - include "armeabi-v7a", "x86" - } - } - signingConfigs { - release { - storeFile file("keystore.jks") - storePassword "12345678" - keyAlias "key0" - keyPassword "12345678" - } - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - signingConfig signingConfigs.release - } - } - // applicationVariants are e.g. debug, release - applicationVariants.all { variant -> - variant.outputs.each { output -> - // For each separate APK per architecture, set a unique version code as described here: - // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits - def versionCodes = ["armeabi-v7a": 1, "x86": 2] - def abi = output.getFilter(OutputFile.ABI) - if (abi != null) { // null for the universal-debug, universal-release variants - output.versionCodeOverride = - versionCodes.get(abi) * 1048576 + defaultConfig.versionCode - } - } + defaultConfig { + applicationId "com.testing" + minSdkVersion 18 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" } - packagingOptions { - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/NOTICE.txt' + testBuildType System.getProperty('testBuildType', 'debug') + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + missingDimensionStrategy "minReactNative", "minReactNative46" + multiDexEnabled true + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86" } + } + signingConfigs { + release { + storeFile file("keystore.jks") + storePassword "12345678" + keyAlias "key0" + keyPassword "12345678" + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + signingConfig signingConfigs.release + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a": 1, "x86": 2] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } + + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/NOTICE' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE.txt' + } } -project.ext.firebaseVersion = '11.8.0' +project.ext.firebaseVersion = '12.0.0' dependencies { - implementation "com.android.support:appcompat-v7:27.0.2" - implementation "com.facebook.react:react-native:+" // From node_modules + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" + implementation(project(':react-native-firebase')) { + transitive = false + } - compile project(':react-native-firebase') - - androidTestImplementation(project(path: ":detox")) - androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test:rules:1.0.1' + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.google.android.gms:play-services-base:$firebaseVersion" +// compile "com.google.firebase:firebase-ads:$firebaseVersion" + compile "com.google.firebase:firebase-auth:$firebaseVersion" + compile "com.google.firebase:firebase-config:$firebaseVersion" + compile "com.google.firebase:firebase-core:$firebaseVersion" + compile "com.google.firebase:firebase-crash:$firebaseVersion" + compile "com.google.firebase:firebase-database:$firebaseVersion" + compile "com.google.firebase:firebase-messaging:$firebaseVersion" + compile "com.google.firebase:firebase-perf:$firebaseVersion" + compile "com.google.firebase:firebase-storage:$firebaseVersion" + compile "com.google.firebase:firebase-firestore:$firebaseVersion" + compile "com.google.firebase:firebase-invites:$firebaseVersion" + compile('com.crashlytics.sdk.android:crashlytics:2.9.1@aar') { + transitive = true + } + compile "com.android.support:appcompat-v7:27.1.0" + implementation fileTree(dir: "libs", include: ["*.jar"]) + androidTestImplementation(project(path: ":detox")) + androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test:rules:1.0.1' } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { - from configurations.compile - into 'libs' + from configurations.compile + into 'libs' } apply plugin: 'com.google.gms.google-services' diff --git a/tests-new/android/app/google-services.json b/tests-new/android/app/google-services.json index 30a94c5d..5181bffd 100644 --- a/tests-new/android/app/google-services.json +++ b/tests-new/android/app/google-services.json @@ -1,27 +1,38 @@ { "project_info": { - "project_number": "305229645282", - "firebase_url": "https://rnfirebase-b9ad4.firebaseio.com", - "project_id": "rnfirebase-b9ad4", - "storage_bucket": "rnfirebase-b9ad4.appspot.com" + "project_number": "17067372085", + "firebase_url": "https://rnfirebase-5579a.firebaseio.com", + "project_id": "rnfirebase", + "storage_bucket": "rnfirebase.appspot.com" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:305229645282:android:efe37851d57e1d05", + "mobilesdk_app_id": "1:17067372085:android:efe37851d57e1d05", "android_client_info": { "package_name": "com.reactnativefirebasedemo" } }, "oauth_client": [ { - "client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com", + "client_id": "17067372085-ltes2e70ehnlp4s5bl7uljuqvjul2l9s.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.reactnativefirebasedemo", + "certificate_hash": "34d770365973b7580e04dab50692506aff7bd32f" + } + }, + { + "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { - "current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM" + "current_key": "AIzaSyB-z0ytgXRRiClvslJl0tp-KbhDub9o6AM" + }, + { + "current_key": "AIzaSyAJw8mR1fPcEYC9ouZbkCStJufcCQrhmjQ" } ], "services": { @@ -29,8 +40,74 @@ "status": 1 }, "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "17067372085-siujfe334vool17t2mtrmjrsgl81nhd9.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.invertase.RNFirebaseTests" + } + } + ] + }, + "ads_service": { + "status": 2 + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:17067372085:android:af36d4d29a83e04c", + "android_client_info": { + "package_name": "com.testing" + } + }, + "oauth_client": [ + { + "client_id": "17067372085-066pi4ln798odtdrifoktb3mea9dtipt.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.testing", + "certificate_hash": "34d770365973b7580e04dab50692506aff7bd32f" + } + }, + { + "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyB-z0ytgXRRiClvslJl0tp-KbhDub9o6AM" + }, + { + "current_key": "AIzaSyAJw8mR1fPcEYC9ouZbkCStJufcCQrhmjQ" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "17067372085-siujfe334vool17t2mtrmjrsgl81nhd9.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.invertase.RNFirebaseTests" + } + } + ] }, "ads_service": { "status": 2 diff --git a/tests-new/android/app/src/androidTest/java/com/example/DetoxTest.java b/tests-new/android/app/src/androidTest/java/com/testing/DetoxTest.java similarity index 96% rename from tests-new/android/app/src/androidTest/java/com/example/DetoxTest.java rename to tests-new/android/app/src/androidTest/java/com/testing/DetoxTest.java index e63fdfd4..74c23acf 100755 --- a/tests-new/android/app/src/androidTest/java/com/example/DetoxTest.java +++ b/tests-new/android/app/src/androidTest/java/com/testing/DetoxTest.java @@ -1,4 +1,4 @@ -package com.example; +package com.testing; import android.support.test.filters.LargeTest; import android.support.test.rule.ActivityTestRule; diff --git a/tests-new/android/app/src/main/AndroidManifest.xml b/tests-new/android/app/src/main/AndroidManifest.xml index 677f0759..32144f02 100755 --- a/tests-new/android/app/src/main/AndroidManifest.xml +++ b/tests-new/android/app/src/main/AndroidManifest.xml @@ -1,31 +1,71 @@ + package="com.testing"> - + + + + + + + - - - - - + android:launchMode="singleTask" + android:theme="@style/AppTheme"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + diff --git a/tests-new/android/app/src/main/java/com/example/MainActivity.java b/tests-new/android/app/src/main/java/com/example/MainActivity.java deleted file mode 100755 index e84b7255..00000000 --- a/tests-new/android/app/src/main/java/com/example/MainActivity.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.example; - -import com.facebook.react.ReactActivity; - -public class MainActivity extends ReactActivity { - - /** - * Returns the name of the main component registered from JavaScript. - * This is used to schedule rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "example"; - } -} diff --git a/tests-new/android/app/src/main/java/com/example/MainApplication.java b/tests-new/android/app/src/main/java/com/example/MainApplication.java deleted file mode 100755 index b404a437..00000000 --- a/tests-new/android/app/src/main/java/com/example/MainApplication.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.example; - -import android.app.Application; - -import com.facebook.react.ReactApplication; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.shell.MainReactPackage; -import com.facebook.soloader.SoLoader; - -import java.util.Arrays; -import java.util.List; - -public class MainApplication extends Application implements ReactApplication { - private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - return Arrays.asList( - new MainReactPackage() - ); - } - }; - - @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; - } - - @Override - public void onCreate() { - super.onCreate(); - SoLoader.init(this, /* native exopackage */ false); - } - -} diff --git a/tests-new/android/app/src/main/java/com/testing/MainActivity.java b/tests-new/android/app/src/main/java/com/testing/MainActivity.java new file mode 100755 index 00000000..50575705 --- /dev/null +++ b/tests-new/android/app/src/main/java/com/testing/MainActivity.java @@ -0,0 +1,80 @@ +package com.testing; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + public static final int PERMISSION_REQ_CODE = 1234; + public static final int OVERLAY_PERMISSION_REQ_CODE = 1235; + + String[] perms = { + "android.permission.READ_EXTERNAL_STORAGE", + "android.permission.WRITE_EXTERNAL_STORAGE" + }; + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "testing"; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// checkWindowPerms(); + } + + public void checkWindowPerms() { + // Checking if device version > 22 and we need to use new permission model + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { + // Checking if we can draw window overlay + if (!Settings.canDrawOverlays(this)) { + // Requesting permission for window overlay(needed for all react-native apps) + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE); + } + for (String perm : perms) { + // Checking each permission and if denied then requesting permissions + if (checkSelfPermission(perm) == PackageManager.PERMISSION_DENIED) { + requestPermissions(perms, PERMISSION_REQ_CODE); + break; + } + } + } + } + + // Window overlay permission intent result + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { + checkWindowPerms(); + } + } + + // Permission results + @Override + public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) { + switch (permsRequestCode) { + case PERMISSION_REQ_CODE: + // example how to get result of permissions requests (there can be more then one permission dialog) + // boolean readAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED; + // boolean writeAccepted = grantResults[1]==PackageManager.PERMISSION_GRANTED; + // checking permissions to prevent situation when user denied some permission + checkWindowPerms(); + break; + + } + } +} diff --git a/tests-new/android/app/src/main/java/com/testing/MainApplication.java b/tests-new/android/app/src/main/java/com/testing/MainApplication.java new file mode 100755 index 00000000..0039f9d0 --- /dev/null +++ b/tests-new/android/app/src/main/java/com/testing/MainApplication.java @@ -0,0 +1,73 @@ +package com.testing; + +import android.app.Application; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +import io.invertase.firebase.RNFirebasePackage; +//import io.invertase.firebase.admob.RNFirebaseAdMobPackage; +import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage; +import io.invertase.firebase.auth.RNFirebaseAuthPackage; +import io.invertase.firebase.config.RNFirebaseRemoteConfigPackage; +import io.invertase.firebase.crash.RNFirebaseCrashPackage; +import io.invertase.firebase.database.RNFirebaseDatabasePackage; +import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage; +import io.invertase.firebase.firestore.RNFirebaseFirestorePackage; +import io.invertase.firebase.instanceid.RNFirebaseInstanceIdPackage; +import io.invertase.firebase.invites.RNFirebaseInvitesPackage; +import io.invertase.firebase.links.RNFirebaseLinksPackage; +import io.invertase.firebase.messaging.RNFirebaseMessagingPackage; +import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage; +import io.invertase.firebase.perf.RNFirebasePerformancePackage; +import io.invertase.firebase.storage.RNFirebaseStoragePackage; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new RNFirebasePackage(), +// new RNFirebaseAdMobPackage(), + new RNFirebaseAnalyticsPackage(), + new RNFirebaseAuthPackage(), + new RNFirebaseRemoteConfigPackage(), + new RNFirebaseCrashPackage(), + new RNFirebaseCrashlyticsPackage(), + new RNFirebaseDatabasePackage(), + new RNFirebaseFirestorePackage(), + new RNFirebaseInstanceIdPackage(), + new RNFirebaseInvitesPackage(), + new RNFirebaseLinksPackage(), + new RNFirebaseMessagingPackage(), + new RNFirebaseNotificationsPackage(), + new RNFirebasePerformancePackage(), + new RNFirebaseStoragePackage() + ); + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } + +} diff --git a/tests-new/android/app/src/main/res/drawable-hdpi/ic_launcher.png b/tests-new/android/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..7164396bb30967ba6c18de6d7c96de542b3fee69 GIT binary patch literal 5271 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 z:uZy>DjHUWFucux<2zojHJ<0Ac5z4Nwob;qT7+@hmalD`d}zOK}*1nbthHP;YI zDR?1gaT!!v2}@iJ>Gf+j?8m5%et-(C-`D$`1C_V#Mu38BryG!6r#6qE1&gzkWuBV( zhAw+{w-*K=_G1&C17+dQE!UN|x;5ke!%8CNCuBhQaE3fgCfbv7noy~XzwMxm`2KEM7dJL-7lvfa+#&V(SfvV3dQ z=lHe>_7{3zPiZU+AIXWW=Y3{GWd0rVPMXybapiaq@pGoGmp~`&d^+YhCoWSe-d=6+3z;);$#lEf6 zb@Lz}2eLKN0p`bbG`hkzliEH4yF`=G=esRO(B*v`hN#he@mM~I;|AhMnj`R`5%>Zj z35d#wg!+0t|86)El`F~SSKJ6uMw~?CP~2k|zs$0&G4cVLo6q4)Uz$uzh&oyZenZc9 z)XF2}WZlDO9LEDTErT%$shZ~qg@2+hQTF>bbT{t`W%v>g_XJOA1Q`FDK@133(@_oB z6ppiNxvB;vIN3bkPb#++_mr%uZE4%OlbkHxE`esucl-fnlP?AQeD-GWfSMIn5ht-7 zpXox7QwduwsP&^%rJ#qSG}>pF zEepyVj4}r*v#JtU%khShV&ZN6XHg>$ahZQFHW`yI0XGoFYs_G){9H6dRa60Vkfb~! z;>Xn8LV(ZiX>VSrL4T4&!co})3MEo$2<}MaH zB=o<;Ja9dJEqGh2y^it&`7}%VJ;=?FtB)N$UhPbUvL$P)wjWMCV~~Dk0mF zSG^j7 zQ$W9%7>`3w()@*>cc6CL z)$yK#LqWLfGP60HVN=aiYExcv12y)GM=_6yFPQ&l9GljxJ3UR7T=0j@<-urP#F+nH zefx}+d_Ru1&@0Sw$GnH#-0gv04h7<{o*m_od3)WL)|oYJwdVmq-X-Gl89t5+>8jk| zC`|(ETn_9=?cJ$!!?O|v%>#w#x@Z+_pYXa-dY~ z8R@V@-29IckEdn12f(O09wL_f$iYgriiw~N=4VHxn)LVq05R$`5{PtS1hL_J3r6Jl zNsbY`Rhnq?&q#U&45IZ<`Q-Q5S<^PGu9U;BdC+nw8Ipy@3OK6Lc#j0y zUv;UmO6|uqH)#0+zXw2htv=7fh_ybfZ+qvDi%;^VRz!Fz?(j~1vzGc*XfQJqu>#k; zU_0#_Pj32tlBm&76DV>qsCz{unS&pdZ&{dLlR3ixOjI-o67g@b#H6Z;MyEompL9RF zl>`4m|IT@lOK1+`XkHih+Apsy(6in><<6A3U4qQCS^_giICvgUtRoOqz9btP)mfVQ z25_hU62eW_$=6H%Nw0r%mJ)ZTP}lGK+bh$-?K684(M?jwHqCDk1ub!HK{?A^P`VgRKd0Z&4O#vBEtSp3aO)SZc%LAenH@dO|4 zMYMw$Y%P%NTa|AeGsQ`-e|PE3Cg?hnIVy+n&#Ui@a97GWs5zIr+?Qe+%r< zRp*@_exV_Df$F0TbS!p;`+Ic|ohY-47am}z>JqknS1{e;f%tck7$3|W{i47jrv-o5 z1IhzdlrOT&_#{~#2;&Be1(d-L(=PZuoq1`B#V9fWn@SicP%Y|gDHaZeNx-{@^~Wu532a9odclC-;ndzq{yAJ`x};aNHxYmCDQYLWVqCOl|H5xg!VWH@p*D3m%SNgy^14j1R~D ztsIZLTcOT9b&*a$QF3CMVgYGB)T>kr_XRN$fIBaQcnR~4iB^=cl<6G+c)g!p!AS`X zbod1iDPd+>%N@kTJNxWk4GjxU1bAvQexpja3UJx3TF@fV#+=RvSZ^?>JtC=;LxmD0 zwF}rh`sR?G*CWSj{C?U1gYi6tqra;BO(|^;xP>wpdCd{?^ymB-j|Zzv?yG8wH&)QP z#4S{Qse8D*#joEqV;Dmdg?lekMM8buo z(kW6^Z)D&?EWj&GX_nt?PWO1%e=d=)cvg7^SS%Y~%T?l&eq+6ZNe@qR;E}!1{X9Sq zOl|7hb6MeVs$Zo%vBiZ(VG3O}>&x{S^u`9%m6Yub`m+oZtvhNj?hf(m9nk*PcRB zSf@Og^7**+szPX{qXX-{f-icXgeI zk(nhf0@!x9JV}#wMD^Q3*Ms3x?zjgMT2u%baAz*l9MNAT-B6&gurtZ-`(*fBX1-1? zW9*VHUUQl|ae zBeyQ`TJHe_w$^RQGFW(i`Jhb;Zs!>CT$0FX)9{9zHMxIL#vR^w)a~TxxhXc_F>r4%q|)*zj`i5tAqd`93UXYULuUJ!n{Kl9?HcfkC!RaJDG^0`j#xerkQ30Ve{ z=Cjt0P~nyxftI<3HxzkO6+aPC9l@O2j=DIs%DOIazDdH66~FT^_t>2$_dog^)^<-G z53M@mc0I_(c`Z;9C)kXLyZ7hLh z11!I2-J`YqP@8n`ni+-grRbN-P~mdy?^vnn@PA27-TEyr#IdI0f0(iHRQ9L$)u`^9 z5^pa+vRO92bKvT$rbla#IbJi?Xv6K@0-E8o_V3;*tqf0Vswe4AxTic9CHWbMz>T|9 zpZnPtMi+4GYdg~VS)iRsUmA+M4leQZ1I|=j9sNERSV?Pxh!5DY6;?5q(zh{ph+=i> z{cAAm)0bpmg^fN;Y;(7WRFM=;V`G=z+~e;cS+>Dfv9Jy6nvKRlgu8Tb$2s&toXIs%*1B+oN!qd5B1QOEKUV3^jK9LVB76^4&Zjccol4ZGi6U9Eq*(eZhY=vWz%1HDgGu>+ zNMG-#n*QZGD^*BDIhD6sM0|F5MR*O>Z>1h#SrS5Jbir7`OXopjq%#Tos(6GU31Z#> z!(M_Y^?w+jLQz+0nlPHJafNGs%6>rn12r`5AFxLLgaYHYt&S_fltP=(dWAE@I|;`j z?1ZIt67U)Iz#`}zND}&?XM)DP%4!xv6%WVzQ!0Y>`5sR-c2fo8plTIert3 zw(;YQE!No1367@bxe?m$D}mRQgq=Q+SkXAX)sNEf59bsKimMnj9Ubi z)E{2bk>kE#B~uS~N+!WY6+Uc>1mm@=q8iXxf&`i`Yc)-oCFQCg<&i{|e&Ukoj9HcH zNC;RQ?WrXW=fd>tC}qd-ihaooYIA%c&6g6Z5gd9&VmsSb0vq9rK3$sT%P+Za-Rli0 zsD1JKiIl`PJZ+T6ToX(IXknz)g&ItU<>iQ!DZ*x_F-ie4y#2LN+-BTH|2wM~)AZ_w zGB*~t2RiL@_*!r}CMspmO#$!^&ED_WCysJ~W=zO8NSa60{$17?d2R|im{lRCR!|q8 z{4T}Lp%~6mBO87I{&VWuUgkN5`FT-vb=yw&8yb01$=9i!?Kr*q%ay3%dYF9Ed*i|< zI9xBk^S9K(e(hpi@z*^Yt95vILd@Scrg_x+Bvv-x&o6o`WF-P4qlx#fSFv2EqA^y2 z#CCY4bL7yXkcs&G#Ny?bd!7o8**+LHs>W2A4kP* zyfikz>mE{O3Y($=tqs)QzQ^>0)jNLOS^S|+d|&Tl>vL~XY^;_;Oxf*J&T`ZthwC8c zL%qw{Vu7V=g7y;Z>08UIoBtXn4|kQT1_b|j#zc}i3MEP0cdR ztwH9rD8XPN>O&RZs+ouMZ0WS+7so3@t49Vjqv!>K;Fi!@%!KwZikpf1q zgK|y=>eV>vAlgmk}Qc7(NgGMWA@R%=X~!@>(` z@7^3->s3!&kFR8g3jZ_ad zSYD1j!>jjeScvIWWD^J+^$@ya=yZ*{zp*x}HOHCzG6nBDbg|h#F<4nW>ryGR% z$+dv{F{ZJOvJb#=V#uk}yezUorOisGUo?@ZID!Y0m)jz$&PaoGRSsa?ZY@8qXO?d7 z5O1GB+w~*KrYNdY$i81k3tS{$cclA~kii(U76RjmJ?%7>P88b^;D6stBEq259n11I zmAF-QhS!bA=EzWPBQNcE)vW0ffn#+%}XDkgOFYQ~)s_58#|%ck1`E|YumHlfEj zG`XP+LVA3E^VU#(`58##JJUbi8fP*g%)Rk9gONUa`v#8=Tn}otbb}Hb7K_$tg;{I_ z*#GOx(S77#4kd$R?rYGkD_;ziipA8`An_rI#nym_a7t6_{^~D(t%LJ4yZr7CsccY_?Mk3%Lo}yH5zgeHVOZ%e(-pP#YN+0 zR}8B)l`@9OWN@ziu$wc_8O#A>c|%f{7H1{3(33+Fb5s#ZT}GsLIH*yU#0Cld3f_I4 zN3_3fQ{R%BOv4#5J~Zl(#k;&&R`>)rWm_wMzjna#HSN!C(c6bL+B(O%fgV)0Nq&&I zA)(3JqeR)>cgEnCL?=4os-dXM6W}eE@=cRZy1mIMh;OaZZoqLQMZDJU}&AbCVw!p^zv@W3=aW;2_=6^DhBBxB0su zv&Z}rtB+9s4Wik!GT8cgwO^X7R`X?f6?Y#}MNF~}?nd(8tj*hHN_9qBt$9F10Avt+ zf8?~1Z7b`59zw?isE(m2Q-Ehy)td~*G6TDJUPq|)EM(b0ajJrSW*BwiE)%dc^&-DO7|-Y18>uQ3t<~ z5S6s=SybMk^(q;y4spP1S%rSdo_cnQ-+3|6GUF-W*ChSDBXKoqHm`#3Vm)(SCY6*Z z@za3N7`}mG+dhLEJ7B+Ph$LzP&Hrw$bX1F;DR)X7d?S;x5fhI;tfK2o6rm(8+TdTZ zGgZfVkKOV(OA%H6g_aMGvwG2MK5t2dIzxo6?P*j=84c&CYN4m4o#q5q=G<4<{Gcac(D4xT|3GDgQ{Vtcubx;E!pl^7LxUTREfMXz^Y5 z?_gYHG>CJssxkUt5giL6GYU^PXupDhDP|R&VbY&%M0zs@c)ulqfGgHJJ`;&`ARHlrcdu6& z8W$T{@*M)F>2GGp#*GUOK7UTj#;IjL?V6BCP#c~`HnDK|e{0PDli%k2)6p0KjBYPs z+3yO@96qp&w>_(o*eBn`r&PT*9?>S=y+Peo6zKD`9DrrMASm}#Nlf>Wnv)mSpE9;E zZ|S2P#uK>%V-u=E?PY)IxF@OtH_LDTP7*KnF(B)*5(u&p{d9v zQ}ye8n!4%?>7T{~?)XTU(e@(zj+@G*G^ajP6(?5^H)h9g&$Eu)B0~(2cFOJq#q@@i1V1)Na!4_ zW-IcP@JVEp^{Oc9;nKnj79Qd%rcC@No-NK;-mjTnQ&V%Mqo1-i39w{zQB{5Z4 zcS<5m3{MIsxsk5iQx-=gHy*FgZk zL2@DUQKjMO06)E}tZm28k1s~x`+rz~gRgD?n|t{}jAmYDoDF)u2jh9L>)Fqf`q<96 zZ?gLV6WXS%f?YwDtmi$XZWu~phq&LAKXRDu)@uZ1gJoJJE}i*;-1QMV=PJyh!xZv>E5_*M*r@@e(3?r$mO~Yl=|wt0CR<%@A^$b@ zSONy861sn6Ttt?3BR9T10?7)2NR9i{6NzV!2H4d}(k4V$`xHq3n-v0=|0kkAo>Z?@ zjYgP?Roic2iHcFluP0od0DIq@@MAqb&V3mj!;TTCRls&gQhsfbKyNMS#!LN~!0t~7iRPQkU7ufa@ql~+tZmxWc2(5r%D z)s*}qgrN7;D18wMO`jLQ>3N)JKD*I!6vcaB0l$La>(zF3wWYG%QO6zbnm}=TD)=DDBO$ z8SmTG#Sc#x(4UPU$yj?aH&>q{LpN#D^<*L$=vx%bMC%+{zOUYYg{tb=3BwWN@@F6x5VMGP z(U7Q@B#ASqd8wgX^{Y&EHcAB1*QTrVi=8nii$HIrhAcF$SrM`uB* zftDsCyG&ZCRx9YZ5Sm66G|#K2_n;({LEoc_SM+sjt8XR2YRUwbVxz^`=wvkkk=_L` ztzBCX&WR$lOnlMtj1w6Bs!|3LkRm1B>NItVG!r6}X=3K&74B`ux15nAgKVAm zhxJAxIMN|m%RL5Rya5tHyVd8|(ihAYI*6<)M~H^Tm#nc%87XPL6dehbH~olg zWJGr^>g(lPw&bC!LeE@XkvJ@pXkH4jZMY=v)nkHpiq!2 zH%gC`(;3e1@t?EJ|7>M^GnOK?;3uxyWTgzAX@i4)M_kvJm8uNg1$<@N9>h(0IDu_n zvuku{Inn;q@jO;BT-Tg)WHBd)E2@45hrr^i`!r!LwcCf~R3T*1Ma66dsHJLva(%^( zC?!taKo&vJzk;#Bg0h?0r{4?N1v$@c_vAk|YD^A|aeidAWd+Q2*@ON3DZo*sz=}}H zn!L)H=xZsqN$XEfD;WpF_3MZ;(^=Efa?||B7#!d^t4fj7Sv|e(#)e8p4POl^j%jWC z`?RODZ2&u2y{hQn`t!x!&1aG`)<25JRy&MJqwah;pKiSvOk?~he>(Wm4i48^^D1ZWB4I)_8|dJg<&gF6s*or z0%YoOw3v;r3MPxZa%bpC22@S|q%$Xj4YY#D7F#VF%qlzMwP!JF3qhzMC54ceZew=1 z$`0}s_T_w>7nyJEvGG>4zQ2lU-z>ojrNuy3Zc?3B<=u=EzP2^yf73Pi5;EoM+iwV= z5OO`5#ccU$oW7fWh|v8!Ds6g1OuBevK7u|!3H}q#c*q;CQ&8?z6kXSP9wA>`t{Wrc z=5q!a`6P(D=+q#}wYi<`Gc7cp@agvIO?U{VK9%}aZK*l%c6&@V(CZoK-$ zmj#;2a&$B~B8y-k*BQQN{4Zxl-3UKqc5B*I8PVc;8*gC31Ml{)rP37Z^sUc|Vuw~2 z+2FNjjfyVu-x4Em=aF*x<~SMOj03)>PR{yM^OEHK9uH`>`T+w|HnM2;Hm0^$)_$inH7bBgec_}v^T2fY3}{}q)hTpPGs-B$>e z8)r}e^G%g@Jd>$qmV^F8G=T>ElOQZ=30fcuS$kzj^k=5Mwz;-Svy%$=c~Q%6_#7#G1lDvSQS;)^CWO%hj*sikWN&L zO__{v{<|Q>R$cE-!-tpde?VO`v#Rl{yVW$4xMEszj%(~mNj9floq0Dd*NYvK0qx4U zHqy)Kc9C4FHG?MMZbfMsNNt*ITcPePjfoggIw3XbR=1GxDNGxh5k+}>f0G=(Zc$#! z*T85sk8mrlRc0vgvNt%Ugf_*p&}&5yxCjQSrBt5%@G+$xd6b{L79f2ax`d%8ltv!; z6VYVh9Y$qzW6w-Skotn3!LzI9ojNHVPm0q)Z_pA5>Q}8uj*3Vg{{_1tzE@X&A!}Nm zeSMALAN68Gxfig?VJMrdc_FXPv(S4x5H`T~@oYnWT$uaYSAG2-#6Hb(giz=E-nxAh zkWEO#T-O%&a%P+Vj0!@P3t6Htq5L=+EWXdKc&9A;LM%^#GtZ_FsQPYTFvT-yL9Ts} zo3kheQvfsdYTJtWJn)J!W+|<7u+b_2n!FcUYA;z}wDZjG`MH<6CKf`kL`tp8FPH>t z)>j{dLOHBC_Jk0M2zF!P)AJtN|NTK$>fbnjBc0N@Lxi9x+7YZJlXmOjjGv8bsXWQa z;P>FZ_hn);QyEEY%|BVGYU*OaQ>BICp;G1v8l?vG*pjk>_y5C6t|9Xh0Phd8W WS3<-%|H&7S4Umyg6t5LE4*oBP9HF)V literal 0 HcmV?d00001 diff --git a/tests-new/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png b/tests-new/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..e268634b4e9db4d446170e11c83ae2e24830d58c GIT binary patch literal 16380 zcmdU$Q+p;&w1(fj(Ztq-6Wf@W6Wg|J+nLz5ZQHhOO>8GS-~I{vY#(%Y9rRVrs7EdmGq6B+;jz=??p$$uYv{@Z>)eDCEqEFl2^0)UthzoM)5g%^~k;^M>C(*~Oc zV|)#HdZmPhOC`1#Ul8I`Sxd|hL+O;x7bSPDgN=~qa zIIxZH(?wT)rHUDQJiYUp#H|3XI^CF3!Gw>zc|tj!vOF$ZpEg{sT8}*ua%G{BSAt0R z+OJn&1nEQp;Naxc{#xolaPXDF;%@q9e<12o4=s(KKk$5RUI){Y9|YP$H#v!qAH@0e zln%Bl2N1+kBO#HK18Bd$$AHk0_3it)Qb-~z>#ODd3KKv_&Zh@=K8H+B-b2g%5!8!; zno|SeYzBpjs+*kk*|!4&EvpLJ-T(#-T_Yjmqj&v%`3k@P`{loalzp9|aWs%aF%Jni z5>&6?@^%Rl3W_pkrFSBI)kh-`#7rqwW>PRZqxhqNt%KhmR;*q4mefz1ah%ETat95@N^ z@h9%Qq>{6l|8=+L#?(TM^&vn3U@GxaJ}gz=r3H4S6-vra{&2+)B$FyyCAq?R;*!d) z@({EUX|(#1c}Ru}GJtZ7Amz#MwFeBEJ)1%9DtKS% zY|>a(bw>2Cfnvr8=KlHNDu6NxKll+n`ywN3FWN36dIN(JuAg=An_Nk1q8M}~9|PA4 z;slhOyWeGD7bfi1fv5)i3qt_J_09BtNqgLHIO*?1=UOOmdjhKI^AWpf&QTy@g<^@&En#SDJiWyUp?CQ}$AQ|AB z;(+ZD0e*t(%97R#@ufid$VG7ewZP_?EU;>Fayv~WVd#F+0Q=+xc$i2OV_d9ZYNC+q zTr1LZqpgOue*p|IK}Ih-pS4TR9A!o1_e}tKYZ+DH%Bg znYO@on{+J<@w{x@^5~3JTqRj$e#iQm!Tww}s~9T@>~+lw-JX%fM>e1V#TB|n(j#Yv zH{3%3;GFwEhIa)rWJRSmGZ4W$*Nvq@+ZO9(UsXd30bh{-_Pg~|Pv9tiRE&)qKQ3$; z@TJmZKwSy>tX)=#L8B@6Gy>w2D@F=y^e0Wca4WolvW+lI#xBcsARUu|$D(&I6Q;K+ z)UMN~Eklziki@H8icDP~eF$@^3q^0^XEtMpzu(f8x4wr##n^rrLALI=2ra~CzundI zoDKJU`MCP<27^bc*uFiY4@{42GD^eGh%wz**Gh zBQ2mZ-wn6+;N8hcPjqfWQ<5R+MHTjhCb^)JL}S}8 z-C222$TIXcHC|)=eJ<9;+0CnV(tULjp?7P>wYi8ixhPGrqhX-6b($?)7{8zPf|@3B zoao@W)4wpyYDw0w@0~gr3;cpfSo|6af^}}AbSnkyt{V7&6lJSY0lpZS-9}kHNKPL^ z%DO7hzMzSh@*1ijGwP6gJBU@^-c7jOBItNMh%KCSl3@fn9#IG;nB!Y&T^&Eh91|UG zJUU8WX(4Evb02cLJGO3UNHMtX!6Q2qeMip6LR#-uect+*Ntli7v~+%qM;VV1pDic* z(ZluZGkslY3+>#iB`i!E-s4=F?%F%TUmAB z&uQ0=e{c7R{2>6x1mycEvu_F&rw5a)O6W;4sA{BJ+nctv3}iJWt(- zT%GJBR(bSZ5TH}VqGe~_Noj+cHxnq1OwmQN`rUA5!@oxfPfz^gr&6I}Tw>Nk=W9&5 z$Bh3#dvF#h(5#Jd;BYJc{)mZW0XKn##6O~gNZCXIBk**YB8_|Fy^iz8b|cQ3NWGXp z{V7);y&|{epp)H2XkDE{5PtA=lpevii!hLOG0HaWJaK|%r(VanKqIDdKT;d3o3e-! zP9R8I|0Y8Lxi`|PJr(&kBn45MaZ|ZP{kh!vOI#L+_WmwW1C%^|qdMxIP3AHd;9OFq zNq%3i=mxyJ5N3RLmBmtpK2Tpl7Ih}jC^2BATTHpDW-twU5^dHE7X0rW8O*ENf8y@l zDBAGeePcNwidE$+1+%*;_M`g!5T-(E-fL2o?&Ld%!02;WcN~X7f%MSOz@OL1%g>n~ zn8buGTXFbv^IUvbUaOGk>M%(%@h&o6O?M5c zlr^F&A#>|4wl%~SzSDD1Siruvg7bt!`&;~=O_dqw@SM9?HFyL??%HwIDT4Oq{jMVz zCAlv))KQMK;bY3p#6P+8>Fc}RwO|JJlcHr}VoQe0yA9AjXBXrsQoW`I|9G?9V4gWK zNtzF1cd|BZrgMMad6E=p5dsY43I48sIi6WfMSI#%hz%5*2l1Gf8%8n4SDgg*B6}NiDI~Nv z<@V#e^Cww+2#><4-3Z4rklmDSFX3mjRa%Q72GIH#xnfhx#fzxbI*5$@+}i%K zbyLFVSRP8#^~jOF5AfdBd|_lgP52d<8FM6?L){QRX5psYPQZf}{$){;fPG9&`YY2y zD68B(uU0iz8PK5s$ntQM8@Oiy@eG~ZDGD~nw%mf63(I%u9!@u>H7vmE#HnS2@(gJ{ zzbela_*b=Mx(m2iphKBal z5AlHKp;++@E`JY~_m1vASXw-KTxPHBFfJ!SJdCtCb#5B&w=1mmb!n(zo3tlAoEAg4 zBv!F!uh8y;Rac@84?hr!yE>A7=TCT==jiu#ziC`{UeL*)<=kF5>#D+jI*47wg?mt? zNUpiO+=UaR5PAO1QFU#!<58MerxoQ1vT6IOSMig7E|Ly)f-=>_&6vA>`@HXtX3Ny- zmED6g4`NFrmJO=*{FF!?u`(%1PlldhwK$g&x>t|@Vf^WTh3BK`;}+<=`!E4|;>(Qk zT9QgnOTlBjMcMAI#s9<$n5~S`0Sc!ZYbtRx?pA*^hU!PM9Q!@TLX`#^l#v;{%M!<=gChX(Z*&}|WH7WsaT3OB|3?gwp5kczu?z=_;Oj$Si z0~A8e?d{gz8DpmR&yM9cd5KD+J2p`%M7djdM+xo9EaFEYi`#z#E#qENWNJnsna)E* ze;)q_UQ2r=uKk^*fDXfwR3D=O`mKn)txB5soo9Hcw4(HYT6>P#+rT534>TCCZsziUrA*rWY#H z0njomba+Aqk`jHTMeGSpr8PIb85*$)+l9BsKii;|=myewgN;%_Q?@m*>h(GviN^{a z#1zxwd}`+x06}ktfDjLMh9G~%<99&F7$Jra9_t%73!nJ}MUX5-X6OlVw{&3SL_=6l zIfF6StIzI$1%MKqQ-zPqd%Mc~S;35p2Tp7ZIC8ologO zHE`nR6AI!wt^;O;G>nv*p;_{$t-Df>N#GMJEDm63nQsMV!ULtW6X2QH>+c!UTvw|m zuoplB@_-L~PD$Zeg3cyCd6H7-X43!iQd#2{8%iU6N6Ue+IvLXaDe@vR?NRTOY;Em6 zzAhHEpp-T0%QdNiPWY;3d%Ks;o6b|W@&ogY;m%JNXKW_`>Yc@xINZ==YDcS+eo1il zP4^vRz{dY34dmf*&GjHZU^ubdwdCRtA9AQ_l8P6Ufuj!L{KMh>ch=W1#r+5{`gwb; zpNb(#_S`r!7L^P|vC{Ve0DF~>(cxS3=evn=Y3>op%?}puGD;uk;XRX>8q2kO7yT1hVJA(-n_p=z);G6NADHJkCR^9t%FzsJT%O` z_8F90&q-_ddl9fq`ceqJUXuo0SLTW;pTE8CpH%+uK8mIx*M-b1V6hdyw2N5?k$BcCL zwD*{>b$~T!IjgaCkN_kD2cIcf5vB!k=tPS$rE#kO^WV_X{$Ti+V2lEP-y|Y!h7N6UKup@O)b>p89#tAnMZKIB-0&+ zsJlE*+2BlN5Y|u61K+?QyR=hO!qDU5^LvdO35Z!+x?i!?@%u3RY-uplkS**+q8pn@OQV3o&8=)%J$45n@+b+naW?lMe1LeSa1 zH&9I7#jpm2KBi_GJ3DdhqXfmoo^rue@CkGiYLukSD7aY~EI38zfn1|MY!pr+O}V_G zn}yh8uP^m5R}|{EWEid{|2_s6gkEf@Qp17S-^UWgXoYojf!r*{I#LfqHAJXR6YcE& zTBg&{1}_DR#lpI*25bHWp?v%Yf*+v<}*#dFbg*?_?9O6vPQzLIw5<>vvT7 zv4vg3GkfWhyPG_1P9Rizo*7KCEc>fz##Hy`3-(W%jy6 zf5j%cST={L*0=I9T!;+3F~nPP?A;>5;&yR%2v!<+96Yo;slhmks5^#d^?%3~N-no~ zk96sH{92}{sEa*=SMqIaj#!hSa^OOp0x@t*!&D!?8I>zAMesQ;FZnYI<;~kg>JDZ0 z@VFCYl-=qE9?80t!9ReUQHP8Ae6007DZ_f{*`UlCF_~>zwNdNs`4jjmIy+Ff#9W1t zvW>XTq35&f@sJXk(f`DEUMAyA{3@tYh-Tr%RxVNMn;B!r5;g;-LG3c-McCu;; zbYHg)A@2!JA4d>#e@4EXXgP^UP4y1M!TcBP<{zAy$y=5ZMVD2l+V?V-M2q9eK6id5 z`=S&-c{7L>eJ^3`N>;?i#ay;%jsZ{B?rvJp+TE9vS&>v31OMk2Qot9Lf}jjUf8;c4%rkb zB?;}~kW;`d!3vX@XL_fBdDmyRHBkqlYxO!folaW4 zXio9uF9rJ36_T?O*0acnsAQ2CUC|^-lKlfbythBp@cPU;j?OC2ulJ?bO|)E~yrIB@OT$t7p%Qz8;??g9c>%6?`p0Oso!B} zQzXKzbwcT!`P9lOF&T;A`oLM1n$(XDVTrcZ$^5Jc|67eazAhwIjmAR3Iv(~+a)^qJ*eH6XaM-mnB3|NVEMQCTcVY?ci+dh9oH}Wt)sv5=g2(PWtLbWic zt`jf}Ck5$nc<6GaLVGuZo-row#cvW{s6K@oNK6=Z00Su@vZZ@=Q!zO(U6Vt73iV0q zL!_^;8@hSqd|8?kGY7y%vCmk?TP1~In9v3Ermj^@DWr4~V!bM17^G?2oRfEHiiBT$ zOX04pIPYqQpcMFt)qYw{h7CMNHg|mfa}>eP-DEAUi{sne(Y%1=X*}3~7_DDFCgCc- zU86OGYH!^v$&sO|(rTOdo2u$CxvJ|d&bV~|ldL>v=QDm1Bo`${xyzZzA1{Ol5TFhE zRce&Y7x!s1Ad_yNMNNDRUGv&>3xmopf4b70W66CBSMcJ^_`H?7TB=3EsC!03P9z z51#YK-wa)CXDu+An=^&*Nx~rcidJ_B%w_h8%pS}v6!LNxTtoaU1vfZ8?n-%F-z-E9 zm(k#XY(Lt2Aogw=ck z^krxFd5uJy9_;3X`+CM$I}~xE?_9Yb= zKC!;rO_Io}L!QE7t#WHRep2dh)REYSrdjUDOEE z4bdTb4BZa=uClUKqxL2j?Q;`{f4ASt&xuAM;5|e~;FJr)^MIycw#G*3F~x1kF#L@s2h*kX&*@{mToNGvVwQk;*?^VM<~+Aa(04)Xzi#+iSg`*)&KPIZ?aQqNSmr>4ePMk zv?hL3GLS^tr2B@_>v+c^*(N9~)k4gf8=3|vYc7?}t6ano48P)~6SR=odzpd=InVZi zc6V&tXOIk~NVo{!Xu1qvX#*LW*XNuON{i#HrBDrU^C$#ZSkv*wrjkS_DiqyC!ySz~XE(jqcC9^lNShz0^Br0O z;w>>kLyFvDv_3gT&oZSr}FD~jZkH0RfTx>8VI9bccL7CKRq{jp<1w=FEvmXS zHp(v!{jfib9-5Bk9mi5eudY74b-sL?KrqgvLq*dgssG$~5poo=*?_B2^}K3Cf6B{~ z7!KRo4j(EJ@c2ACwRW}^qZqHB+o6OV{u+FxE*A%;N9z58a;U3+u_2Z!eim*OaX33F zwX#0E88A%kC0(^9Ttg1B$-(I2*T8jRL6M zW~cFywPcVmYYa=>mJtl*Bh`aQ^%J&&EU)$y4^Y25T`DymQFjGEY;hZJu`w&pF+m6$ z?)pQ!^|#`6OdLf1xbfj%bdDl4LHjL4!{_BSwsNRat_}<@N8N8^__2T)=PqjG0sV6Z zz(Ku}Mr{3Mh{rFWB)(iZpl%YUH)xi~9x(R5-T>f_DO{ z*6=##k1ebc)$4xCz?DM=%A_Kj)kZdFgr-uzKTSWdB%cj(Dn9EhU$3OFU(%{S++I+P zH+HX6(hU2Qh^j1_8b8@@ViJK38}18Ovf@!Z&YyVp=cRb|AGc_oNPISH&Lj&-ce~04 z%+3e`a1EQX1>+cAh8b-%-rXA7eGWrV0_#faOVKu=L|rdF#S~C`vAS8PF8($wB`aao z!=Pv|(hlSOO%LiO(W^Rk|HvDTo4QAXcUBz;bXSZWpixTfMFg^&H`KjpQDKmqGM`S1 zC9}qmmOqa3C5;klfdvh>k9Dn1`>b&a{HUywMwOT zKyc~c?J%zb=37&>Jw0w0SexyCF+fF(9RC?+;Q-6cBlE`r@%zQ&D&8R*@HYnnmECQk zWB*A+*w`HYVpPyj`S3w<f(vH;Ld-<)?FUJ@s-|?byG!oHn$^Xi0b9ZL?zne7?s@z|jUXtT1@Y zhIIMiLK>OdpC6GVc7=KE>DMfl+f!0j4qt9-ZU1mbF-6 z8<%%q%Hj)R@9rSN>inqXjV|1br_*u4A8z#)B~`@f1y{4`P9k?nWceZSFq>bg z{@F|@-?nWqxE!dhs+^+8^?J3m&nBBvazW9Ue}JG_M8Xc&gA9-{UA>=kcm}5U%Y5F zoT>c7t{U+zI#+|v!m2$F*{*(A@gCx*u2pPc_3^Z`YkG_$y_xkBc^f2o3&OT}3^b62 zu{vti$pP5q6V~sq@2|}!HM9U)4s#-ChjVxujzjsVw}1N+jdojJ;<7!5AJKOjixFgH z>P=jV?Kff#qH%l}X~5n$b!vMk6z9>zZj$wHxz4cU?0x4~@i|v7=>jA+LF0y~9!q|` zAMeg5nI!P?I*S1S_|5;Z00a!!;4v6t63GTY8vLIKQBw=a+lv!MY80=1sc&)9^*v~kal^hIxdsyKqW!@;wLeDeYo}4?4c~}zn|Z? z8xVuRQI5WrRi{4r(faLC#9Pn5eN8mcS{ZocPvbS%UK z`c_2S#qI0nr30ON;_KHmLqq%Kv7CL6GrL}m%Im^UfVX!0Ll@z>kj<&4mB-WDbPO2^ zY12%6Jc98Cy)kIOqz{473vv9v;X&>&g&qfv;zhC>g|_HH#gr&%^Zn5kvnNEWtU;TL z328Y|P0j5t^ovc&HI#|!hZC9jsS&rV>x(zo-#vnNwu=|gwdn(DT@Z8obh>sPzE_%6 zShf^Da3Kcxr*3gbv1}$1({tlr2LpFGovsUt-xk~$;%gfw$m3xy4_U49W?ow)xe*wZ z0XItuV~9@>?O;w)!MnVABP`R+MvN_8CD>L~p9~C^4l0GtUyB;-Y^)GuY!OpOvt(TD z@_m%}$SRPlAG>lC645RtoJ_^(Hz>04QhlA)YkQ?X5#Zc6DT-^i7T_HO$$IJ`v^3Sz z-+Xb|3^tplOqhi6O28mQIt*x_{jzX>B4ov#-f$X#$8>2JC$QrNr1bD&v0A;rOQn&1 zUNCfCV%hE0p|rHJ!fv)HnNnHIw9Ksy-Y$-m2pvVV$6~R;5VuPM2O$ysO>EZ)iX&bD zyJUX1;L?6yQTK6DCfVi!tAqwW#vJVrjcq$nt1svP zVc`u%qesv4EJG71v1=B_ia3*^AoXkPvR1@@qk(^zu%CYWhF7oR$AH-1kAMx&#Nm%XcuN)O;64DF~Qx_q`yiYn?; z3Jl-l4|{hB)T1_AnOo8nX*emhcSTmOqdcjxhoh!gm0Cguy7e4_eHwqejheIM%F%}D ziY`E(;&(*nMJ^N|pn(Cw=OdfOckUkt*3Mp$Na?ekNa516sRCmL)$*GR%>&Lhx(K6f zwGQ)K%hgV|O#oa0^KGn#WC(rn|GgH(&?d)ysl{!>9q;u&hUUa7Rxh4UUm1wIF1e#{ z>+DZPY`zm3m93TMo}{70#hb?EP%Vxt4aUVi`0G*i@_^i?Ta5g!a|S85-*u953yb^3 zGmfj**l7bSt=r-qr{~bYaXn_^e7Mr)^O}u=G1b12k6jfBCZ0D1CMvCZ>a;y;^jXZo zs0fOKrvmDmvJIvE&%dpS$Hm9-CVwQd8o|!Ir)cF_oJz5RuA)+piGba$=w94m1qD6o zCzKZ5{_VcpM4+3c43hbGQ1-z$clGg^#Vd~RVmhd>1Whoow59AFNSee{NJC2E$7SV|m7j05D5-Ri z`2#!fpJqg9cra@yW#{2T)rZ!>S%RrFf98ssOwTMB8LtINE2t{Qr(B8isExyKcoF&R zExUVN8|GiMn73tT00psembl`ml*hONpUJX&agc&x+T-871&;3PGRQZA8O+vr-Uj&x zr5E*cH+!pZB;GnQYe*QpCFAQPUZv1E>+}!9$!!_lfO} z9cFz>uyHx4>5~y(msC2}R_~Q#_%~<6e7~po-M^sWe=N`Heiuotbh`5|5>uyxqUxBTH1?wMt+6Gc9S>K&E% zN_`NQn6H7+63Uw*zMW)bu7?a`9Xvt^k@PtIbksR+!uSweEw$%xD9>uTQ!nOpj2+eeKyyT>vY^a6qyV|I>XK}f zDr^t547R8@L4~9hH<0$YNTtFmK)C^C%V_UtoymL#jrXrK6mUd=(g{NB69I_Lb9mU~ zB}Ru%NG_MB&01y*0+HGxC=(Fk@I~v{&K>L(J5QIMag(PgYFZkoDTrOzb}ZuSxs+Y} z?9*`aZiZQmNa7*nc_Bn|z_MBP0qH9&($^#g-gC7i!|(RtUWsYyuov#S@@PBNOc<}- zIrUSbLt;olyfk;PeVavl?s_#P#>1D49fzVC0fVi1RVZasXhDh#jC!WV#4h`j1~i#R z=i@6pwJFq`bX_uf+Ak;qv7VzM;@)?1ze~Kd6V;51*%7pv#6LX}>*r;HVU4O0IJCF# z^ST#t8XK@fWzy%oEFoqA)da?V+RXtmr@A?K^k1mtyyj-4BDm!|DC5&u07mi_uH7no<@V{&R)KLb`bj3-nk#66d+v-;;86U9=MtNS? zy_z*$|6U*Bb%+RPsUgw1cgo8QW)!`yRqIayI;r%`SvhD;{B%8#JpI4ZqSB6^b@%qjQAzUteU7~)j`m$~ zyuwC#NR8T>y>U$U+dlehdSW4 zgaO0;3{y)VTpzxK1QWj)eR$8;l8O7Jvy;8$d@tn+I$k?=>r<~$?A?Q$SS&w7f9N-= zqI2o55L{h?Xh>oLFK-ih`6aOu$Y2Oag=$NaK9Mi3H1=T%3{Km5~Q57~O3W&2OGfAk6g7H$lYJHXh<8VzwdH zp+h6~b+#yL^utO6D3GUGHtp@~Q@M7mm0!>^n=dBac&+WRvWvi%T}0EXmJ#BirUw1r zinR}#C#LpRZyM=S2sS%Ioz)WAm9hBT`M;5^GXthL!?o0^(XzGNtLoE*e9S_)0Hoqg z@0qFRNVWXw33>G$MEK4TPB9X%M;=2Fckr^&0Togu#)`$Z(jIbPL{}7ri`f$eFG5AS z%AF@J?M0l5AZ@Kk;jygwWWIKBq#Usv_y(FcN7%{{J9_QwW5lo9G$+rx;pfcRAoqAF z+mO2k`mceoA95L7_+F*?C50x$a%UA~plD`Lkz6xL^t(p$1H|P89<3QUjtv9gL|gA= zE0=>jHcyMBbo~1{C{qs#8Mp5UAnTw^VrBdU4P^G zFy~jg!NqBu!_n+jLSKhazZCOj)8Bafbt|-C``XAo^Z6SAYWJOixo z%-Wc7)6nc%1H_|sSCoY08>GiB?oOydAIIo2F;D0==d;Q9+FWMkCbcP>(?MR_?PMY- z#JP^AF9S1ueEd5altBV+>zxe>&hpHxp*YCz*jedJeLt?iJ#JuA?|Z2xq=hA$ErS6M z++J~GY4+NFa59fGQx&e9NwJb|R-dR5s^?R_ocgK4k5Wt>kRxa}NJvBu^O6faiYCU0 z$7WVznq}0euWKvY_vYLou_)&TA>KtO^!q?odi7Ld64W)UP((!VmHuRocl zQRak#PCiB{VOWDg#3s@D#<;!$462fG#Le#@V3c|_V4<+0AVRfXHKI$PSLWo`7tX^3 zAfIsth-B^hKQPbuRmFEmih853>?Wa?Y1cas>)9^u1VEbPgHM&R8n>1Lu>J++fkJqZ z)wqOdiOV!}w<6UlX$-a)QUW5H}kU6uyBKzZ4V26V5bhK#c__oDY!}uKur=o@d%tTGOLs@49)HT8* zKYDH~xL9Ohs98Q zy6u=#l18Vg6@x@lH|{14s&4p2R>Q!Njb?$-2={ML;~OqUf&Vf5^_Yn3>`v=1(S%kU z2oD_Mf{3z1c4E}`{<)se5{+VLEa{inYo5sr( z>;(Ndj?aZ6N4f+;zolN1LIl03SU4C{Vc*#_Fd z;Wm?>N$^*+9`W%_y8s*G2blD9r9|5| zm3wpaFF!?r=WIziQ@e=6iRk*eqRhtko*EE<^KZDw;!G;{SlX5+V=tACsmEOIhp zh(Pr7M9&Me`;oHjvh~nzk+h-n;lspwvFe5SxDYetZ?_xg+#+k!s}qjz{%ecOp{ncK zcZ7SG$8;EY$xgtdbo^5d=*SZ7v73B|bsIp;fBe~Nl9tP{Fp%pW&w85LX?v*+_6amLQ2=1$FHC~QQt=>#X4Sc;q z;sQ@%=;Ft4KdVXhe_QOlz*S+GRg|hrw(z>#{x#Cg1d$v8qhyG`aay)Y*K&)|XMU9Y zT8oDdsG=^_qCW9;Q@K?Fxg@QDV`N!=8OM-3txh%dDfu^Ud9rGWFqEhtghj`=5LBpu z-gz;g0ligNYD^glj3-9+R_8N*EsXjVOaj{hGr5 z3BuE7-ZAjpRGp{&zW>%J%A+5QPGSoNhLWR3?52O6E}?8QuG>)hDhY+)$X9e?eVa3l z(G>{qGsoD=uLH}a$z~1Fv#d6My^T4(&;paQ6}n#o`}%J=*_ z;Z*Uj-y%^+%7vWoB`_ZXCCP%rc(~6EW6NgwyBbq~<6N?&rF9$6)9;|o5g4u+U2pZ+ zA`z;fLs~wES!RWqc*gSB+=_{AkblA^|rMwu8i z3w1UJc9P6*@Zl&2wC1vy{*C1?c938x)D{m}>HvCp_qD#gFJS=X&bIQCof+e~>Vv!L zdpeIg3Nt`WclO)Fe?=C?!eyd+BTvzu^3~KQa@wx~)~ecObjX0az4A5{hinURdx9oR#!H$0u)vSvdONsAO=B2cEiHrD|llcmC z%OA>prRmM;`YQWB830)t&8CaUCseuliOToCW_`#WiN3aT zvLf&)4JRL;dt@pf2bsWuG;?!6)4&BVgk?T36bU#PeosK-$C%+GV1&9 zx#DE9Z_;Re`jR`pg}C7mqkte)2FIvU9F#s73}mfr&6vK64vN}-RE4$SxmMvLmImns zh-1E()j$g)t<}bby~bHLi6IWc+)`D7kKel?nL)fuP679s}1?kY3QZpME@L(j;BZxo9A&Ta9P;$!+_L+d>?Qs=%`P0@G_g6(Cz#Wref@3y zinF)WkmC+x`Q+OayG_xn3g>Gsf=nja@iJ~>W;VJ};B@D^aEShjV~x)P^wYSiRjT2B zuae6NW1YL)rxbjB|FI{xVQ+ELhrs&lgaa4P!7Rq_0`F?3#-5(3Lw_$uXs#oelWEB& zvbz-Pi1Y>dhpFaT&#u(KeS1mNr*-pP(t}t}c>1pJKgwuB-PFnbtuuau6(q03p%FNr z)unHwaOa5W@BTIOYTyrN-w8QxCVXS6l&yw%dST#pFJ>s+-y7X>akuZ)f{k~nx zvTh)2*Bv(&QHQ?w(kmNom`>_cctbrF9qI5qPy^lvb_lK)Y3O_l zY~6%I?|W4Z0q7iSwx6EQU zjuD{%`Bfv_tJ_A;Y*c%lGR(|X$@xYaD^Wmz# z!CjtYg@RUC`<3&ALeoP;mA+JS5G(4oh{}OAL$HZT7HqIq5Xq07@x+^nR zYKzjhUP4H`0pSh8IU`xZHQ_&&ozCDVXx!r5zvsq_jK5xY+3MIT#sTZ`&`ryuSSJ(7 zM-h`-tMyPr-MyYFwg~&FTX^AyPYB5n$B4Sedf1CO@e&pooW@xjSm(|N^@K;hEHAo&|n^4RxRkBao~9PZ-~kXN?e zjI8l+*SCpUw4yw-g_qA=*4YrldL7tng@V@H-y1h&=`EWy4$*iOaamMhC&Tbh>CUEA z@KRd0LOL{4pE>Ev&;$gnP~FE!5K#_@NydHLpG}b}ZL6h(R8t%M7C*@tcOB`wKw=Ei zmaJZ_yZ-$W4dG337(w=~8bh(3WdRGF+>C5(AoE~=P$tjw0ewY-b8i+bl?ib! zbWNm_(wOURCUjtqd7Wd2IAPk~nrR=_5uYzM$?~t`Rc;#Rxm5=-j0h`oC}WlvtAyl; zP;>q=`Eidx?$FAXt3nv&Tyq+RhH+(F(@GWqzmOJ97^HAPpn`KEp!k>Oqzzb$4-D@z*ox?E8OZd`JvF^Fk zNqG^o@E&eQ5zmM%`GDGewF*jbzieh9nA3sZQe=L_164$n0ZEV4nWlR@lj(Bo9-y@@b8t(nfp9Lq!v8~F7UD77&Vv!W8l`5M5jp;on82o0!fJ%`~Koj=5P@m z-%B4k-#>}#4RAkKv2}!AYW8x7V_R=Y02QX@@sl3u!}ac{9zHu!quWtRwY#VkfWJ?0 z@J~Nd(%_%r@*F#<>(2eN+mE^S3)OB}=o#)7Q-jIGg|LaEquMdO&sMaYVL;Xe)g#wD zwnta^pQJlVu4c*w>$Lrp)8C{x5#gKX%Z3hoy?Plcd$&^{22lpniD&LgSkV zs-4N%4?P8jvd?3g_^g{fgXI`Wx>NA;9%H*5V1Jydt*Kb#q3rw2@LT>Ij_G}45(HEQ zD-@$q{1}~tM1yDv>bWwn)F$fp{8b{{krf@QZGHG0r09FuTZ~tql<9;>A!xZ4!pP5v z3lCZ{UL9lrsN7#D*zbnzxL}LGc*AyEy)Zm%%NjA63?RdRM1rpyE|$>$G2e_~bg!FY zk@cFsg`p1es*&w&}U3Ey6Fd-4fj>3jTl>(*up8s}K{zix8rVG91Ab@=WRRUkmIYhL&H?dk`J M{gD=`643SkAHwIsrT_o{ literal 0 HcmV?d00001 diff --git a/tests-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tests-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index cde69bcccec65160d92116f20ffce4fce0b5245c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3418 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 diff --git a/tests-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index bfa42f0e7b91d006d22352c9ff2f134e504e3c1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4842 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 diff --git a/tests-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 324e72cdd7480cb983fa1bcc7ce686e51ef87fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7718 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 diff --git a/tests-new/android/app/src/main/res/values/strings.xml b/tests-new/android/app/src/main/res/values/strings.xml index d75426c8..7fec7c14 100755 --- a/tests-new/android/app/src/main/res/values/strings.xml +++ b/tests-new/android/app/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - example + RNF Test diff --git a/tests-new/android/build.gradle b/tests-new/android/build.gradle index e4a1617e..26b405aa 100755 --- a/tests-new/android/build.gradle +++ b/tests-new/android/build.gradle @@ -1,49 +1,50 @@ buildscript { - repositories { - jcenter() - google() - maven { - url 'https://maven.fabric.io/public' - } - } - dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath 'com.google.gms:google-services:3.1.2' - classpath 'com.google.firebase:firebase-plugins:1.1.1' - classpath 'io.fabric.tools:gradle:1.25.1' + repositories { + jcenter() + google() + maven { + url 'https://maven.fabric.io/public' } + } + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.google.gms:google-services:3.1.2' + classpath 'com.google.firebase:firebase-plugins:1.1.1' + classpath 'io.fabric.tools:gradle:1.25.1' + } } allprojects { - repositories { - mavenLocal() - jcenter() - google() - maven { - url "$projectDir/../../node_modules/react-native/android" - } + repositories { + mavenLocal() + jcenter() + google() + maven { + url "$rootDir/../node_modules/react-native/android" } + } } subprojects { - ext { - compileSdk = 27 - buildTools = "27.0.2" - minSdk = 18 - targetSdk = 26 - } + ext { + compileSdk = 27 + buildTools = "27.0.2" + minSdk = 18 + targetSdk = 26 + } - afterEvaluate { project -> - if (!project.name.equalsIgnoreCase("app") - && project.hasProperty("android")) { - android { - compileSdkVersion compileSdk - buildToolsVersion buildTools - defaultConfig { - minSdkVersion minSdk - targetSdkVersion targetSdk - } - } + afterEvaluate { project -> + if (!project.name.equalsIgnoreCase("app") + && project.hasProperty("android")) { + android { + compileSdkVersion compileSdk + buildToolsVersion buildTools + defaultConfig { + minSdkVersion minSdk + targetSdkVersion targetSdk } + } } + } +} diff --git a/tests-new/android/gradle/wrapper/gradle-wrapper.jar b/tests-new/android/gradle/wrapper/gradle-wrapper.jar old mode 100755 new mode 100644 diff --git a/tests-new/android/gradle/wrapper/gradle-wrapper.properties b/tests-new/android/gradle/wrapper/gradle-wrapper.properties index b8dc2d4a..a38b50a7 100755 --- a/tests-new/android/gradle/wrapper/gradle-wrapper.properties +++ b/tests-new/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip diff --git a/tests-new/android/settings.gradle b/tests-new/android/settings.gradle index 0f7f3a44..2b21f7b0 100755 --- a/tests-new/android/settings.gradle +++ b/tests-new/android/settings.gradle @@ -1,4 +1,4 @@ -rootProject.name = 'DetoxRNExample' +rootProject.name = 'RNFTests' include ':react-native-firebase' project(':react-native-firebase').projectDir = new File(rootProject.projectDir, './../../android') diff --git a/tests-new/app.js b/tests-new/app.js index 471bc82d..8bdffe2d 100755 --- a/tests-new/app.js +++ b/tests-new/app.js @@ -5,51 +5,34 @@ */ import React, { Component } from 'react'; -import { - AppRegistry, - StyleSheet, - Text, - View, - TouchableOpacity -} from 'react-native'; +import rnModule, { AppRegistry, Text, View } from 'react-native'; -class example extends Component { +import testModule from './firebase'; + +class Root extends Component { constructor(props) { super(props); - this.state = { - greeting: undefined - }; + this.state = {}; } + + componentDidMount() { + if (global.__initializeEnvironment) { + console.log('Initializing environment...'); + global.__initializeEnvironment({ + root: this, + rnModule, + testModule, + }); + } + } + render() { - if (this.state.greeting) return this.renderAfterButton(); return ( - - - Welcome - - - Say Hello - - - Say World - + + React Native Firebase Test App ); } - renderAfterButton() { - return ( - - - {this.state.greeting}!!! - - - ); - } - onButtonPress(greeting) { - this.setState({ - greeting: greeting - }); - } } -AppRegistry.registerComponent('example', () => example); +AppRegistry.registerComponent('testing', () => Root); diff --git a/tests-new/e2e/example.spec.js b/tests-new/e2e/example.spec.js index caa08866..e18ea21d 100755 --- a/tests-new/e2e/example.spec.js +++ b/tests-new/e2e/example.spec.js @@ -1,19 +1,31 @@ -describe('Example', () => { +// describe('Example', () => { +// beforeEach(async () => { +// await device.reloadReactNative(); +// }); +// +// it('should have welcome screen', async () => { +// await expect(element(by.id('welcome'))).toBeVisible(); +// }); +// +// it('should show hello screen after tap', async () => { +// await element(by.id('hello_button')).tap(); +// await expect(element(by.text('Hello!!!'))).toBeVisible(); +// }); +// +// it('should show world screen after tap', async () => { +// await element(by.id('world_button')).tap(); +// await expect(element(by.text('World!!!'))).toBeVisible(); +// }); +// }); + +describe('should work inside node', () => { beforeEach(async () => { await device.reloadReactNative(); }); - - it('should have welcome screen', async () => { - await expect(element(by.id('welcome'))).toBeVisible(); + + it('should require', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + should.equal(firebase.auth.nativeModuleExists, true); }); - - it('should show hello screen after tap', async () => { - await element(by.id('hello_button')).tap(); - await expect(element(by.text('Hello!!!'))).toBeVisible(); - }); - - it('should show world screen after tap', async () => { - await element(by.id('world_button')).tap(); - await expect(element(by.text('World!!!'))).toBeVisible(); - }); -}); \ No newline at end of file +}); diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index 99b1114b..8e9cd41e 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -1,3 +1,5 @@ +--delay --recursive --timeout 120000 ---bail \ No newline at end of file +--bail +--require bridge diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json new file mode 100644 index 00000000..6d6af682 --- /dev/null +++ b/tests-new/package-lock.json @@ -0,0 +1,8903 @@ +{ + "name": "example", + "version": "7.2.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "absolute-path": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", + "integrity": "sha1-p4di+9rftSl76ZsV01p4Wy8JW/c=" + }, + "accepts": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", + "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=", + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.5.3" + } + }, + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "ansi": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", + "integrity": "sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=" + }, + "ansi-escapes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==" + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", + "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "kind-of": "6.0.2", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "arch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.0.tgz", + "integrity": "sha1-NhOqRhSQZLPB8GB5Gb8dR4boKIk=" + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "aria-query": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-0.3.0.tgz", + "integrity": "sha1-y4qZhOKGJxHIPICt5bj1yg3itGc=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" + }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" + }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=" + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "array.prototype.find": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", + "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.11.0" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "art": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/art/-/art-0.10.1.tgz", + "integrity": "sha1-OFQYg+OZIlxeGT/yRujxV897IUY=" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "requires": { + "lodash": "4.17.5" + } + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", + "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "babel-cli": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", + "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", + "dev": true, + "requires": { + "babel-core": "6.26.0", + "babel-polyfill": "6.26.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "chokidar": "1.7.0", + "commander": "2.15.1", + "convert-source-map": "1.5.1", + "fs-readdir-recursive": "1.1.0", + "glob": "7.1.2", + "lodash": "4.17.5", + "output-file-sync": "1.1.2", + "path-is-absolute": "1.0.1", + "slash": "1.0.0", + "source-map": "0.5.7", + "v8flags": "2.1.1" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-core": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" + } + }, + "babel-eslint": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", + "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0" + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.5", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "requires": { + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "esutils": "2.0.2" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "requires": { + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.5" + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "requires": { + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.5" + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "requires": { + "babel-helper-optimise-call-expression": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-jest": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-19.0.0.tgz", + "integrity": "sha1-WTI87ZmjqE01naIZyogQdP/Gzj8=", + "dev": true, + "requires": { + "babel-core": "6.26.0", + "babel-plugin-istanbul": "4.1.6", + "babel-preset-jest": "19.0.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-external-helpers": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.22.0.tgz", + "integrity": "sha1-IoX0iwK9Xe3oUXXK+MYuhq3M76E=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-flow-react-proptypes": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/babel-plugin-flow-react-proptypes/-/babel-plugin-flow-react-proptypes-0.21.0.tgz", + "integrity": "sha1-RVebmSZUDK5jo9aw6uw0Tp5coEo=", + "dev": true, + "requires": { + "babel-core": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-istanbul": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "find-up": "2.1.0", + "istanbul-lib-instrument": "1.10.1", + "test-exclude": "4.2.1" + } + }, + "babel-plugin-jest-hoist": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-19.0.0.tgz", + "integrity": "sha1-SuKgTqYSpuc2UfP95SwXiZEwS+o=", + "dev": true + }, + "babel-plugin-react-transform": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-3.0.0.tgz", + "integrity": "sha512-4vJGddwPiHAOgshzZdGwYy4zRjjIr5SMY7gkOaCyIASjgpcsyLTlZNuB5rHOFoaTvGlhfo8/g4pobXPyHqm/3w==", + "requires": { + "lodash": "4.17.5" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=" + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=" + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=" + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=" + }, + "babel-plugin-syntax-flow": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=" + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=" + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.16.0.tgz", + "integrity": "sha1-Gew2yxSGtZ+fRorfpCzhOQjKKZk=", + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.5" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "requires": { + "babel-helper-define-map": "6.26.0", + "babel-helper-function-name": "6.24.1", + "babel-helper-optimise-call-expression": "6.24.1", + "babel-helper-replace-supers": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", + "requires": { + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "requires": { + "babel-helper-replace-supers": "6.24.1", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "requires": { + "babel-helper-call-delegate": "6.24.1", + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "requires": { + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "requires": { + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "regexpu-core": "2.0.0" + } + }, + "babel-plugin-transform-es3-member-expression-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz", + "integrity": "sha1-cz00RPPsxBvvjtGmpOCWV7iWnrs=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es3-property-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz", + "integrity": "sha1-sgeNWELiKr9A9z6M3pzTcRq9V1g=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-flow-strip-types": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", + "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "requires": { + "babel-plugin-syntax-flow": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-object-assign": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", + "integrity": "sha1-+Z0vZvGgsNSY40bFNZaEdAyqILo=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "requires": { + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-react-display-name": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", + "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "requires": { + "babel-helper-builder-react-jsx": "6.26.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-react-jsx-source": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", + "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", + "requires": { + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "requires": { + "regenerator-transform": "0.10.1" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "core-js": "2.5.3", + "regenerator-runtime": "0.10.5" + }, + "dependencies": { + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=", + "dev": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-preset-es2015-node": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015-node/-/babel-preset-es2015-node-6.1.1.tgz", + "integrity": "sha1-YLIxVwJLDP6/OmNVTLBe4DW05V8=", + "requires": { + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "semver": "5.5.0" + } + }, + "babel-preset-fbjs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz", + "integrity": "sha512-6XVQwlO26V5/0P9s2Eje8Epqkv/ihaMJ798+W98ktOA8fCn2IFM6wEi7CDW3fTbKFZ/8fDGvGZH01B6GSuNiWA==", + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-plugin-syntax-flow": "6.18.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es3-member-expression-literals": "6.22.0", + "babel-plugin-transform-es3-property-literals": "6.22.0", + "babel-plugin-transform-flow-strip-types": "6.22.0", + "babel-plugin-transform-object-rest-spread": "6.26.0", + "babel-plugin-transform-react-display-name": "6.25.0", + "babel-plugin-transform-react-jsx": "6.24.1" + } + }, + "babel-preset-jest": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-19.0.0.tgz", + "integrity": "sha1-ItZyAdAjJKGVgRKI6zgpS7PKw5Y=", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "19.0.0" + } + }, + "babel-preset-react-native": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-native/-/babel-preset-react-native-1.9.1.tgz", + "integrity": "sha1-7I43gnRBDXj1UPqfjt1wNT87sv4=", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-react-transform": "2.0.2", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-plugin-syntax-flow": "6.18.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-flow-strip-types": "6.22.0", + "babel-plugin-transform-object-assign": "6.22.0", + "babel-plugin-transform-object-rest-spread": "6.26.0", + "babel-plugin-transform-react-display-name": "6.25.0", + "babel-plugin-transform-react-jsx": "6.24.1", + "babel-plugin-transform-react-jsx-source": "6.22.0", + "babel-plugin-transform-regenerator": "6.26.0", + "react-transform-hmr": "1.0.4" + }, + "dependencies": { + "babel-plugin-react-transform": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-2.0.2.tgz", + "integrity": "sha1-UVu/qZaJOYEULZCx+bFjXeKZUQk=", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + } + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "requires": { + "babel-core": "6.26.0", + "babel-runtime": "6.26.0", + "core-js": "2.5.3", + "home-or-tmp": "2.0.0", + "lodash": "4.17.5", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" + }, + "dependencies": { + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" + }, + "dependencies": { + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + } + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.5" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.5" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.5", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "base64-js": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", + "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==" + }, + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz", + "integrity": "sha1-GZ/WYXAqDnt9yubgaYuwicUvbXg=" + }, + "basic-auth": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" + }, + "basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz", + "integrity": "sha1-/bC0OWLKe0BFanwrtI/hc9otISI=" + }, + "batch": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz", + "integrity": "sha1-PzQU84AyF0O/wQQvmoP/HVgk1GQ=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=" + }, + "big-integer": { + "version": "1.6.27", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.27.tgz", + "integrity": "sha512-NzUKMYW4SWme+H5K+mfEmBxEF/V04PhlzoxxXwSnDig78y2t7HLBVotfDBMUhRPRA3WWID3GmJB/OJSWPhVXtg==" + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true, + "optional": true + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "body-parser": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.3.tgz", + "integrity": "sha1-wIzzMMM1jhUQFqBXRvE/ApyX+pc=", + "requires": { + "bytes": "2.1.0", + "content-type": "1.0.4", + "debug": "2.2.0", + "depd": "1.0.1", + "http-errors": "1.3.1", + "iconv-lite": "0.4.11", + "on-finished": "2.3.0", + "qs": "4.0.0", + "raw-body": "2.1.7", + "type-is": "1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "iconv-lite": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.1" + } + }, + "bplist-creator": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", + "integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=", + "requires": { + "stream-buffers": "2.2.0" + } + }, + "bplist-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", + "integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=", + "requires": { + "big-integer": "1.6.27" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "requires": { + "node-int64": "0.4.0" + } + }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, + "child-process-promise": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz", + "integrity": "sha1-RzChHvYQ+tRQuPIjx50x172tgHQ=", + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "node-version": "1.1.3", + "promise-polyfill": "6.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "4.1.2", + "which": "1.3.0" + } + } + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "optional": true, + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.1.3", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + }, + "dependencies": { + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + } + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "clipboardy": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", + "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", + "requires": { + "arch": "2.1.0", + "execa": "0.8.0" + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "compressible": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", + "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", + "requires": { + "mime-db": "1.33.0" + } + }, + "compression": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/compression/-/compression-1.5.2.tgz", + "integrity": "sha1-sDuNhub4rSloPLqN+R3cb/x3s5U=", + "requires": { + "accepts": "1.2.13", + "bytes": "2.1.0", + "compressible": "2.0.13", + "debug": "2.2.0", + "on-headers": "1.0.1", + "vary": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.5", + "typedarray": "0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "connect": { + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/connect/-/connect-2.30.2.tgz", + "integrity": "sha1-jam8vooFTT0xjXTf7JA7XDmhtgk=", + "requires": { + "basic-auth-connect": "1.0.0", + "body-parser": "1.13.3", + "bytes": "2.1.0", + "compression": "1.5.2", + "connect-timeout": "1.6.2", + "content-type": "1.0.4", + "cookie": "0.1.3", + "cookie-parser": "1.3.5", + "cookie-signature": "1.0.6", + "csurf": "1.8.3", + "debug": "2.2.0", + "depd": "1.0.1", + "errorhandler": "1.4.3", + "express-session": "1.11.3", + "finalhandler": "0.4.0", + "fresh": "0.3.0", + "http-errors": "1.3.1", + "method-override": "2.3.10", + "morgan": "1.6.1", + "multiparty": "3.3.2", + "on-headers": "1.0.1", + "parseurl": "1.3.2", + "pause": "0.1.0", + "qs": "4.0.0", + "response-time": "2.3.2", + "serve-favicon": "2.3.2", + "serve-index": "1.7.3", + "serve-static": "1.10.3", + "type-is": "1.6.16", + "utils-merge": "1.0.0", + "vhost": "3.0.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "connect-timeout": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.6.2.tgz", + "integrity": "sha1-3ppexh4zoStu2qt7XwYumMWZuI4=", + "requires": { + "debug": "2.2.0", + "http-errors": "1.3.1", + "ms": "0.7.1", + "on-headers": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" + }, + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" + }, + "cookie-parser": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", + "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz", + "integrity": "sha1-+mIuG8OIvyVzCQgta2UgDOZwkLo=" + }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.2", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "csrf": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz", + "integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.5", + "uid-safe": "2.1.4" + } + }, + "csurf": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", + "integrity": "sha1-I/KhO/HY/OHQyZZYg5RELLqGpWo=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "csrf": "3.0.6", + "http-errors": "1.3.1" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.41" + } + }, + "damerau-levenshtein": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", + "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=" + }, + "depd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", + "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "requires": { + "repeating": "2.0.1" + } + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + }, + "detox": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/detox/-/detox-7.2.0.tgz", + "integrity": "sha512-hj+GgnXJWmNit0lzeDY1KjZZv3nvNWsB3V6oGIvwCe3Jdl2JXqg6Qn2USa137loTpuvdR77Zgl5m3u1iG1Am/Q==", + "dev": true, + "requires": { + "child-process-promise": "2.2.1", + "commander": "2.15.1", + "detox-server": "7.0.0", + "fs-extra": "4.0.3", + "get-port": "2.1.0", + "ini": "1.3.5", + "lodash": "4.17.5", + "npmlog": "4.1.2", + "shell-utils": "1.0.9", + "tail": "1.2.3", + "telnet-client": "0.15.3", + "ws": "1.1.5" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "detox-server": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/detox-server/-/detox-server-7.0.0.tgz", + "integrity": "sha512-zs9ZP/MgeEmaZD/+MCl5PVcYHRjUtFBkBx3xQRPcsjJ/PmpCKy/BvygjLO6tRsR/2SC9UYay6W+BdguEYeft8g==", + "dev": true, + "requires": { + "lodash": "4.17.5", + "npmlog": "4.1.2", + "ws": "1.1.5" + }, + "dependencies": { + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.0.2" + } + }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "requires": { + "readable-stream": "1.1.14" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "emoji-regex": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", + "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "0.4.19" + } + }, + "envinfo": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-3.11.1.tgz", + "integrity": "sha512-hKkh7aKtont6Zuv4RmE4VkOc96TkBj9NXj7Ghsd/qCA9LuJI0Dh+ImwA1N5iORB9Vg+sz5bq9CHJzs51BILNCQ==", + "requires": { + "clipboardy": "1.2.3", + "glob": "7.1.2", + "minimist": "1.2.0", + "os-name": "2.0.1", + "which": "1.3.0" + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "requires": { + "is-arrayish": "0.2.1" + } + }, + "errorhandler": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", + "integrity": "sha1-t7cO2PNZ6duICS8tIMD4MUIK2D8=", + "requires": { + "accepts": "1.3.5", + "escape-html": "1.0.3" + }, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.6.1" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + } + } + }, + "es-abstract": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", + "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", + "dev": true, + "requires": { + "es-to-primitive": "1.1.1", + "function-bind": "1.1.1", + "has": "1.0.1", + "is-callable": "1.1.3", + "is-regex": "1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "1.1.3", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" + } + }, + "es5-ext": { + "version": "0.10.41", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.41.tgz", + "integrity": "sha512-MYK02wXfwTMie5TEJWPolgOsXEmz7wKCQaGzgmRjZOoV6VLG8I5dSv2bn6AOClXhK64gnSQTQ9W9MKvx87J4gw==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.41", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.41", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.41", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.41" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.41", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "chalk": "1.1.3", + "concat-stream": "1.6.2", + "debug": "2.6.9", + "doctrine": "2.1.0", + "escope": "3.6.0", + "espree": "3.5.4", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.17.2", + "is-resolvable": "1.1.0", + "js-yaml": "3.11.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.5", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.7.8", + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.17.5", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + } + } + }, + "eslint-config-airbnb": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-14.1.0.tgz", + "integrity": "sha1-NV0pAEC7+OAL+LSxn0twy+fCMX8=", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "11.3.2" + } + }, + "eslint-config-airbnb-base": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.3.2.tgz", + "integrity": "sha512-/fhjt/VqzBA2SRsx7ErDtv6Ayf+XLw9LIOqmpBuHFCVwyJo2EtzGWMB9fYRFBoWWQLxmNmCpenNiH0RxyeS41w==", + "dev": true, + "requires": { + "eslint-restricted-globals": "0.1.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "2.6.9", + "resolve": "1.6.0" + } + }, + "eslint-module-utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", + "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "pkg-dir": "1.0.0" + } + }, + "eslint-plugin-flowtype": { + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.46.1.tgz", + "integrity": "sha512-GJzxW7QwiIiW0ZA/+nY+N5TuK3es4Uei0D4xuy14dLZBYEFhM6e7c0J1u4+/iwfPqFtkr5a0oSApnSKF4U6KHw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "eslint-plugin-import": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.9.0.tgz", + "integrity": "sha1-JgAu+/ylmJtyiKwEdQi9JPIXsWk=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1", + "contains-path": "0.1.0", + "debug": "2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.3.2", + "eslint-module-utils": "2.1.1", + "has": "1.0.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "read-pkg-up": "2.0.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-4.0.0.tgz", + "integrity": "sha1-d5uw/nsI2lZKQiYkkR3hAGHgSO4=", + "dev": true, + "requires": { + "aria-query": "0.3.0", + "ast-types-flow": "0.0.7", + "damerau-levenshtein": "1.0.4", + "emoji-regex": "6.5.1", + "jsx-ast-utils": "1.4.1", + "object-assign": "4.1.1" + } + }, + "eslint-plugin-react": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", + "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", + "dev": true, + "requires": { + "array.prototype.find": "2.0.4", + "doctrine": "1.5.0", + "has": "1.0.1", + "jsx-ast-utils": "1.4.1", + "object.assign": "4.1.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "5.5.3", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "etag": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", + "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.41" + } + }, + "event-target-shim": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz", + "integrity": "sha1-qG5e5r2qFgVEddp5fM3fDFVphJE=" + }, + "eventemitter3": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.0.1.tgz", + "integrity": "sha512-QOCPu979MMWX9XNlfRZoin+Wm+bK1SP7vv3NGUniYwuSJK/+cPA10blMaeRgzg31RvoSFk6FsCDVa4vNryBTGA==" + }, + "exec-sh": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.1.tgz", + "integrity": "sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg==", + "requires": { + "merge": "1.2.0" + } + }, + "execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "2.2.3" + } + }, + "express-session": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz", + "integrity": "sha1-XMmPP1/4Ttg1+Ry/CqvQxxB0AK8=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "crc": "3.3.0", + "debug": "2.2.0", + "depd": "1.0.1", + "on-headers": "1.0.1", + "parseurl": "1.3.2", + "uid-safe": "2.0.0", + "utils-merge": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "uid-safe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz", + "integrity": "sha1-p/PGymSh9qXQTsDvPkw9U2cxcTc=", + "requires": { + "base64-url": "1.2.1" + } + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", + "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.19", + "tmp": "0.0.33" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "requires": { + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "requires": { + "bser": "2.0.0" + } + }, + "fbjs": { + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.17" + } + }, + "fbjs-scripts": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-0.8.1.tgz", + "integrity": "sha512-hTjqlua9YJupF8shbVRTq20xKPITnDmqBLBQyR9BttZYT+gxGeKboIzPC19T3Erp29Q0+jdMwjUiyTHR61q1Bw==", + "requires": { + "babel-core": "6.26.0", + "babel-preset-fbjs": "2.1.4", + "core-js": "2.5.3", + "cross-spawn": "5.1.0", + "gulp-util": "3.0.8", + "object-assign": "4.1.1", + "semver": "5.5.0", + "through2": "2.0.3" + }, + "dependencies": { + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + } + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "finalhandler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", + "requires": { + "debug": "2.2.0", + "escape-html": "1.0.2", + "on-finished": "2.3.0", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "escape-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", + "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "2.0.0" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "1.0.2" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "0.2.2" + } + }, + "fresh": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" + }, + "fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "2.4.0", + "klaw": "1.3.1" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "optional": true, + "requires": { + "nan": "2.10.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", + "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", + "requires": { + "ansi": "0.3.1", + "has-unicode": "2.0.1", + "lodash.pad": "4.5.1", + "lodash.padend": "4.6.1", + "lodash.padstart": "4.6.1" + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + }, + "get-port": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-2.1.0.tgz", + "integrity": "sha1-h4P53OvR7qSVozThpqJR54iHqxo=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "2.0.1" + } + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "2.19.0", + "process": "0.5.2" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "glogg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", + "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", + "requires": { + "sparkles": "1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "requires": { + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl": "0.5.3" + }, + "dependencies": { + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "requires": { + "glogg": "1.0.1" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "requires": { + "sparkles": "1.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "hosted-git-info": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "2.0.3", + "statuses": "1.4.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "dev": true + }, + "image-size": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.2.tgz", + "integrity": "sha512-pH3vDzpczdsKHdZ9xxR3O46unSjisgVx0IImay7Zz2EdhRVbCkj+nthx9OuuWEhakx9FAO+fNVGrF0rZ2oMOvw==" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.3.2", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.1.0", + "figures": "2.0.0", + "lodash": "4.17.5", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "1.11.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "3.2.2" + } + }, + "is-odd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "requires": { + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } + } + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "1.7.3", + "whatwg-fetch": "2.0.3" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", + "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", + "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "dev": true, + "requires": { + "babel-generator": "6.26.1", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.2.0", + "semver": "5.5.0" + } + }, + "jest-docblock": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-22.1.0.tgz", + "integrity": "sha512-/+OGgBVRJb5wCbXrB1LQvibQBz2SdrvDdKRNzY1gL+OISQJZCR9MOewbygdT5rVzbbkfhC4AR2x+qWmNUdJfjw==", + "requires": { + "detect-newline": "2.1.0" + } + }, + "jest-haste-map": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-22.1.0.tgz", + "integrity": "sha512-vETdC6GboGlZX6+9SMZkXtYRQSKBbQ47sFF7NGglbMN4eyIZBODply8rlcO01KwBiAeiNCKdjUyfonZzJ93JEg==", + "requires": { + "fb-watchman": "2.0.0", + "graceful-fs": "4.1.11", + "jest-docblock": "22.1.0", + "jest-worker": "22.1.0", + "micromatch": "2.3.11", + "sane": "2.5.0" + } + }, + "jest-worker": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-22.1.0.tgz", + "integrity": "sha512-ezLueYAQowk5N6g2J7bNZfq4NWZvMNB5Qd24EmOZLcM5SXTdiFvxykZIoNiMj9C98cCbPaojX8tfR7b1LJwNig==", + "requires": { + "merge-stream": "1.0.1" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "4.1.11" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", + "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "requires": { + "graceful-fs": "4.1.11" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "1.0.0" + } + }, + "left-pad": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz", + "integrity": "sha1-0wpzxrggHY99jnlWupYWCHpo4O4=" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=" + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=" + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "requires": { + "lodash._root": "3.0.1" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.pad": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", + "integrity": "sha1-QzCUmoM6fI2iLMIPaibE1Z3runA=" + }, + "lodash.padend": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", + "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" + }, + "lodash.padstart": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", + "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "requires": { + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" + } + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", + "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "macos-release": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-1.1.0.tgz", + "integrity": "sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA==" + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "requires": { + "tmpl": "1.0.4" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "1.0.1" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "requires": { + "mimic-fn": "1.2.0" + } + }, + "merge": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz", + "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=" + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "requires": { + "readable-stream": "2.3.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "method-override": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", + "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", + "requires": { + "debug": "2.6.9", + "methods": "1.1.2", + "parseurl": "1.3.2", + "vary": "1.1.2" + }, + "dependencies": { + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "metro": { + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.24.7.tgz", + "integrity": "sha512-9Fr3PDPPCTR3WJUHPLZL2nvyEWyvqyyxH9649OmA2TOF7VEtRzWedZlc6PAcl/rDOzwDOu2/c98NRFxnS1CYlw==", + "requires": { + "absolute-path": "0.0.0", + "async": "2.6.0", + "babel-core": "6.26.0", + "babel-generator": "6.26.1", + "babel-plugin-external-helpers": "6.22.0", + "babel-preset-es2015-node": "6.1.1", + "babel-preset-fbjs": "2.1.4", + "babel-preset-react-native": "4.0.0", + "babel-register": "6.26.0", + "babylon": "6.18.0", + "chalk": "1.1.3", + "concat-stream": "1.6.2", + "connect": "3.6.6", + "core-js": "2.5.3", + "debug": "2.6.9", + "denodeify": "1.2.1", + "eventemitter3": "3.0.1", + "fbjs": "0.8.16", + "fs-extra": "1.0.0", + "graceful-fs": "4.1.11", + "image-size": "0.6.2", + "jest-docblock": "22.1.0", + "jest-haste-map": "22.1.0", + "jest-worker": "22.1.0", + "json-stable-stringify": "1.0.1", + "json5": "0.4.0", + "left-pad": "1.2.0", + "lodash.throttle": "4.1.1", + "merge-stream": "1.0.1", + "metro-core": "0.24.7", + "metro-source-map": "0.24.7", + "mime-types": "2.1.11", + "mkdirp": "0.5.1", + "request": "2.85.0", + "rimraf": "2.6.2", + "serialize-error": "2.1.0", + "source-map": "0.5.7", + "temp": "0.8.3", + "throat": "4.1.0", + "uglify-es": "3.3.9", + "wordwrap": "1.0.0", + "write-file-atomic": "1.3.4", + "ws": "1.1.5", + "xpipe": "1.0.5", + "yargs": "9.0.1" + }, + "dependencies": { + "babel-preset-react-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-native/-/babel-preset-react-native-4.0.0.tgz", + "integrity": "sha512-Wfbo6x244nUbBxjr7hQaNFdjj7FDYU+TVT7cFVPEdVPI68vhN52iLvamm+ErhNdHq6M4j1cMT6AJBYx7Wzdr0g==", + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-react-transform": "3.0.0", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-syntax-flow": "6.18.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-flow-strip-types": "6.22.0", + "babel-plugin-transform-object-assign": "6.22.0", + "babel-plugin-transform-object-rest-spread": "6.26.0", + "babel-plugin-transform-react-display-name": "6.25.0", + "babel-plugin-transform-react-jsx": "6.24.1", + "babel-plugin-transform-react-jsx-source": "6.22.0", + "babel-plugin-transform-regenerator": "6.26.0", + "babel-template": "6.26.0", + "react-transform-hmr": "1.0.4" + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "1.3.2", + "utils-merge": "1.0.1" + } + }, + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + } + }, + "json5": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", + "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=" + }, + "mime-db": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz", + "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk=" + }, + "mime-types": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz", + "integrity": "sha1-wlnEcb2oCKhdbNGTtDCl+uRHOzw=", + "requires": { + "mime-db": "1.23.0" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + } + } + }, + "metro-core": { + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.24.7.tgz", + "integrity": "sha512-Qheab9Wmc8T2m3Ax9COyKUk8LxRb1fHWe13CpoEgPIjwFBd6ILNXaq7ZzoWg0OoAbpMsNzvUOnOJNHvfRuJqJg==", + "requires": { + "lodash.throttle": "4.1.1" + } + }, + "metro-source-map": { + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.24.7.tgz", + "integrity": "sha512-12WEgolY5CGvHeHkF5QlM2qatdQC1DyjWkXLK9LzCqzd8YhUZww1+ZCM6E67rJwpeuCU9o1Mkiwd1h7dS+RBvA==", + "requires": { + "source-map": "0.5.7" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "0.1.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mocha": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "dependencies": { + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "morgan": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", + "requires": { + "basic-auth": "1.0.4", + "debug": "2.2.0", + "depd": "1.0.1", + "on-finished": "2.3.0", + "on-headers": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multiparty": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", + "integrity": "sha1-Nd5oBNwZZD5SSfPT473GyM4wHT8=", + "requires": { + "readable-stream": "1.1.14", + "stream-counter": "0.2.0" + } + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "requires": { + "duplexer2": "0.0.2" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "optional": true + }, + "nanomatch": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", + "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "node-notifier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", + "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", + "requires": { + "growly": "1.3.0", + "semver": "5.5.0", + "shellwords": "0.1.1", + "which": "1.3.0" + } + }, + "node-version": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.1.3.tgz", + "integrity": "sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==", + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "requires": { + "hosted-git-info": "2.6.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "2.0.1" + } + }, + "npmlog": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.4.tgz", + "integrity": "sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI=", + "requires": { + "ansi": "0.3.1", + "are-we-there-yet": "1.1.4", + "gauge": "1.2.7" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + } + } + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.0.11" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "1.2.0" + } + }, + "opn": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/opn/-/opn-3.0.3.tgz", + "integrity": "sha1-ttmec5n3jWXDuq/+8fsojpuFJDo=", + "requires": { + "object-assign": "4.1.1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "0.0.10", + "wordwrap": "0.0.3" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + }, + "dependencies": { + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + } + } + }, + "os-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-2.0.1.tgz", + "integrity": "sha1-uaOGNhwXrjohc27wWZQFyajF3F4=", + "requires": { + "macos-release": "1.1.0", + "win-release": "1.1.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "output-file-sync": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", + "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "1.3.1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "2.3.0" + } + }, + "pause": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.1.0.tgz", + "integrity": "sha1-68ikqGGf8LioGsFRPDQ0/0af23Q=" + }, + "pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + } + } + }, + "plist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", + "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=", + "requires": { + "base64-js": "0.0.8", + "util-deprecate": "1.0.2", + "xmlbuilder": "4.0.0", + "xmldom": "0.1.27" + }, + "dependencies": { + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=" + } + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "pretty-format": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-4.3.1.tgz", + "integrity": "sha1-UwvlxCs8BbNkFKeipDN6qArNDo0=" + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "2.0.6" + } + }, + "promise-polyfill": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", + "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=", + "dev": true + }, + "prop-types": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", + "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", + "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "range-parser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", + "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=" + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" + } + } + }, + "react": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.2.0.tgz", + "integrity": "sha512-ZmIomM7EE1DvPEnSFAHZn9Vs9zJl5A9H7el0EGTE6ZbW9FKe/14IYAlPbC8iH25YarEQxZL+E8VW7Mi7kfQrDQ==", + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.1" + } + }, + "react-clone-referenced-element": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/react-clone-referenced-element/-/react-clone-referenced-element-1.0.1.tgz", + "integrity": "sha1-K7qMaUBMXkqUQ5hgC8xMlB+GBoI=" + }, + "react-deep-force-update": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz", + "integrity": "sha1-vNMUeAJ7ZLMznxCJIatSC0MT3Cw=" + }, + "react-devtools-core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-3.0.0.tgz", + "integrity": "sha512-24oLTwNqZJceQXfAfKRp3PwCyg2agXAQhgGwe/x6V6CvjLmnMmba4/ut9S8JTIJq7pS9fpPaRDGo5u3923RLFA==", + "requires": { + "shell-quote": "1.6.1", + "ws": "2.3.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "ws": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz", + "integrity": "sha1-a5Sz5EfLajY/eF6vlK9jWejoHIA=", + "requires": { + "safe-buffer": "5.0.1", + "ultron": "1.1.1" + } + } + } + }, + "react-native": { + "version": "0.52.3", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.52.3.tgz", + "integrity": "sha512-0GFnmxtkUk0KRkB61JTBv5pkj1JsnKxa0jH8vtM0wa+WSKHLuRCDmEERtk09KOMgJ+esKZO+ZO30nhHup+ZPYg==", + "requires": { + "absolute-path": "0.0.0", + "art": "0.10.1", + "babel-core": "6.26.0", + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-to-generator": "6.16.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-flow-strip-types": "6.22.0", + "babel-plugin-transform-object-rest-spread": "6.26.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "base64-js": "1.2.3", + "chalk": "1.1.3", + "commander": "2.15.1", + "connect": "2.30.2", + "create-react-class": "15.6.3", + "debug": "2.6.9", + "denodeify": "1.2.1", + "envinfo": "3.11.1", + "event-target-shim": "1.1.1", + "fbjs": "0.8.16", + "fbjs-scripts": "0.8.1", + "fs-extra": "1.0.0", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "inquirer": "3.3.0", + "lodash": "4.17.5", + "metro": "0.24.7", + "metro-core": "0.24.7", + "mime": "1.6.0", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "node-fetch": "1.7.3", + "node-notifier": "5.2.1", + "npmlog": "2.0.4", + "opn": "3.0.3", + "optimist": "0.6.1", + "plist": "1.2.0", + "pretty-format": "4.3.1", + "promise": "7.3.1", + "prop-types": "15.6.1", + "react-clone-referenced-element": "1.0.1", + "react-devtools-core": "3.0.0", + "react-timer-mixin": "0.13.3", + "regenerator-runtime": "0.11.1", + "rimraf": "2.6.2", + "semver": "5.5.0", + "shell-quote": "1.6.1", + "stacktrace-parser": "0.1.4", + "whatwg-fetch": "1.1.1", + "ws": "1.1.5", + "xcode": "0.9.3", + "xmldoc": "0.4.0", + "yargs": "9.0.1" + }, + "dependencies": { + "whatwg-fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz", + "integrity": "sha1-rDydOfMgxtzlM5lp0FTvQ90zMxk=" + } + } + }, + "react-proxy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz", + "integrity": "sha1-nb/Z2SdSjDqp9ETkVYw3gwq4wmo=", + "requires": { + "lodash": "4.17.5", + "react-deep-force-update": "1.1.1" + } + }, + "react-timer-mixin": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/react-timer-mixin/-/react-timer-mixin-0.13.3.tgz", + "integrity": "sha1-Dai5+AfsB9w+hU0ILHN8ZWBbPSI=" + }, + "react-transform-hmr": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz", + "integrity": "sha1-4aQL0Krvxy6N/Xp82gmvhQZjl7s=", + "requires": { + "global": "4.3.2", + "react-proxy": "1.1.8" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.5", + "set-immediate-shim": "1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "1.6.0" + } + }, + "regenerate": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "private": "0.1.8" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "requires": { + "regenerate": "1.3.3", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "requires": { + "jsesc": "0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "1.0.2" + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + }, + "request": { + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + }, + "dependencies": { + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.6.0.tgz", + "integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "response-time": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", + "integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=", + "requires": { + "depd": "1.1.2", + "on-headers": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + } + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "0.1.15" + } + }, + "sane": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.0.tgz", + "integrity": "sha512-glfKd7YH4UCrh/7dD+UESsr8ylKWRE7UQPoXuz28FgmcF0ViJQhCTCCZHICRKxf8G8O1KdLEn20dcICK54c7ew==", + "requires": { + "anymatch": "2.0.0", + "exec-sh": "0.2.1", + "fb-watchman": "2.0.0", + "fsevents": "1.1.3", + "micromatch": "3.1.10", + "minimist": "1.2.0", + "walker": "1.0.7", + "watch": "0.18.0" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", + "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "kind-of": "6.0.2", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "sax": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.6.tgz", + "integrity": "sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA=" + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "send": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", + "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=", + "requires": { + "debug": "2.2.0", + "depd": "1.1.2", + "destroy": "1.0.4", + "escape-html": "1.0.3", + "etag": "1.7.0", + "fresh": "0.3.0", + "http-errors": "1.3.1", + "mime": "1.3.4", + "ms": "0.7.1", + "on-finished": "2.3.0", + "range-parser": "1.0.3", + "statuses": "1.2.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", + "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" + } + } + }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=" + }, + "serve-favicon": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.3.2.tgz", + "integrity": "sha1-3UGeJo3gEqtysxnTN/IQUBP5OB8=", + "requires": { + "etag": "1.7.0", + "fresh": "0.3.0", + "ms": "0.7.2", + "parseurl": "1.3.2" + }, + "dependencies": { + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + } + } + }, + "serve-index": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.7.3.tgz", + "integrity": "sha1-egV/xu4o3GP2RWbl+lexEahq7NI=", + "requires": { + "accepts": "1.2.13", + "batch": "0.5.3", + "debug": "2.2.0", + "escape-html": "1.0.3", + "http-errors": "1.3.1", + "mime-types": "2.1.18", + "parseurl": "1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "serve-static": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", + "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", + "requires": { + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.13.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "optional": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "requires": { + "array-filter": "0.0.1", + "array-map": "0.0.0", + "array-reduce": "0.0.0", + "jsonify": "0.0.0" + } + }, + "shell-utils": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/shell-utils/-/shell-utils-1.0.9.tgz", + "integrity": "sha512-JbTHnKpMyj9TUUbL+Us2Rx2iVHFvH5QyQoke9SN1L0pueiZeO2Gzlzopmloi7oqObL4qtvdSuZPE3UfdIzmlag==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.1.0", + "rechoir": "0.6.2" + } + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-plist": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.2.1.tgz", + "integrity": "sha1-cXZts1IyaSjPOoByQrp2IyJjZyM=", + "requires": { + "bplist-creator": "0.0.7", + "bplist-parser": "0.1.1", + "plist": "2.0.1" + }, + "dependencies": { + "base64-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.1.2.tgz", + "integrity": "sha1-1kAMrBxMZgl22Q0HoENR2JOV9eg=" + }, + "plist": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-2.0.1.tgz", + "integrity": "sha1-CjLKlIGxw2TpLhjcVch23p0B2os=", + "requires": { + "base64-js": "1.1.2", + "xmlbuilder": "8.2.2", + "xmldom": "0.1.27" + } + }, + "xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=" + } + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "3.2.2" + } + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", + "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "requires": { + "atob": "2.0.3", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "requires": { + "source-map": "0.5.7" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=" + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "3.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "stacktrace-parser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.4.tgz", + "integrity": "sha1-ATl5IuX2Ls8whFUiyVxP4dJefU4=" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" + }, + "stream-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz", + "integrity": "sha1-3tJmVWMZyLDiIoErnPOyb6fZR94=", + "requires": { + "readable-stream": "1.1.14" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.5", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + } + } + }, + "tail": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/tail/-/tail-1.2.3.tgz", + "integrity": "sha1-sI1vp5+5KIaWMaNBpRwUSXwcQlU=", + "dev": true + }, + "telnet-client": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-0.15.3.tgz", + "integrity": "sha512-GSfdzQV0BKIYsmeXq7bJFJ2wHeJud6icaIxCUf6QCGQUD6R0BBGbT1+yLDhq67JRdgRpwyPwUbV7JxFeRrZomQ==", + "dev": true, + "requires": { + "bluebird": "3.5.1" + } + }, + "temp": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "requires": { + "os-tmpdir": "1.0.2", + "rimraf": "2.2.8" + }, + "dependencies": { + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" + } + } + }, + "test-exclude": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", + "integrity": "sha512-qpqlP/8Zl+sosLxBcVKl9vYy26T9NPalxSzzCP/OY6K7j938ui2oKgo+kRZYfxAeIpLqpbVnsHq1tyV70E4lWQ==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "3.1.10", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", + "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "kind-of": "6.0.2", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "requires": { + "readable-stream": "2.3.5", + "xtend": "4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "3.2.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + } + } + } + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "1.4.1" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "tsscmp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", + "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "ua-parser-js": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", + "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "requires": { + "commander": "2.13.0", + "source-map": "0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "uid-safe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", + "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=", + "requires": { + "random-bytes": "1.0.0" + } + }, + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "1.1.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "requires": { + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" + } + }, + "vary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", + "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "vhost": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.2.tgz", + "integrity": "sha1-L7HezUxGaqiLD5NBrzPcGv8keNU=" + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "requires": { + "clone": "1.0.4", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "requires": { + "makeerror": "1.0.11" + } + }, + "watch": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", + "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", + "requires": { + "exec-sh": "0.2.1", + "minimist": "1.2.0" + } + }, + "whatwg-fetch": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", + "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "requires": { + "string-width": "1.0.2" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "win-release": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", + "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=", + "requires": { + "semver": "5.5.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": "0.0.6", + "ultron": "1.0.2" + } + }, + "xcode": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/xcode/-/xcode-0.9.3.tgz", + "integrity": "sha1-kQqJwWrubMC0LKgFptC0z4chHPM=", + "requires": { + "pegjs": "0.10.0", + "simple-plist": "0.2.1", + "uuid": "3.0.1" + }, + "dependencies": { + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + } + } + }, + "xmlbuilder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz", + "integrity": "sha1-mLj2UcowqmJANvEn0RzGbce5B6M=", + "requires": { + "lodash": "3.10.1" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } + } + }, + "xmldoc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-0.4.0.tgz", + "integrity": "sha1-0lciS+g5PqrL+DfvIn/Y7CWzaIg=", + "requires": { + "sax": "1.1.6" + } + }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" + }, + "xpipe": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/xpipe/-/xpipe-1.0.5.tgz", + "integrity": "sha1-jdi/Rfw/f1Xw4FS4ePQ6YmFNr98=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "requires": { + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "requires": { + "camelcase": "4.1.0" + } + } + } +} diff --git a/tests-new/package.json b/tests-new/package.json index ae7a911a..2d3e4cb4 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -3,7 +3,8 @@ "version": "7.2.0", "private": true, "scripts": { - "start": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android", + "start": "REACT_DEBUGGER='todo' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow", + "start-ci": "REACT_DEBUGGER='todo' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow --nonPersistent", "android:dev": "react-native run-android", "android:prod": "react-native run-android --configuration=release", "ios:dev": "react-native run-ios", @@ -51,13 +52,13 @@ "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk", "build": "pushd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && popd", "type": "android.emulator", - "name": "Nexus_5X_API_24_-_GPlay" + "name": "TestingAVD" }, "android.emu.release": { "binaryPath": "android/app/build/outputs/apk/release/app-release.apk", "build": "pushd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && popd", "type": "android.emulator", - "name": "Nexus_5X_API_24_-_GPlay" + "name": "TestingAVD" } } } From 82889e45965b594b4ddb2652144c6779f82870ce Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 23 Mar 2018 22:24:44 +0000 Subject: [PATCH 07/40] misc From 9eeddcab6e9692fbcdbe16dd8c4c89738d67f0f7 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 02:02:59 +0000 Subject: [PATCH 08/40] ignore --- tests-new/android/app/build.gradle | 6 +- .../main/java/com/testing/MainActivity.java | 2 +- .../java/com/testing/MainApplication.java | 8 +- tests-new/android/settings.gradle | 4 + tests-new/app.js | 22 +-- tests-new/bridge/env/node/index.js | 61 +++++++ tests-new/bridge/env/node/vm.js | 158 ++++++++++++++++++ tests-new/bridge/env/node/ws.js | 12 ++ tests-new/bridge/env/rn.js | 28 ++++ tests-new/e2e/bridge.spec.js | 60 +++++++ tests-new/e2e/example.spec.js | 31 +++- tests-new/e2e/mocha.opts | 3 +- tests-new/index.js | 2 +- tests-new/package-lock.json | 126 +++++++------- tests-new/package.json | 9 +- 15 files changed, 447 insertions(+), 85 deletions(-) create mode 100644 tests-new/bridge/env/node/index.js create mode 100644 tests-new/bridge/env/node/vm.js create mode 100644 tests-new/bridge/env/node/ws.js create mode 100644 tests-new/bridge/env/rn.js create mode 100755 tests-new/e2e/bridge.spec.js diff --git a/tests-new/android/app/build.gradle b/tests-new/android/app/build.gradle index 2f7f3bab..fce8183a 100755 --- a/tests-new/android/app/build.gradle +++ b/tests-new/android/app/build.gradle @@ -87,9 +87,13 @@ dependencies { transitive = false } + implementation(project(':react-native-restart')) { + transitive = false + } + compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.google.android.gms:play-services-base:$firebaseVersion" -// compile "com.google.firebase:firebase-ads:$firebaseVersion" + compile "com.google.firebase:firebase-ads:$firebaseVersion" compile "com.google.firebase:firebase-auth:$firebaseVersion" compile "com.google.firebase:firebase-config:$firebaseVersion" compile "com.google.firebase:firebase-core:$firebaseVersion" diff --git a/tests-new/android/app/src/main/java/com/testing/MainActivity.java b/tests-new/android/app/src/main/java/com/testing/MainActivity.java index 50575705..286ab301 100755 --- a/tests-new/android/app/src/main/java/com/testing/MainActivity.java +++ b/tests-new/android/app/src/main/java/com/testing/MainActivity.java @@ -31,7 +31,7 @@ public class MainActivity extends ReactActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); -// checkWindowPerms(); + checkWindowPerms(); } public void checkWindowPerms() { diff --git a/tests-new/android/app/src/main/java/com/testing/MainApplication.java b/tests-new/android/app/src/main/java/com/testing/MainApplication.java index 0039f9d0..653ceee8 100755 --- a/tests-new/android/app/src/main/java/com/testing/MainApplication.java +++ b/tests-new/android/app/src/main/java/com/testing/MainApplication.java @@ -9,7 +9,8 @@ import com.facebook.react.shell.MainReactPackage; import com.facebook.soloader.SoLoader; import io.invertase.firebase.RNFirebasePackage; -//import io.invertase.firebase.admob.RNFirebaseAdMobPackage; +import com.avishayil.rnrestart.ReactNativeRestartPackage; +import io.invertase.firebase.admob.RNFirebaseAdMobPackage; import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage; import io.invertase.firebase.auth.RNFirebaseAuthPackage; import io.invertase.firebase.config.RNFirebaseRemoteConfigPackage; @@ -25,6 +26,7 @@ import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage; import io.invertase.firebase.perf.RNFirebasePerformancePackage; import io.invertase.firebase.storage.RNFirebaseStoragePackage; + import java.util.Arrays; import java.util.List; @@ -39,8 +41,9 @@ public class MainApplication extends Application implements ReactApplication { protected List getPackages() { return Arrays.asList( new MainReactPackage(), + new ReactNativeRestartPackage(), new RNFirebasePackage(), -// new RNFirebaseAdMobPackage(), + new RNFirebaseAdMobPackage(), new RNFirebaseAnalyticsPackage(), new RNFirebaseAuthPackage(), new RNFirebaseRemoteConfigPackage(), @@ -67,6 +70,7 @@ public class MainApplication extends Application implements ReactApplication { @Override public void onCreate() { super.onCreate(); + getReactNativeHost().getReactInstanceManager().getDevSupportManager().getDevSettings().setRemoteJSDebugEnabled(true); SoLoader.init(this, /* native exopackage */ false); } diff --git a/tests-new/android/settings.gradle b/tests-new/android/settings.gradle index 2b21f7b0..3a8b98ad 100755 --- a/tests-new/android/settings.gradle +++ b/tests-new/android/settings.gradle @@ -1,7 +1,11 @@ rootProject.name = 'RNFTests' + include ':react-native-firebase' project(':react-native-firebase').projectDir = new File(rootProject.projectDir, './../../android') +include ':react-native-restart' +project(':react-native-restart').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-restart/android') + include ':app' include ':detox' diff --git a/tests-new/app.js b/tests-new/app.js index 8bdffe2d..80af3871 100755 --- a/tests-new/app.js +++ b/tests-new/app.js @@ -4,26 +4,20 @@ * @flow */ -import React, { Component } from 'react'; -import rnModule, { AppRegistry, Text, View } from 'react-native'; +// must import before all else +import Bridge from './bridge/env/rn'; -import testModule from './firebase'; +import React, { Component } from 'react'; +import { AppRegistry, Text, View } from 'react-native'; + +import firebase from './firebase'; class Root extends Component { constructor(props) { super(props); this.state = {}; - } - - componentDidMount() { - if (global.__initializeEnvironment) { - console.log('Initializing environment...'); - global.__initializeEnvironment({ - root: this, - rnModule, - testModule, - }); - } + Bridge.provideRoot(this); + Bridge.provideModule(firebase); } render() { diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js new file mode 100644 index 00000000..8a506d01 --- /dev/null +++ b/tests-new/bridge/env/node/index.js @@ -0,0 +1,61 @@ +const detox = require('detox'); +const vm = require('./vm'); +const ws = require('./ws'); + +const detoxOriginalInit = detox.init.bind(detox); +const detoxOriginalCleanup = detox.cleanup.bind(detox); +let detoxOriginalReloadReactNative = null; + +let bridgeReady = false; +process.on('rn-ready', () => { + // console.log('READY', true); + bridgeReady = true; +}); + +function onceBridgeReady() { + if (bridgeReady) return Promise.resolve(); + return new Promise(resolve => { + process.once('rn-ready', resolve); + }); +} + +detox.init = async (...args) => { + bridgeReady = false; + console.log('detox.init.start'); + + return detoxOriginalInit(...args).then(() => { + console.log('detox.init.complete'); + + detoxOriginalReloadReactNative = device.reloadReactNative.bind(device); + device.reloadReactNative = async () => { + console.log('reloadReactNative.start'); + bridgeReady = false; + // return device.launchApp({ newInstance: true }).then(() => { + global.bridge.reload(); + + return onceBridgeReady(); + }; + + return onceBridgeReady(); + }); +}; + +detox.cleanup = async (...args) => { + console.log('detox.cleanup'); + + return detoxOriginalCleanup(...args).then(() => { + console.log('detox.cleanup.end'); + + ws.close(); + process.exit(); + }); +}; + +global.bridge = { + _ws: null, + + rootSetState(state) { + // todo + return Promise.resolve(); + }, +}; diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js new file mode 100644 index 00000000..6185bc08 --- /dev/null +++ b/tests-new/bridge/env/node/vm.js @@ -0,0 +1,158 @@ +const url = require('url'); +const http = require('http'); +const invariant = require('assert'); +const { createContext, Script } = require('vm'); +const ws = require('./ws'); + +let currentContext = null; +let scriptCached = null; + +// this is a dummy file path - without a file name the source map is not used in the vm +const TEMP_BUNDLE_PATH = '/tmp/bridge/react-native.js'; + +/** + * + * @param replyId + * @param result + */ +function sendResult(replyID, result) { + ws.send( + JSON.stringify({ + replyID, + result, + }) + ); +} + +/** + * + * @param message + */ +function sendError(message) { + console.error(message); +} + +/** + * + * @param src + * @param callback + */ +function getScript(src, callback) { + if (scriptCached) return callback(null, scriptCached); + return http + .get(src, res => { + let buff = ''; + + res.setEncoding('utf8'); + + res.on('data', chunk => { + buff += chunk; + }); + + res.on('end', () => { + scriptCached = new Script(buff, { + // lineOffset: -1, + // columnOffset: -1, + timeout: 120000, + displayErrors: true, + produceCachedData: true, + filename: TEMP_BUNDLE_PATH, + }); + + callback(null, scriptCached); + }); + }) + .on('error', err => { + callback(err); + }); +} + +process.on('ws-message', request => { + // console.log(request.method); + switch (request.method) { + case 'prepareJSRuntime': + currentContext = undefined; + currentContext = createContext({ + console, + __bridgeNode: { + ready() { + process.emit('rn-ready'); + }, + provideReactNativeModule(rnModule) { + global.bridge.rn = undefined; + global.bridge.rn = rnModule; + }, + provideModule(moduleExports) { + global.bridge.module = undefined; + global.bridge.module = moduleExports; + }, + provideReload(reloadFn) { + global.bridge.reload = undefined; + global.bridge.reload = reloadFn; + }, + provideRoot(rootComponent) { + global.bridge.root = undefined; + global.bridge.root = rootComponent; + }, + }, + }); + sendResult(request.id); + return; + + case 'executeApplicationScript': + // Modify the URL to make sure we get the inline source map. + const parsedUrl = url.parse(request.url, /* parseQueryString */ true); + invariant(parsedUrl.query); + parsedUrl.query.inlineSourceMap = true; + delete parsedUrl.search; + // $FlowIssue url.format() does not accept what url.parse() returns. + const scriptUrl = url.format(parsedUrl); + + getScript(scriptUrl, (err, script) => { + if (err != null) { + sendError(`Failed to get script from packager: ${err.message}`); + return; + } + + if (currentContext == null) { + sendError('JS runtime not prepared'); + return; + } + + if (request.inject) { + for (const name in request.inject) { + currentContext[name] = JSON.parse(request.inject[name]); + } + } + + try { + script.runInContext(currentContext, TEMP_BUNDLE_PATH); + } catch (e) { + sendError(`Failed to exec script: ${e.message}`); + } + sendResult(request.id); + }); + + return; + + default: + let returnValue = [[], [], [], 0]; + try { + if ( + currentContext != null && + typeof currentContext.__fbBatchedBridge === 'object' + ) { + returnValue = currentContext.__fbBatchedBridge[request.method].apply( + null, + request.arguments + ); + } + } catch (e) { + sendError( + `Failed while making a call ${request.method}:::${e.message}` + ); + } finally { + sendResult(request.id, JSON.stringify(returnValue)); + } + } +}); diff --git a/tests-new/bridge/env/node/ws.js b/tests-new/bridge/env/node/ws.js new file mode 100644 index 00000000..0d4c4684 --- /dev/null +++ b/tests-new/bridge/env/node/ws.js @@ -0,0 +1,12 @@ +const WebSocket = require('ws'); + +const ws = new WebSocket( + // todo read url from somewhere + 'ws://' + 'localhost:8081' + '/debugger-proxy?role=debugger&name=Chrome' +); + +ws.onmessage = message => process.emit('ws-message', JSON.parse(message.data)); +ws.onopen = () => console.log('WS open'); +ws.onclose = event => (!event.wasClean ? console.log('WS close', event) : ''); + +module.exports = ws; diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js new file mode 100644 index 00000000..39763587 --- /dev/null +++ b/tests-new/bridge/env/rn.js @@ -0,0 +1,28 @@ +import reactNative, { Platform, NativeModules } from 'react-native'; +import RNRestart from 'react-native-restart'; // Import package from node modules + +const bridgeNode = global.__bridgeNode; + +// https://github.com/ptmt/react-native-macos/blob/master/React/Modules/RCTDevSettings.mm +if (Platform.OS === 'ios' && !bridgeNode) { + NativeModules.RCTDevSettings.setIsDebuggingRemotely(true); +} + +if (bridgeNode) { + bridgeNode.provideReactNativeModule(reactNative); + bridgeNode.provideReload(RNRestart.Restart); +} + +export default { + provideModule(moduleExports) { + if (bridgeNode) { + bridgeNode.provideModule(moduleExports); + bridgeNode.ready(); + } + }, + provideRoot(rootComponent) { + if (bridgeNode) { + bridgeNode.provideRoot(rootComponent); + } + }, +}; diff --git a/tests-new/e2e/bridge.spec.js b/tests-new/e2e/bridge.spec.js new file mode 100755 index 00000000..98aaa7a8 --- /dev/null +++ b/tests-new/e2e/bridge.spec.js @@ -0,0 +1,60 @@ +// describe('Example', () => { +// beforeEach(async () => { +// await device.reloadReactNative(); +// }); +// +// it('should have welcome screen', async () => { +// await expect(element(by.id('welcome'))).toBeVisible(); +// }); +// +// it('should show hello screen after tap', async () => { +// await element(by.id('hello_button')).tap(); +// await expect(element(by.text('Hello!!!'))).toBeVisible(); +// }); +// +// it('should show world screen after tap', async () => { +// await element(by.id('world_button')).tap(); +// await expect(element(by.text('World!!!'))).toBeVisible(); +// }); +// }); + +describe('should work inside node', () => { + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('should provide bridge global', () => { + const firebase = bridge.module; + + + return Promise.resolve(); + }); + + it('should require 2', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); + }); + + it('should require 3', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); + }); + + it('should require 4', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); + }); + + it('should require 5', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); + }); +}); diff --git a/tests-new/e2e/example.spec.js b/tests-new/e2e/example.spec.js index e18ea21d..5b4e6e74 100755 --- a/tests-new/e2e/example.spec.js +++ b/tests-new/e2e/example.spec.js @@ -25,7 +25,36 @@ describe('should work inside node', () => { it('should require', () => { const firebase = bridge.module; + + + return Promise.resolve(); + }); + + it('should require 2', () => { + const firebase = bridge.module; // const { Platform } = bridge.rnModule; - should.equal(firebase.auth.nativeModuleExists, true); + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); + }); + + it('should require 3', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); + }); + + it('should require 4', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); + }); + + it('should require 5', () => { + const firebase = bridge.module; + // const { Platform } = bridge.rnModule; + // should.equal(firebase.auth.nativeModuleExists, true); + return Promise.resolve(); }); }); diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index 8e9cd41e..131492bb 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -1,5 +1,4 @@ ---delay --recursive --timeout 120000 --bail ---require bridge +--require ./bridge/env/node diff --git a/tests-new/index.js b/tests-new/index.js index 3a208360..4cd59214 100755 --- a/tests-new/index.js +++ b/tests-new/index.js @@ -1 +1 @@ -require('./app'); \ No newline at end of file +require('./app'); diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index 6d6af682..b3fc04c7 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -1,5 +1,5 @@ { - "name": "example", + "name": "rnfirebase-tests", "version": "7.2.0", "lockfileVersion": 1, "requires": true, @@ -337,8 +337,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "arch": { "version": "2.1.0", @@ -520,6 +519,11 @@ "dev": true, "optional": true }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1458,8 +1462,7 @@ "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, "body-parser": { "version": "1.13.3", @@ -1544,8 +1547,7 @@ "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=" }, "bser": { "version": "2.0.0", @@ -1639,7 +1641,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz", "integrity": "sha1-RzChHvYQ+tRQuPIjx50x172tgHQ=", - "dev": true, "requires": { "cross-spawn": "4.0.2", "node-version": "1.1.3", @@ -1650,7 +1651,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, "requires": { "lru-cache": "4.1.2", "which": "1.3.0" @@ -2052,8 +2052,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "contains-path": { "version": "0.1.0", @@ -2304,7 +2303,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/detox/-/detox-7.2.0.tgz", "integrity": "sha512-hj+GgnXJWmNit0lzeDY1KjZZv3nvNWsB3V6oGIvwCe3Jdl2JXqg6Qn2USa137loTpuvdR77Zgl5m3u1iG1Am/Q==", - "dev": true, "requires": { "child-process-promise": "2.2.1", "commander": "2.15.1", @@ -2324,7 +2322,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, "requires": { "graceful-fs": "4.1.11", "jsonfile": "4.0.0", @@ -2335,7 +2332,6 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, "requires": { "aproba": "1.2.0", "console-control-strings": "1.1.0", @@ -2351,7 +2347,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -2360,7 +2355,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, "requires": { "graceful-fs": "4.1.11" } @@ -2369,7 +2363,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, "requires": { "are-we-there-yet": "1.1.4", "console-control-strings": "1.1.0", @@ -2381,12 +2374,20 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", "strip-ansi": "3.0.1" } + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": "0.0.6", + "ultron": "1.0.2" + } } } }, @@ -2394,7 +2395,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/detox-server/-/detox-server-7.0.0.tgz", "integrity": "sha512-zs9ZP/MgeEmaZD/+MCl5PVcYHRjUtFBkBx3xQRPcsjJ/PmpCKy/BvygjLO6tRsR/2SC9UYay6W+BdguEYeft8g==", - "dev": true, "requires": { "lodash": "4.17.5", "npmlog": "4.1.2", @@ -2405,7 +2405,6 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, "requires": { "aproba": "1.2.0", "console-control-strings": "1.1.0", @@ -2421,7 +2420,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -2430,7 +2428,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, "requires": { "are-we-there-yet": "1.1.4", "console-control-strings": "1.1.0", @@ -2442,20 +2439,27 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", "strip-ansi": "3.0.1" } + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": "0.0.6", + "ultron": "1.0.2" + } } } }, "diff": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", - "dev": true + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" }, "doctrine": { "version": "2.1.0", @@ -4214,7 +4218,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-2.1.0.tgz", "integrity": "sha1-h4P53OvR7qSVozThpqJR54iHqxo=", - "dev": true, "requires": { "pinkie-promise": "2.0.1" } @@ -4311,8 +4314,7 @@ "growl": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", - "dev": true + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==" }, "growly": { "version": "1.3.0", @@ -4482,8 +4484,7 @@ "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" }, "hoek": { "version": "4.2.1", @@ -4561,8 +4562,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { "version": "3.3.0", @@ -5554,6 +5554,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": "0.0.6", + "ultron": "1.0.2" + } } } }, @@ -5675,7 +5684,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", - "dev": true, "requires": { "browser-stdout": "1.3.0", "commander": "2.11.0", @@ -5692,14 +5700,12 @@ "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -5707,14 +5713,12 @@ "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" }, "supports-color": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, "requires": { "has-flag": "2.0.0" } @@ -5862,8 +5866,7 @@ "node-version": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.1.3.tgz", - "integrity": "sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==", - "dev": true + "integrity": "sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==" }, "normalize-package-data": { "version": "2.4.0", @@ -6273,14 +6276,12 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "2.0.4" } @@ -6392,8 +6393,7 @@ "promise-polyfill": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", - "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=", - "dev": true + "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=" }, "prop-types": { "version": "15.6.1", @@ -6605,9 +6605,23 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz", "integrity": "sha1-rDydOfMgxtzlM5lp0FTvQ90zMxk=" + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": "0.0.6", + "ultron": "1.0.2" + } } } }, + "react-native-restart": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/react-native-restart/-/react-native-restart-0.0.6.tgz", + "integrity": "sha512-ysKvNOjCeCS8A4ouQkJ8DZRYH4wgPdPEJqqqSGFuNzK4eAi3VPOeneQ3w0HHYeOui5fc6fV9y9ClOB9LcKbKpQ==" + }, "react-proxy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz", @@ -7430,7 +7444,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/shell-utils/-/shell-utils-1.0.9.tgz", "integrity": "sha512-JbTHnKpMyj9TUUbL+Us2Rx2iVHFvH5QyQoke9SN1L0pueiZeO2Gzlzopmloi7oqObL4qtvdSuZPE3UfdIzmlag==", - "dev": true, "requires": { "lodash": "4.17.5" } @@ -7906,14 +7919,12 @@ "tail": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/tail/-/tail-1.2.3.tgz", - "integrity": "sha1-sI1vp5+5KIaWMaNBpRwUSXwcQlU=", - "dev": true + "integrity": "sha1-sI1vp5+5KIaWMaNBpRwUSXwcQlU=" }, "telnet-client": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-0.15.3.tgz", "integrity": "sha512-GSfdzQV0BKIYsmeXq7bJFJ2wHeJud6icaIxCUf6QCGQUD6R0BBGbT1+yLDhq67JRdgRpwyPwUbV7JxFeRrZomQ==", - "dev": true, "requires": { "bluebird": "3.5.1" } @@ -8522,8 +8533,7 @@ "universalify": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", - "dev": true + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" }, "unpipe": { "version": "1.0.0", @@ -8704,7 +8714,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "dev": true, "requires": { "string-width": "1.0.2" }, @@ -8713,7 +8722,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -8722,7 +8730,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -8798,12 +8805,11 @@ } }, "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.0.tgz", + "integrity": "sha512-7KU/qkUXtJW9aa5WRKlo0puE1ejEoAgDb0D/Pt+lWpTkKF7Kp+MqFOtwNFwnuiYeeDpFjp0qyMniE84OjKIEqQ==", "requires": { - "options": "0.0.6", - "ultron": "1.0.2" + "async-limiter": "1.0.0" } }, "xcode": { diff --git a/tests-new/package.json b/tests-new/package.json index 2d3e4cb4..0db469f8 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -3,7 +3,8 @@ "version": "7.2.0", "private": true, "scripts": { - "start": "REACT_DEBUGGER='todo' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow", + "start-local-debug": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow", + "start": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow", "start-ci": "REACT_DEBUGGER='todo' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow --nonPersistent", "android:dev": "react-native run-android", "android:prod": "react-native run-android --configuration=release", @@ -12,10 +13,12 @@ "ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .." }, "dependencies": { + "detox": "^7.2.0", + "mocha": "^4.0.1", "react": "^16.2.0", "react-native": "^0.52.3", - "detox": "^7.2.0", - "mocha": "^4.0.1" + "react-native-restart": "0.0.6", + "ws": "^5.1.0" }, "devDependencies": { "babel-cli": "^6.24.0", From 1aa2e032f9f42fdeba5bccff816ca44860339782 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 05:53:49 +0000 Subject: [PATCH 09/40] wip --- tests-new/README.md | 12 + tests-new/android/app/google-services.json | 97 +++++--- tests-new/app.js | 2 +- tests-new/bridge/env/node/index.js | 49 ++-- tests-new/bridge/env/node/vm.js | 86 ++++++- tests-new/bridge/env/node/ws.js | 2 +- tests-new/bridge/env/rn.js | 10 + tests-new/e2e/auth/auth.js | 241 +++++++++++++++++++ tests-new/e2e/bridge.spec.js | 62 ++--- tests-new/e2e/example.spec.js | 60 ----- tests-new/e2e/firestore/transactions.e2e.js | 78 ++++++ tests-new/e2e/mocha.opts | 1 + tests-new/package-lock.json | 250 ++++++++++++++++++++ tests-new/package.json | 5 + 14 files changed, 789 insertions(+), 166 deletions(-) create mode 100644 tests-new/e2e/auth/auth.js delete mode 100755 tests-new/e2e/example.spec.js create mode 100644 tests-new/e2e/firestore/transactions.e2e.js diff --git a/tests-new/README.md b/tests-new/README.md index b7421b50..c487929e 100755 --- a/tests-new/README.md +++ b/tests-new/README.md @@ -81,3 +81,15 @@ CPU/ABI: null (null) Path: /Users/mike/.android/avd/Actually_THIS_one.avd Error: Failed to parse properties from /Users/mike/.android/avd/Actually_THIS_one.avd/config.ini + +#### Running specific tests + +Add a `--grep` to e2e/mocha.opts file, e.g. `--grep auth` for all tests that have auth in the file path or tests descriptions. + +#### Running Node debugger + +Add `--inspect` to e2e/mocha.opts file + +To open node debugger tools on chrome navigate to chrome://inspect/#devices and click the `Open dedicated DevTools for Node` link. + +Add the default connection of `localhost:9229` if you haven't already - then the debugger will automatically connect everytime you start tests with inspect flag. diff --git a/tests-new/android/app/google-services.json b/tests-new/android/app/google-services.json index 5181bffd..4828b6f8 100644 --- a/tests-new/android/app/google-services.json +++ b/tests-new/android/app/google-services.json @@ -1,38 +1,43 @@ { "project_info": { - "project_number": "17067372085", - "firebase_url": "https://rnfirebase-5579a.firebaseio.com", - "project_id": "rnfirebase", - "storage_bucket": "rnfirebase.appspot.com" + "project_number": "305229645282", + "firebase_url": "https://rnfirebase-b9ad4.firebaseio.com", + "project_id": "rnfirebase-b9ad4", + "storage_bucket": "rnfirebase-b9ad4.appspot.com" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:17067372085:android:efe37851d57e1d05", + "mobilesdk_app_id": "1:305229645282:android:efe37851d57e1d05", "android_client_info": { "package_name": "com.reactnativefirebasedemo" } }, "oauth_client": [ { - "client_id": "17067372085-ltes2e70ehnlp4s5bl7uljuqvjul2l9s.apps.googleusercontent.com", + "client_id": "305229645282-5fgq5kq024eqpvji5o0i7jq7q7bnnpl9.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "com.reactnativefirebasedemo", - "certificate_hash": "34d770365973b7580e04dab50692506aff7bd32f" + "certificate_hash": "1f92c8aab0a091a3aaccfa144bf402bb97273494" } }, { - "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", + "client_id": "305229645282-cvp6v0iogjjuuvi5g2dcb3lrr9n884a3.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.reactnativefirebasedemo", + "certificate_hash": "859f2afac694e21d26ca67e750c9875107c2e755" + } + }, + { + "client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { - "current_key": "AIzaSyB-z0ytgXRRiClvslJl0tp-KbhDub9o6AM" - }, - { - "current_key": "AIzaSyAJw8mR1fPcEYC9ouZbkCStJufcCQrhmjQ" + "current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM" } ], "services": { @@ -43,15 +48,15 @@ "status": 2, "other_platform_oauth_client": [ { - "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "17067372085-siujfe334vool17t2mtrmjrsgl81nhd9.apps.googleusercontent.com", + "client_id": "305229645282-t29pn6o2t7se1f7rvrfsll4r0pvd6fb6.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "com.invertase.RNFirebaseTests" } + }, + { + "client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com", + "client_type": 3 } ] }, @@ -62,31 +67,28 @@ }, { "client_info": { - "mobilesdk_app_id": "1:17067372085:android:af36d4d29a83e04c", + "mobilesdk_app_id": "1:305229645282:android:c9de0f8cb930daf5", "android_client_info": { - "package_name": "com.testing" + "package_name": "com.reactnativefirebaseexamples" } }, "oauth_client": [ { - "client_id": "17067372085-066pi4ln798odtdrifoktb3mea9dtipt.apps.googleusercontent.com", + "client_id": "305229645282-hu7tr12kgn5lfhq82l51b1sh66aaue5f.apps.googleusercontent.com", "client_type": 1, "android_info": { - "package_name": "com.testing", - "certificate_hash": "34d770365973b7580e04dab50692506aff7bd32f" + "package_name": "com.reactnativefirebaseexamples", + "certificate_hash": "1f92c8aab0a091a3aaccfa144bf402bb97273494" } }, { - "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", + "client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { - "current_key": "AIzaSyB-z0ytgXRRiClvslJl0tp-KbhDub9o6AM" - }, - { - "current_key": "AIzaSyAJw8mR1fPcEYC9ouZbkCStJufcCQrhmjQ" + "current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM" } ], "services": { @@ -97,15 +99,15 @@ "status": 2, "other_platform_oauth_client": [ { - "client_id": "17067372085-n572o9802h9jbv9oo60h53117pk9333k.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "17067372085-siujfe334vool17t2mtrmjrsgl81nhd9.apps.googleusercontent.com", + "client_id": "305229645282-t29pn6o2t7se1f7rvrfsll4r0pvd6fb6.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "com.invertase.RNFirebaseTests" } + }, + { + "client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com", + "client_type": 3 } ] }, @@ -113,6 +115,37 @@ "status": 2 } } + }, + { + "client_info": { + "mobilesdk_app_id": "1:305229645282:android:af36d4d29a83e04c", + "android_client_info": { + "package_name": "com.testing" + } + }, + "oauth_client": [ + { + "client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } } ], "configuration_version": "1" diff --git a/tests-new/app.js b/tests-new/app.js index 80af3871..224c6eb7 100755 --- a/tests-new/app.js +++ b/tests-new/app.js @@ -23,7 +23,7 @@ class Root extends Component { render() { return ( - React Native Firebase Test App + React Native Firebase Test App ); } diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js index 8a506d01..31905726 100644 --- a/tests-new/bridge/env/node/index.js +++ b/tests-new/bridge/env/node/index.js @@ -2,13 +2,13 @@ const detox = require('detox'); const vm = require('./vm'); const ws = require('./ws'); +// TODO each reload/relaunch should capture __coverage__ + const detoxOriginalInit = detox.init.bind(detox); const detoxOriginalCleanup = detox.cleanup.bind(detox); -let detoxOriginalReloadReactNative = null; let bridgeReady = false; process.on('rn-ready', () => { - // console.log('READY', true); bridgeReady = true; }); @@ -19,37 +19,38 @@ function onceBridgeReady() { }); } +function shimDevice() { + // reloadReactNative + const detoxOriginalReloadReactNative = device.reloadReactNative.bind(device); + device.reloadReactNative = async () => { + bridgeReady = false; + global.bridge.reload(); + return onceBridgeReady(); + }; + + // launchApp + const detoxOriginalLaunchApp = device.launchApp.bind(device); + device.launchApp = async (...args) => { + bridgeReady = false; + await detoxOriginalLaunchApp(...args); + return onceBridgeReady(); + }; + + // todo other device methods +} + detox.init = async (...args) => { bridgeReady = false; - console.log('detox.init.start'); - return detoxOriginalInit(...args).then(() => { - console.log('detox.init.complete'); - - detoxOriginalReloadReactNative = device.reloadReactNative.bind(device); - device.reloadReactNative = async () => { - console.log('reloadReactNative.start'); - bridgeReady = false; - // return device.launchApp({ newInstance: true }).then(() => { - global.bridge.reload(); - - return onceBridgeReady(); - }; - + shimDevice(); return onceBridgeReady(); }); }; -detox.cleanup = async (...args) => { - console.log('detox.cleanup'); - - return detoxOriginalCleanup(...args).then(() => { - console.log('detox.cleanup.end'); - +detox.cleanup = async (...args) => + detoxOriginalCleanup(...args).then(() => { ws.close(); - process.exit(); }); -}; global.bridge = { _ws: null, diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index 6185bc08..da4a33d3 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -25,11 +25,12 @@ function sendResult(replyID, result) { } /** - * + * TODO * @param message */ -function sendError(message) { - console.error(message); +function sendError(error) { + console.log('error'); + throw error; } /** @@ -67,13 +68,80 @@ function getScript(src, callback) { }); } +function consoleShim() { + return { + ...console, + log(...args) { + if ( + args[0] && + typeof args[0] === 'string' && + args[0].startsWith('Running application "') + ) { + return; + } + + if ( + args[0] && + typeof args[0] === 'string' && + args[0].startsWith('Deprecated') + ) { + return; + } + console.log(...args); + }, + warn(...args) { + if ( + args[0] && + typeof args[0] === 'string' && + args[0].startsWith('Running application "') + ) { + return; + } + + if ( + args[0] && + typeof args[0] === 'string' && + args[0].startsWith('Deprecated') + ) { + return; + } + console.log(...args); + }, + }; +} + process.on('ws-message', request => { // console.log(request.method); switch (request.method) { case 'prepareJSRuntime': + if (currentContext) { + try { + for (const name in currentContext.__fbBatchedBridge) { + currentContext.__fbBatchedBridge[name] = undefined; + delete currentContext.__fbBatchedBridge[name]; + } + + for (const name in currentContext.__fbGenNativeModule) { + currentContext.__fbGenNativeModule[name] = undefined; + delete currentContext.__fbGenNativeModule[name]; + } + + for (const name in currentContext.__fbBatchedBridgeConfig) { + currentContext.__fbBatchedBridgeConfig[name] = undefined; + delete currentContext.__fbBatchedBridgeConfig[name]; + } + + for (const name in currentContext) { + currentContext[name] = undefined; + delete currentContext[name]; + } + } catch (e) { + console.error(e); + } + } currentContext = undefined; currentContext = createContext({ - console, + console: consoleShim(), __bridgeNode: { ready() { process.emit('rn-ready'); @@ -128,7 +196,7 @@ process.on('ws-message', request => { try { script.runInContext(currentContext, TEMP_BUNDLE_PATH); } catch (e) { - sendError(`Failed to exec script: ${e.message}`); + sendError(e); } sendResult(request.id); }); @@ -148,9 +216,11 @@ process.on('ws-message', request => { ); } } catch (e) { - sendError( - `Failed while making a call ${request.method}:::${e.message}` - ); + if (request.method !== '$disconnected') { + sendError( + `Failed while making a call ${request.method}:::${e.message}` + ); + } } finally { sendResult(request.id, JSON.stringify(returnValue)); } diff --git a/tests-new/bridge/env/node/ws.js b/tests-new/bridge/env/node/ws.js index 0d4c4684..daf9745d 100644 --- a/tests-new/bridge/env/node/ws.js +++ b/tests-new/bridge/env/node/ws.js @@ -6,7 +6,7 @@ const ws = new WebSocket( ); ws.onmessage = message => process.emit('ws-message', JSON.parse(message.data)); -ws.onopen = () => console.log('WS open'); +// ws.onopen = () => console.log('WS open'); ws.onclose = event => (!event.wasClean ? console.log('WS close', event) : ''); module.exports = ws; diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index 39763587..87baab0e 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -1,6 +1,10 @@ import reactNative, { Platform, NativeModules } from 'react-native'; import RNRestart from 'react-native-restart'; // Import package from node modules +require('sinon'); +require('should-sinon'); +require('should'); + const bridgeNode = global.__bridgeNode; // https://github.com/ptmt/react-native-macos/blob/master/React/Modules/RCTDevSettings.mm @@ -26,3 +30,9 @@ export default { } }, }; + +// keep alive +setInterval(() => { + // I don't do anything lol + // BUT i am needed - otherwise RN's batch bridge starts to hang in detox... ??? +}, 50); diff --git a/tests-new/e2e/auth/auth.js b/tests-new/e2e/auth/auth.js new file mode 100644 index 00000000..447ee416 --- /dev/null +++ b/tests-new/e2e/auth/auth.js @@ -0,0 +1,241 @@ +const sinon = require('sinon'); +require('should-sinon'); +const should = require('should'); + +const randomString = (length, chars) => { + let mask = ''; + if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz'; + if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (chars.indexOf('#') > -1) mask += '0123456789'; + if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; + let result = ''; + for (let i = length; i > 0; --i) { + result += mask[Math.round(Math.random() * (mask.length - 1))]; + } + return result; +}; + +describe('.auth()', () => { + beforeEach(async () => { + await device.reloadReactNative(); + }); + + describe('.signInAnonymously()', () => { + it('it should sign in anonymously', () => { + const successCb = currentUser => { + debugger; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + + currentUser.should.equal(bridge.module.auth().currentUser); + + return bridge.module.auth().signOut(); + }; + + return bridge.module + .auth() + .signInAnonymously() + .then(successCb); + }); + }); + + describe('.signInAnonymouslyAndRetrieveData()', () => { + it('it should sign in anonymously', () => { + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(bridge.module.auth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + + return bridge.module.auth().signOut(); + }; + + return bridge.module + .auth() + .signInAnonymouslyAndRetrieveData() + .then(successCb); + }); + }); + + describe('.signInWithEmailAndPassword()', () => { + it('it should login with email and password', () => { + const email = 'test@test.com'; + const pass = 'test1234'; + + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(bridge.module.auth().currentUser); + + return bridge.module.auth().signOut(); + }; + + return bridge.module + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const email = 'disabled@account.com'; + const pass = 'test1234'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal( + 'The user account has been disabled by an administrator.' + ); + return Promise.resolve(); + }; + + return bridge.module + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if password incorrect', () => { + const email = 'test@test.com'; + const pass = 'test1234666'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal( + 'The password is invalid or the user does not have a password.' + ); + return Promise.resolve(); + }; + + return bridge.module + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const email = 'randomSomeone@fourOhFour.com'; + const pass = 'test1234'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal( + 'There is no user record corresponding to this identifier. The user may have been deleted.' + ); + return Promise.resolve(); + }; + + return bridge.module + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + }); + + describe('.onAuthStateChanged()', () => { + it('calls callback with the current user and when auth state changes', async () => { + await bridge.module.auth().signInAnonymouslyAndRetrieveData(); + + // Test + const callback = sinon.spy(); + + let unsubscribe; + await new Promise(resolve => { + unsubscribe = bridge.module.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledWith(bridge.module.auth().currentUser); + callback.should.be.calledOnce(); + + // Sign out + + await bridge.module.auth().signOut(); + + await new Promise(resolve => { + setTimeout(() => resolve(), 100); + }); + + // Assertions + + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); + }); + + it('stops listening when unsubscribe called', async () => { + await bridge.module.auth().signInAnonymouslyAndRetrieveData(); + + // Test + const callback = sinon.spy(); + + let unsubscribe; + await new Promise(resolve => { + unsubscribe = bridge.module.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledWith(bridge.module.auth().currentUser); + callback.should.be.calledOnce(); + + // Sign out + + await bridge.module.auth().signOut(); + + await new Promise(resolve => { + setTimeout(() => resolve(), 100); + }); + + // Assertions + + // callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Unsubscribe + + unsubscribe(); + + // Sign back in + + await bridge.module.auth().signInAnonymouslyAndRetrieveData(); + + // Assertions + + callback.should.be.calledTwice(); + + // Tear down + + await bridge.module.auth().signOut(); + }); + }); +}); diff --git a/tests-new/e2e/bridge.spec.js b/tests-new/e2e/bridge.spec.js index 98aaa7a8..e9b007f9 100755 --- a/tests-new/e2e/bridge.spec.js +++ b/tests-new/e2e/bridge.spec.js @@ -1,60 +1,42 @@ -// describe('Example', () => { -// beforeEach(async () => { -// await device.reloadReactNative(); -// }); -// -// it('should have welcome screen', async () => { -// await expect(element(by.id('welcome'))).toBeVisible(); -// }); -// -// it('should show hello screen after tap', async () => { -// await element(by.id('hello_button')).tap(); -// await expect(element(by.text('Hello!!!'))).toBeVisible(); -// }); -// -// it('should show world screen after tap', async () => { -// await element(by.id('world_button')).tap(); -// await expect(element(by.text('World!!!'))).toBeVisible(); -// }); -// }); +const should = require('should'); -describe('should work inside node', () => { +describe('bridge', () => { beforeEach(async () => { await device.reloadReactNative(); }); - it('should provide bridge global', () => { - const firebase = bridge.module; - - + it('should provide -> global.bridge', () => { + should(bridge).not.be.undefined(); return Promise.resolve(); }); - it('should require 2', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); + it('should provide -> global.bridge.module', () => { + should(bridge.module).not.be.undefined(); return Promise.resolve(); }); - it('should require 3', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); + it('should provide -> global.bridge.rn', () => { + should(bridge.rn).not.be.undefined(); + should(bridge.rn.Platform.OS).be.a.String(); + should(bridge.rn.Platform.OS).equal(device.getPlatform()); return Promise.resolve(); }); - it('should require 4', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); + it('should provide -> global.reload and allow reloadReactNative usage', async () => { + should(bridge.reload).be.a.Function(); + // and check it works without breaking anything + await device.reloadReactNative(); + should(bridge.reload).be.a.Function(); return Promise.resolve(); }); - it('should require 5', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); + it('should allow detox to launchApp without breaking remote debug', async () => { + await device.launchApp({ newInstance: true }); + should(bridge.module).not.be.undefined(); + should(bridge.reload).be.a.Function(); + should(bridge.rn).not.be.undefined(); + should(bridge.rn.Platform.OS).be.a.String(); + should(bridge.rn.Platform.OS).equal(device.getPlatform()); return Promise.resolve(); }); }); diff --git a/tests-new/e2e/example.spec.js b/tests-new/e2e/example.spec.js deleted file mode 100755 index 5b4e6e74..00000000 --- a/tests-new/e2e/example.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -// describe('Example', () => { -// beforeEach(async () => { -// await device.reloadReactNative(); -// }); -// -// it('should have welcome screen', async () => { -// await expect(element(by.id('welcome'))).toBeVisible(); -// }); -// -// it('should show hello screen after tap', async () => { -// await element(by.id('hello_button')).tap(); -// await expect(element(by.text('Hello!!!'))).toBeVisible(); -// }); -// -// it('should show world screen after tap', async () => { -// await element(by.id('world_button')).tap(); -// await expect(element(by.text('World!!!'))).toBeVisible(); -// }); -// }); - -describe('should work inside node', () => { - beforeEach(async () => { - await device.reloadReactNative(); - }); - - it('should require', () => { - const firebase = bridge.module; - - - return Promise.resolve(); - }); - - it('should require 2', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); - return Promise.resolve(); - }); - - it('should require 3', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); - return Promise.resolve(); - }); - - it('should require 4', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); - return Promise.resolve(); - }); - - it('should require 5', () => { - const firebase = bridge.module; - // const { Platform } = bridge.rnModule; - // should.equal(firebase.auth.nativeModuleExists, true); - return Promise.resolve(); - }); -}); diff --git a/tests-new/e2e/firestore/transactions.e2e.js b/tests-new/e2e/firestore/transactions.e2e.js new file mode 100644 index 00000000..f6d716b1 --- /dev/null +++ b/tests-new/e2e/firestore/transactions.e2e.js @@ -0,0 +1,78 @@ +const should = require('should'); + +describe('firestore.runTransaction', () => { + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('should set, update and delete transactionally and allow a return value', async () => { + const firebase = bridge.module; + let deleteMe = false; + const firestore = firebase.firestore(); + + const docRef = firestore + .collection('transactions') + .doc(Date.now().toString()); + + const updateFunction = async transaction => { + const doc = await transaction.get(docRef); + if (doc.exists && deleteMe) { + transaction.delete(docRef); + return 'bye'; + } + + if (!doc.exists) { + transaction.set(docRef, { value: 1 }); + return 1; + } + + const newValue = doc.data().value + 1; + + if (newValue > 2) { + return Promise.reject(new Error('Value should not be greater than 2!')); + } + + transaction.update(docRef, { + value: newValue, + somethingElse: 'update', + }); + + return newValue; + }; + + // set tests + const val1 = await firestore.runTransaction(updateFunction); + should.equal(val1, 1); + const doc1 = await docRef.get(); + doc1.data().value.should.equal(1); + should.equal(doc1.data().somethingElse, undefined); + + // update + const val2 = await firestore.runTransaction(updateFunction); + should.equal(val2, 2); + const doc2 = await docRef.get(); + doc2.data().value.should.equal(2); + doc2.data().somethingElse.should.equal('update'); + + // rejecting / cancelling transaction + let didReject = false; + try { + await firestore.runTransaction(updateFunction); + } catch (e) { + didReject = true; + } + should.equal(didReject, true); + const doc3 = await docRef.get(); + doc3.data().value.should.equal(2); + doc3.data().somethingElse.should.equal('update'); + + // delete + deleteMe = true; + const val4 = await firestore.runTransaction(updateFunction); + should.equal(val4, 'bye'); + const doc4 = await docRef.get(); + should.equal(doc4.exists, false); + + return Promise.resolve('Test Completed'); + }); +}); diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index 131492bb..2fead4e1 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -1,4 +1,5 @@ --recursive --timeout 120000 +--slow 1200 --bail --require ./bridge/env/node diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index b3fc04c7..a8b2ecd2 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -4,6 +4,14 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "requires": { + "samsam": "1.3.0" + } + }, "absolute-path": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", @@ -960,6 +968,15 @@ "babel-runtime": "6.26.0" } }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-plugin-transform-es2015-for-of": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", @@ -1054,6 +1071,14 @@ "babel-runtime": "6.26.0" } }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "requires": { + "babel-runtime": "6.26.0" + } + }, "babel-plugin-transform-es2015-unicode-regex": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", @@ -1185,6 +1210,82 @@ } } }, + "babel-preset-es2015": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.6.0.tgz", + "integrity": "sha1-iLM+WP7JTG695Y3GXs5dFODsJWg=", + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-regenerator": "6.26.0" + } + }, + "babel-preset-es2015-mod": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-es2015-mod/-/babel-preset-es2015-mod-6.6.0.tgz", + "integrity": "sha1-4QW2LrfBABCQq4YiUpiQTPkMHo4=", + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "6.7.7", + "babel-plugin-transform-regenerator": "6.6.5", + "babel-preset-es2015": "6.6.0", + "modify-babel-preset": "2.0.2" + }, + "dependencies": { + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.7.7.tgz", + "integrity": "sha1-+lyiAWYXxNcSEj2M/BV4f8qoPzM=", + "requires": { + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "5.8.38", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.6.5.tgz", + "integrity": "sha1-B5qYK9VuIjXjHuOxetVK66iY1Oc=", + "requires": { + "babel-core": "6.26.0", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-runtime": "5.8.38", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "private": "0.1.8" + } + }, + "babel-runtime": { + "version": "5.8.38", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz", + "integrity": "sha1-HAsC62MxL18If/IEUIJ7QlydTBk=", + "requires": { + "core-js": "1.2.7" + } + } + } + }, "babel-preset-es2015-node": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-preset-es2015-node/-/babel-preset-es2015-node-6.1.1.tgz", @@ -1201,6 +1302,15 @@ "semver": "5.5.0" } }, + "babel-preset-es3": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-es3/-/babel-preset-es3-1.0.1.tgz", + "integrity": "sha1-4I3ZUKFnDauLUKvOqpuT09mszR4=", + "requires": { + "babel-plugin-transform-es3-member-expression-literals": "6.22.0", + "babel-plugin-transform-es3-property-literals": "6.22.0" + } + }, "babel-preset-fbjs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz", @@ -5099,6 +5209,11 @@ "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", "dev": true }, + "just-extend": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==" + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -5216,6 +5331,11 @@ "lodash._root": "3.0.1" } }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -5286,6 +5406,11 @@ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, + "lolex": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", + "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==" + }, "loose-envify": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", @@ -5725,6 +5850,14 @@ } } }, + "modify-babel-preset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/modify-babel-preset/-/modify-babel-preset-2.0.2.tgz", + "integrity": "sha1-v6UJZp/kn0IiwM4XG6RO0OgVUec=", + "requires": { + "require-relative": "0.8.7" + } + }, "morgan": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", @@ -5838,6 +5971,18 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "nise": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.2.tgz", + "integrity": "sha512-KPKb+wvETBiwb4eTwtR/OsA2+iijXP+VnlSFYJo3EHjm2yjek1NWxHOUQat3i7xNLm1Bm18UA5j5Wor0yO2GtA==", + "requires": { + "@sinonjs/formatio": "2.0.0", + "just-extend": "1.1.27", + "lolex": "2.3.2", + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" + } + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -6245,6 +6390,14 @@ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } + }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -6901,6 +7054,11 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, + "require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=" + }, "require-uncached": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", @@ -7008,6 +7166,11 @@ "ret": "0.1.15" } }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==" + }, "sane": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.0.tgz", @@ -7464,6 +7627,59 @@ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, + "should": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz", + "integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==", + "requires": { + "should-equal": "2.0.0", + "should-format": "3.0.3", + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0", + "should-util": "1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "requires": { + "should-type": "1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "requires": { + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0" + } + }, + "should-sinon": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/should-sinon/-/should-sinon-0.0.6.tgz", + "integrity": "sha512-ScBOH5uW5QVFaONmUnIXANSR6z5B8IKzEmBP3HE5sPOCDuZ88oTMdUdnKoCVQdLcCIrRrhRLPS5YT+7H40a04g==" + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=" + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "requires": { + "should-type": "1.4.0", + "should-util": "1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -7501,6 +7717,30 @@ } } }, + "sinon": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.4.8.tgz", + "integrity": "sha512-EWZf/D5BN/BbDFPmwY2abw6wgELVmk361self+lcwEmVw0WWUxURp2S/YoDB2WG/xurFVzKQglMARweYRWM6Hw==", + "requires": { + "@sinonjs/formatio": "2.0.0", + "diff": "3.3.1", + "lodash.get": "4.4.2", + "lolex": "2.3.2", + "nise": "1.3.2", + "supports-color": "5.3.0", + "type-detect": "4.0.8" + }, + "dependencies": { + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "requires": { + "has-flag": "3.0.0" + } + } + } + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -8289,6 +8529,11 @@ } } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8445,6 +8690,11 @@ "prelude-ls": "1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", diff --git a/tests-new/package.json b/tests-new/package.json index 0db469f8..f06c6fc5 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -13,11 +13,16 @@ "ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .." }, "dependencies": { + "babel-preset-es2015-mod": "^6.6.0", + "babel-preset-es3": "^1.0.1", "detox": "^7.2.0", "mocha": "^4.0.1", "react": "^16.2.0", "react-native": "^0.52.3", "react-native-restart": "0.0.6", + "should": "^13.2.1", + "should-sinon": "0.0.6", + "sinon": "^4.4.8", "ws": "^5.1.0" }, "devDependencies": { From c94d408f1b3a09b25d618b4c87ea45ce72df7268 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 05:55:08 +0000 Subject: [PATCH 10/40] wip --- tests-new/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-new/README.md b/tests-new/README.md index c487929e..c9a2e892 100755 --- a/tests-new/README.md +++ b/tests-new/README.md @@ -60,7 +60,7 @@ detox test --configuration ios.sim.debug This action will open a new simulator and run the tests on it. -### TODO +### TODO - Troubleshooting Gradle issues... https://stackoverflow.com/questions/46917365/error-could-not-initialize-class-com-android-sdklib-repository-androidsdkhandle?rq=1 From b2ae5f857da19e4e5609de200d702824cad83827 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 06:31:04 +0000 Subject: [PATCH 11/40] wip --- tests-new/README.md | 4 +++ tests-new/app.js | 10 ++++-- tests-new/bridge/env/node/index.js | 23 +++++-------- tests-new/bridge/env/node/vm.js | 55 ++++++++++++++++++------------ tests-new/bridge/env/node/ws.js | 4 +-- tests-new/bridge/env/rn.js | 6 +--- tests-new/e2e/auth/auth.js | 21 ++---------- tests-new/e2e/init.js | 3 ++ tests-new/e2e/mocha.opts | 3 +- 9 files changed, 63 insertions(+), 66 deletions(-) diff --git a/tests-new/README.md b/tests-new/README.md index c9a2e892..16872d4b 100755 --- a/tests-new/README.md +++ b/tests-new/README.md @@ -93,3 +93,7 @@ Add `--inspect` to e2e/mocha.opts file To open node debugger tools on chrome navigate to chrome://inspect/#devices and click the `Open dedicated DevTools for Node` link. Add the default connection of `localhost:9229` if you haven't already - then the debugger will automatically connect everytime you start tests with inspect flag. + +#### Mocha options + +See https://mochajs.org/#usage diff --git a/tests-new/app.js b/tests-new/app.js index 224c6eb7..ad5cccb4 100755 --- a/tests-new/app.js +++ b/tests-new/app.js @@ -4,6 +4,10 @@ * @flow */ +require('sinon'); +require('should-sinon'); +require('should'); + // must import before all else import Bridge from './bridge/env/rn'; @@ -15,7 +19,9 @@ import firebase from './firebase'; class Root extends Component { constructor(props) { super(props); - this.state = {}; + this.state = { + message: 'React Native Firebase Test App', + }; Bridge.provideRoot(this); Bridge.provideModule(firebase); } @@ -23,7 +29,7 @@ class Root extends Component { render() { return ( - React Native Firebase Test App + {this.state.message} ); } diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js index 31905726..53e1cddf 100644 --- a/tests-new/bridge/env/node/index.js +++ b/tests-new/bridge/env/node/index.js @@ -1,8 +1,9 @@ -const detox = require('detox'); -const vm = require('./vm'); -const ws = require('./ws'); +global.bridge = {}; -// TODO each reload/relaunch should capture __coverage__ +const detox = require('detox'); + +require('./vm'); +const ws = require('./ws'); const detoxOriginalInit = detox.init.bind(detox); const detoxOriginalCleanup = detox.cleanup.bind(detox); @@ -21,7 +22,8 @@ function onceBridgeReady() { function shimDevice() { // reloadReactNative - const detoxOriginalReloadReactNative = device.reloadReactNative.bind(device); + // todo detoxOriginalReloadReactNative currently broken + // const detoxOriginalReloadReactNative = device.reloadReactNative.bind(device); device.reloadReactNative = async () => { bridgeReady = false; global.bridge.reload(); @@ -36,7 +38,7 @@ function shimDevice() { return onceBridgeReady(); }; - // todo other device methods + // todo other device reloading related methods } detox.init = async (...args) => { @@ -51,12 +53,3 @@ detox.cleanup = async (...args) => detoxOriginalCleanup(...args).then(() => { ws.close(); }); - -global.bridge = { - _ws: null, - - rootSetState(state) { - // todo - return Promise.resolve(); - }, -}; diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index da4a33d3..18500568 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -4,12 +4,23 @@ const invariant = require('assert'); const { createContext, Script } = require('vm'); const ws = require('./ws'); -let currentContext = null; +global.context = null; let scriptCached = null; // this is a dummy file path - without a file name the source map is not used in the vm const TEMP_BUNDLE_PATH = '/tmp/bridge/react-native.js'; +// TODO +// TODO +// TODO +// TODO +// TODO This is just dirty code created just as a proof of concept +// TODO - need to cleanup +// TODO +// TODO +// TODO +// TODO + /** * * @param replyId @@ -114,33 +125,33 @@ process.on('ws-message', request => { // console.log(request.method); switch (request.method) { case 'prepareJSRuntime': - if (currentContext) { + if (global.context) { try { - for (const name in currentContext.__fbBatchedBridge) { - currentContext.__fbBatchedBridge[name] = undefined; - delete currentContext.__fbBatchedBridge[name]; + for (const name in global.context.__fbBatchedBridge) { + global.context.__fbBatchedBridge[name] = undefined; + delete global.context.__fbBatchedBridge[name]; } - for (const name in currentContext.__fbGenNativeModule) { - currentContext.__fbGenNativeModule[name] = undefined; - delete currentContext.__fbGenNativeModule[name]; + for (const name in global.context.__fbGenNativeModule) { + global.context.__fbGenNativeModule[name] = undefined; + delete global.context.__fbGenNativeModule[name]; } - for (const name in currentContext.__fbBatchedBridgeConfig) { - currentContext.__fbBatchedBridgeConfig[name] = undefined; - delete currentContext.__fbBatchedBridgeConfig[name]; + for (const name in global.context.__fbBatchedBridgeConfig) { + global.context.__fbBatchedBridgeConfig[name] = undefined; + delete global.context.__fbBatchedBridgeConfig[name]; } - for (const name in currentContext) { - currentContext[name] = undefined; - delete currentContext[name]; + for (const name in global.context) { + global.context[name] = undefined; + delete global.context[name]; } } catch (e) { console.error(e); } } - currentContext = undefined; - currentContext = createContext({ + global.context = undefined; + global.context = createContext({ console: consoleShim(), __bridgeNode: { ready() { @@ -182,19 +193,19 @@ process.on('ws-message', request => { return; } - if (currentContext == null) { + if (global.context == null) { sendError('JS runtime not prepared'); return; } if (request.inject) { for (const name in request.inject) { - currentContext[name] = JSON.parse(request.inject[name]); + global.context[name] = JSON.parse(request.inject[name]); } } try { - script.runInContext(currentContext, TEMP_BUNDLE_PATH); + script.runInContext(global.context, TEMP_BUNDLE_PATH); } catch (e) { sendError(e); } @@ -207,10 +218,10 @@ process.on('ws-message', request => { let returnValue = [[], [], [], 0]; try { if ( - currentContext != null && - typeof currentContext.__fbBatchedBridge === 'object' + global.context != null && + typeof global.context.__fbBatchedBridge === 'object' ) { - returnValue = currentContext.__fbBatchedBridge[request.method].apply( + returnValue = global.context.__fbBatchedBridge[request.method].apply( null, request.arguments ); diff --git a/tests-new/bridge/env/node/ws.js b/tests-new/bridge/env/node/ws.js index daf9745d..99e26292 100644 --- a/tests-new/bridge/env/node/ws.js +++ b/tests-new/bridge/env/node/ws.js @@ -1,12 +1,10 @@ const WebSocket = require('ws'); const ws = new WebSocket( - // todo read url from somewhere - 'ws://' + 'localhost:8081' + '/debugger-proxy?role=debugger&name=Chrome' + 'ws://localhost:8081/debugger-proxy?role=debugger&name=Chrome' ); ws.onmessage = message => process.emit('ws-message', JSON.parse(message.data)); -// ws.onopen = () => console.log('WS open'); ws.onclose = event => (!event.wasClean ? console.log('WS close', event) : ''); module.exports = ws; diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index 87baab0e..2004e9a7 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -1,10 +1,6 @@ import reactNative, { Platform, NativeModules } from 'react-native'; import RNRestart from 'react-native-restart'; // Import package from node modules -require('sinon'); -require('should-sinon'); -require('should'); - const bridgeNode = global.__bridgeNode; // https://github.com/ptmt/react-native-macos/blob/master/React/Modules/RCTDevSettings.mm @@ -35,4 +31,4 @@ export default { setInterval(() => { // I don't do anything lol // BUT i am needed - otherwise RN's batch bridge starts to hang in detox... ??? -}, 50); +}, 60); diff --git a/tests-new/e2e/auth/auth.js b/tests-new/e2e/auth/auth.js index 447ee416..17489767 100644 --- a/tests-new/e2e/auth/auth.js +++ b/tests-new/e2e/auth/auth.js @@ -1,23 +1,8 @@ -const sinon = require('sinon'); -require('should-sinon'); -const should = require('should'); - -const randomString = (length, chars) => { - let mask = ''; - if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz'; - if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - if (chars.indexOf('#') > -1) mask += '0123456789'; - if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; - let result = ''; - for (let i = length; i > 0; --i) { - result += mask[Math.round(Math.random() * (mask.length - 1))]; - } - return result; -}; - describe('.auth()', () => { - beforeEach(async () => { + beforeEach(async function beforeEach() { await device.reloadReactNative(); + // just an example of setting the root components state from inside a test :) + bridge.root.setState({ message: this.currentTest.title }); }); describe('.signInAnonymously()', () => { diff --git a/tests-new/e2e/init.js b/tests-new/e2e/init.js index 5326ee52..0613e6f8 100755 --- a/tests-new/e2e/init.js +++ b/tests-new/e2e/init.js @@ -1,5 +1,8 @@ const detox = require('detox'); const config = require('../package.json').detox; +global.sinon = require('sinon'); +require('should-sinon'); +global.should = require('should'); before(async () => { await detox.init(config); diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index 2fead4e1..de4b6043 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -1,5 +1,6 @@ --recursive --timeout 120000 ---slow 1200 +--slow 1400 --bail +--exit --require ./bridge/env/node From 20fcdef51a00f35f6540a7a6360d90134731260d Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 06:40:55 +0000 Subject: [PATCH 12/40] [tests] new test infra - wip --- tests-new/bridge/env/node/vm.js | 19 ++++++++++++++++++- tests-new/e2e/auth/auth.js | 1 - tests-new/e2e/init.js | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index 18500568..53f7d235 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -10,16 +10,26 @@ let scriptCached = null; // this is a dummy file path - without a file name the source map is not used in the vm const TEMP_BUNDLE_PATH = '/tmp/bridge/react-native.js'; +// TODO ----------------------------------------------------------------------- +// TODO ----------------------------------------------------------------------- +// TODO ----------------------------------------------------------------------- +// TODO +// TODO // TODO // TODO // TODO // TODO // TODO This is just dirty code created just as a proof of concept -// TODO - need to cleanup +// TODO - need to clean it all up / refactor // TODO // TODO // TODO // TODO +// TODO +// TODO +// TODO ----------------------------------------------------------------------- +// TODO ----------------------------------------------------------------------- +// TODO ----------------------------------------------------------------------- /** * @@ -174,12 +184,19 @@ process.on('ws-message', request => { global.bridge.root = rootComponent; }, }, + get __coverage__() { + return global.__coverage__; + }, + set __coverage__(val) { + return (global.__coverage__ = val); + }, }); sendResult(request.id); return; case 'executeApplicationScript': // Modify the URL to make sure we get the inline source map. + // TODO we shouldn't be reparsing if scriptCached is set const parsedUrl = url.parse(request.url, /* parseQueryString */ true); invariant(parsedUrl.query); parsedUrl.query.inlineSourceMap = true; diff --git a/tests-new/e2e/auth/auth.js b/tests-new/e2e/auth/auth.js index 17489767..4f4b09bc 100644 --- a/tests-new/e2e/auth/auth.js +++ b/tests-new/e2e/auth/auth.js @@ -8,7 +8,6 @@ describe('.auth()', () => { describe('.signInAnonymously()', () => { it('it should sign in anonymously', () => { const successCb = currentUser => { - debugger; currentUser.should.be.an.Object(); currentUser.uid.should.be.a.String(); currentUser.toJSON().should.be.an.Object(); diff --git a/tests-new/e2e/init.js b/tests-new/e2e/init.js index 0613e6f8..0e5f6c4a 100755 --- a/tests-new/e2e/init.js +++ b/tests-new/e2e/init.js @@ -1,5 +1,6 @@ const detox = require('detox'); const config = require('../package.json').detox; + global.sinon = require('sinon'); require('should-sinon'); global.should = require('should'); From 92cdeac2bf9169b95edb3d24e52718d3ff037e90 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 06:45:45 +0000 Subject: [PATCH 13/40] [tests] new test infra - wip --- tests-new/bridge/env/rn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index 2004e9a7..e92599ca 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -3,7 +3,7 @@ import RNRestart from 'react-native-restart'; // Import package from node module const bridgeNode = global.__bridgeNode; -// https://github.com/ptmt/react-native-macos/blob/master/React/Modules/RCTDevSettings.mm +// https://github.com/facebook/react-native/blob/master/React/Modules/RCTDevSettings.mm if (Platform.OS === 'ios' && !bridgeNode) { NativeModules.RCTDevSettings.setIsDebuggingRemotely(true); } From 966c808aa31e5243d2d9d98c8a98b1317d9fc756 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 07:09:45 +0000 Subject: [PATCH 14/40] [tests] new test infra - wip --- tests-new/bridge/env/node/vm.js | 60 ++++++++++++++++----------------- tests-new/bridge/env/rn.js | 9 +++++ tests-new/package-lock.json | 5 +++ tests-new/package.json | 1 + 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index 53f7d235..24de3ea0 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -3,8 +3,10 @@ const http = require('http'); const invariant = require('assert'); const { createContext, Script } = require('vm'); const ws = require('./ws'); +const { merge } = require('deeps'); -global.context = null; +global.bridge.context = null; +global.__coverage__ = {}; let scriptCached = null; // this is a dummy file path - without a file name the source map is not used in the vm @@ -135,33 +137,36 @@ process.on('ws-message', request => { // console.log(request.method); switch (request.method) { case 'prepareJSRuntime': - if (global.context) { + if (global.bridge.context) { + // todo __coverage__ not working - mostly empty statements in output + merge(global.__coverage__, global.bridge.context.__coverage__ || {}); + try { - for (const name in global.context.__fbBatchedBridge) { - global.context.__fbBatchedBridge[name] = undefined; - delete global.context.__fbBatchedBridge[name]; + for (const name in global.bridge.context.__fbBatchedBridge) { + global.bridge.context.__fbBatchedBridge[name] = undefined; + delete global.bridge.context.__fbBatchedBridge[name]; } - for (const name in global.context.__fbGenNativeModule) { - global.context.__fbGenNativeModule[name] = undefined; - delete global.context.__fbGenNativeModule[name]; + for (const name in global.bridge.context.__fbGenNativeModule) { + global.bridge.context.__fbGenNativeModule[name] = undefined; + delete global.bridge.context.__fbGenNativeModule[name]; } - for (const name in global.context.__fbBatchedBridgeConfig) { - global.context.__fbBatchedBridgeConfig[name] = undefined; - delete global.context.__fbBatchedBridgeConfig[name]; + for (const name in global.bridge.context.__fbBatchedBridgeConfig) { + global.bridge.context.__fbBatchedBridgeConfig[name] = undefined; + delete global.bridge.context.__fbBatchedBridgeConfig[name]; } - for (const name in global.context) { - global.context[name] = undefined; - delete global.context[name]; + for (const name in global.bridge.context) { + global.bridge.context[name] = undefined; + delete global.bridge.context[name]; } } catch (e) { console.error(e); } } - global.context = undefined; - global.context = createContext({ + global.bridge.context = undefined; + global.bridge.context = createContext({ console: consoleShim(), __bridgeNode: { ready() { @@ -184,12 +189,6 @@ process.on('ws-message', request => { global.bridge.root = rootComponent; }, }, - get __coverage__() { - return global.__coverage__; - }, - set __coverage__(val) { - return (global.__coverage__ = val); - }, }); sendResult(request.id); return; @@ -210,19 +209,19 @@ process.on('ws-message', request => { return; } - if (global.context == null) { + if (global.bridge.context == null) { sendError('JS runtime not prepared'); return; } if (request.inject) { for (const name in request.inject) { - global.context[name] = JSON.parse(request.inject[name]); + global.bridge.context[name] = JSON.parse(request.inject[name]); } } try { - script.runInContext(global.context, TEMP_BUNDLE_PATH); + script.runInContext(global.bridge.context, TEMP_BUNDLE_PATH); } catch (e) { sendError(e); } @@ -235,13 +234,12 @@ process.on('ws-message', request => { let returnValue = [[], [], [], 0]; try { if ( - global.context != null && - typeof global.context.__fbBatchedBridge === 'object' + global.bridge.context != null && + typeof global.bridge.context.__fbBatchedBridge === 'object' ) { - returnValue = global.context.__fbBatchedBridge[request.method].apply( - null, - request.arguments - ); + returnValue = global.bridge.context.__fbBatchedBridge[ + request.method + ].apply(null, request.arguments); } } catch (e) { if (request.method !== '$disconnected') { diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index e92599ca..acd02518 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -14,12 +14,21 @@ if (bridgeNode) { } export default { + /** + * Makes the main module to be tested accessible to nodejs + * @param moduleExports + */ provideModule(moduleExports) { if (bridgeNode) { bridgeNode.provideModule(moduleExports); bridgeNode.ready(); } }, + + /** + * Makes the root component accessible to nodejs - e.g. bridge.root.setState({ ... }); + * @param rootComponent + */ provideRoot(rootComponent) { if (bridgeNode) { bridgeNode.provideRoot(rootComponent); diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index a8b2ecd2..7fe5e53a 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -2330,6 +2330,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deeps": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/deeps/-/deeps-1.4.4.tgz", + "integrity": "sha1-CRJrycesX2DIJ+EciQYa6bVyqWA=" + }, "define-properties": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", diff --git a/tests-new/package.json b/tests-new/package.json index f06c6fc5..ad88d3c0 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -15,6 +15,7 @@ "dependencies": { "babel-preset-es2015-mod": "^6.6.0", "babel-preset-es3": "^1.0.1", + "deeps": "^1.4.4", "detox": "^7.2.0", "mocha": "^4.0.1", "react": "^16.2.0", From 05824d37099f9c7292c84685ca782d09a20e1f31 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 07:11:37 +0000 Subject: [PATCH 15/40] [tests] new test infra - wip --- tests-new/e2e/auth/{auth.js => module.e2e.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests-new/e2e/auth/{auth.js => module.e2e.js} (100%) diff --git a/tests-new/e2e/auth/auth.js b/tests-new/e2e/auth/module.e2e.js similarity index 100% rename from tests-new/e2e/auth/auth.js rename to tests-new/e2e/auth/module.e2e.js From fc12f321da0d97f19c4af100861a2a4f5b92dd5b Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 07:12:46 +0000 Subject: [PATCH 16/40] [tests] new test infra - wip --- tests-new/bridge/env/rn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index acd02518..03fd2531 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -9,8 +9,8 @@ if (Platform.OS === 'ios' && !bridgeNode) { } if (bridgeNode) { - bridgeNode.provideReactNativeModule(reactNative); bridgeNode.provideReload(RNRestart.Restart); + bridgeNode.provideReactNativeModule(reactNative); } export default { From 033081f8bf3081377b88131375ccb75cce71f7af Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 07:13:58 +0000 Subject: [PATCH 17/40] [tests] new test infra - wip --- tests-new/bridge/env/rn.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index 03fd2531..38604ed8 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -11,6 +11,12 @@ if (Platform.OS === 'ios' && !bridgeNode) { if (bridgeNode) { bridgeNode.provideReload(RNRestart.Restart); bridgeNode.provideReactNativeModule(reactNative); + + // keep alive + setInterval(() => { + // I don't do anything lol + // BUT i am needed - otherwise RN's batch bridge starts to hang in detox... ??? + }, 60); } export default { @@ -35,9 +41,3 @@ export default { } }, }; - -// keep alive -setInterval(() => { - // I don't do anything lol - // BUT i am needed - otherwise RN's batch bridge starts to hang in detox... ??? -}, 60); From 31efb517519f6fed139de091b142b51afa7c476d Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 24 Mar 2018 07:14:42 +0000 Subject: [PATCH 18/40] [tests] new test infra - wip --- tests-new/bridge/env/rn.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index 38604ed8..def9c6b2 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -14,8 +14,8 @@ if (bridgeNode) { // keep alive setInterval(() => { - // I don't do anything lol - // BUT i am needed - otherwise RN's batch bridge starts to hang in detox... ??? + // I don't do anything... + // BUT i am needed - otherwise RN's batched bridge starts to hang in detox... ??? }, 60); } From cbe5d27c2a22bebf611ee4223fe9a5ceef945ac8 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 02:23:46 +0100 Subject: [PATCH 19/40] [tests] new test infra - start of bridge cleanup --- tests-new/app.js | 19 +++--- tests-new/bridge/env/node/console.js | 21 +++++++ tests-new/bridge/env/node/context.js | 74 +++++++++++++++++++++++ tests-new/bridge/env/node/index.js | 90 +++++++++++++++------------- tests-new/bridge/env/node/ready.js | 17 ++++++ tests-new/bridge/env/node/ws.js | 9 ++- tests-new/bridge/env/rn.js | 66 ++++++++++++-------- tests-new/e2e/bridge.spec.js | 48 +++++++++++++-- tests-new/e2e/init.js | 4 ++ 9 files changed, 264 insertions(+), 84 deletions(-) create mode 100644 tests-new/bridge/env/node/console.js create mode 100644 tests-new/bridge/env/node/context.js create mode 100644 tests-new/bridge/env/node/ready.js diff --git a/tests-new/app.js b/tests-new/app.js index ad5cccb4..51438121 100755 --- a/tests-new/app.js +++ b/tests-new/app.js @@ -4,32 +4,33 @@ * @flow */ -require('sinon'); -require('should-sinon'); -require('should'); - // must import before all else -import Bridge from './bridge/env/rn'; import React, { Component } from 'react'; import { AppRegistry, Text, View } from 'react-native'; +import bridge from './bridge/env/rn'; import firebase from './firebase'; +require('sinon'); +require('should-sinon'); +require('should'); + class Root extends Component { constructor(props) { super(props); this.state = { - message: 'React Native Firebase Test App', + message: '', }; - Bridge.provideRoot(this); - Bridge.provideModule(firebase); + + bridge.setBridgeProperty('root', this); + bridge.setBridgeProperty('module', firebase); } render() { return ( - {this.state.message} + {this.state.message} ); } diff --git a/tests-new/bridge/env/node/console.js b/tests-new/bridge/env/node/console.js new file mode 100644 index 00000000..105df4fb --- /dev/null +++ b/tests-new/bridge/env/node/console.js @@ -0,0 +1,21 @@ +module.exports = function consoleContext() { + return { + ...console, + /** + * Override console log so we can ignore certain logs like the application being started + * + * @param args + */ + log(...args) { + if ( + args[0] && + typeof args[0] === 'string' && + args[0].startsWith('Running application "') + ) { + return; + } + + console.log(...args); + }, + }; +}; diff --git a/tests-new/bridge/env/node/context.js b/tests-new/bridge/env/node/context.js new file mode 100644 index 00000000..683e4cf6 --- /dev/null +++ b/tests-new/bridge/env/node/context.js @@ -0,0 +1,74 @@ +/* eslint-disable guard-for-in,no-restricted-syntax */ +global.bridge.context = null; + +const consoleContext = require('./console'); +const { createContext } = require('vm'); + +let customBridgeProps = []; + +module.exports = { + /** + * Cleanup existing context - just some quick iterations over common fb/rn/bridge locations + * garbage collection will do the rest. This is probably not needed... + */ + async cleanup() { + if (global.bridge.context) { + if (global.bridge.beforeContextReset) { + await global.bridge.beforeContextReset(); + } + try { + for (const name in global.bridge.context.__fbBatchedBridge) { + global.bridge.context.__fbBatchedBridge[name] = undefined; + delete global.bridge.context.__fbBatchedBridge[name]; + } + + for (const name in global.bridge.context.__fbGenNativeModule) { + global.bridge.context.__fbGenNativeModule[name] = undefined; + delete global.bridge.context.__fbGenNativeModule[name]; + } + + for (const name in global.bridge.context.__fbBatchedBridgeConfig) { + global.bridge.context.__fbBatchedBridgeConfig[name] = undefined; + delete global.bridge.context.__fbBatchedBridgeConfig[name]; + } + + for (const name in global.bridge.context) { + global.bridge.context[name] = undefined; + delete global.bridge.context[name]; + } + } catch (e) { + // do nothing; + } + + global.bridge.context = undefined; + + // clear custom props and reset props track array + for (let i = 0; i < customBridgeProps.length; i++) { + global.bridge[customBridgeProps[i]] = undefined; + delete global.bridge[customBridgeProps[i]]; + } + + customBridgeProps = []; + } + }, + + /** + * Create a new context for a RN app to attach to, we addtionaly provide __bridgeNode for + * the counterpart RN bridge code to attach to and communicate back + */ + create() { + global.bridge.context = createContext({ + console: consoleContext(), + __bridgeNode: { + _ready() { + setTimeout(() => process.emit('bridge-attached'), 5); + }, + + setBridgeProperty(key, value) { + customBridgeProps.push(key); + global.bridge[key] = value; + }, + }, + }); + }, +}; diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js index 53e1cddf..35f3e969 100644 --- a/tests-new/bridge/env/node/index.js +++ b/tests-new/bridge/env/node/index.js @@ -1,55 +1,59 @@ +/* eslint-disable no-param-reassign */ global.bridge = {}; const detox = require('detox'); - -require('./vm'); const ws = require('./ws'); +const ready = require('./ready'); -const detoxOriginalInit = detox.init.bind(detox); -const detoxOriginalCleanup = detox.cleanup.bind(detox); +/* --------------------- + * DEVICE OVERRIDES + * --------------------- */ -let bridgeReady = false; -process.on('rn-ready', () => { - bridgeReady = true; +let device; +Object.defineProperty(global, 'device', { + get() { + return device; + }, + set(originalDevice) { + // device.reloadReactNative({ ... }) + // todo detoxOriginalReloadReactNative currently broken it seems + // const detoxOriginalReloadReactNative = originalDevice.reloadReactNative.bind(originalDevice); + originalDevice.reloadReactNative = async () => { + ready.reset(); + global.bridge.reload(); + return ready.wait(); + }; + + // device.launchApp({ ... }) + const detoxOriginalLaunchApp = originalDevice.launchApp.bind( + originalDevice + ); + originalDevice.launchApp = async (...args) => { + ready.reset(); + await detoxOriginalLaunchApp(...args); + return ready.wait(); + }; + + device = originalDevice; + return originalDevice; + }, }); -function onceBridgeReady() { - if (bridgeReady) return Promise.resolve(); - return new Promise(resolve => { - process.once('rn-ready', resolve); - }); -} - -function shimDevice() { - // reloadReactNative - // todo detoxOriginalReloadReactNative currently broken - // const detoxOriginalReloadReactNative = device.reloadReactNative.bind(device); - device.reloadReactNative = async () => { - bridgeReady = false; - global.bridge.reload(); - return onceBridgeReady(); - }; - - // launchApp - const detoxOriginalLaunchApp = device.launchApp.bind(device); - device.launchApp = async (...args) => { - bridgeReady = false; - await detoxOriginalLaunchApp(...args); - return onceBridgeReady(); - }; - - // todo other device reloading related methods -} +/* ------------------- + * DETOX OVERRIDES + * ------------------- */ +// detox.init() +const detoxOriginalInit = detox.init.bind(detox); detox.init = async (...args) => { - bridgeReady = false; - return detoxOriginalInit(...args).then(() => { - shimDevice(); - return onceBridgeReady(); - }); + ready.reset(); + await detoxOriginalInit(...args); + return ready.wait(); }; -detox.cleanup = async (...args) => - detoxOriginalCleanup(...args).then(() => { - ws.close(); - }); +// detox.cleanup() +const detoxOriginalCleanup = detox.cleanup.bind(detox); +detox.cleanup = async (...args) => { + ws.close(); + await detoxOriginalCleanup(...args); +}; diff --git a/tests-new/bridge/env/node/ready.js b/tests-new/bridge/env/node/ready.js new file mode 100644 index 00000000..97426352 --- /dev/null +++ b/tests-new/bridge/env/node/ready.js @@ -0,0 +1,17 @@ +let ready = false; + +process.on('bridge-attached', () => { + ready = true; +}); + +module.exports = { + wait() { + if (ready) return Promise.resolve(); + return new Promise(resolve => { + process.once('bridge-attached', resolve); + }); + }, + reset() { + ready = false; + }, +}; diff --git a/tests-new/bridge/env/node/ws.js b/tests-new/bridge/env/node/ws.js index 99e26292..5c7829c0 100644 --- a/tests-new/bridge/env/node/ws.js +++ b/tests-new/bridge/env/node/ws.js @@ -1,10 +1,15 @@ +const vm = require('./vm'); const WebSocket = require('ws'); const ws = new WebSocket( 'ws://localhost:8081/debugger-proxy?role=debugger&name=Chrome' ); -ws.onmessage = message => process.emit('ws-message', JSON.parse(message.data)); -ws.onclose = event => (!event.wasClean ? console.log('WS close', event) : ''); +vm.reply = obj => ws.send(JSON.stringify(obj)); + +ws.onmessage = message => vm.message(JSON.parse(message.data)); + +ws.onclose = event => + !event.wasClean ? console.error('Bridge WS Error', event.message) : ''; module.exports = ws; diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index def9c6b2..4bd7d9ce 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -1,43 +1,59 @@ -import reactNative, { Platform, NativeModules } from 'react-native'; +import ReactNative from 'react-native'; import RNRestart from 'react-native-restart'; // Import package from node modules +const { Platform, NativeModules } = ReactNative; + const bridgeNode = global.__bridgeNode; +const INTERNAL_KEYS = ['context', 'rn', 'reload']; // https://github.com/facebook/react-native/blob/master/React/Modules/RCTDevSettings.mm if (Platform.OS === 'ios' && !bridgeNode) { NativeModules.RCTDevSettings.setIsDebuggingRemotely(true); +} else { + if (Platform.OS === 'android' && !bridgeNode) { + // TODO warn to add: + // getReactNativeHost().getReactInstanceManager().getDevSupportManager().getDevSettings().setRemoteJSDebugEnabled(true); + // to MainApplication onCreate + } + + if (bridgeNode) { + if (Platform.OS === 'ios') { + bridgeNode.setBridgeProperty( + 'reload', + NativeModules.RCTDevSettings.reload + ); + } else { + bridgeNode.setBridgeProperty('reload', RNRestart.Restart); + } + + bridgeNode.setBridgeProperty('rn', ReactNative); + + // keep alive + setInterval(() => { + // I don't do anything... + // BUT i am needed - otherwise RN's batched bridge starts to hang in detox... ??? + }, 60); + } } -if (bridgeNode) { - bridgeNode.provideReload(RNRestart.Restart); - bridgeNode.provideReactNativeModule(reactNative); - - // keep alive - setInterval(() => { - // I don't do anything... - // BUT i am needed - otherwise RN's batched bridge starts to hang in detox... ??? - }, 60); -} +let hasInitialized = false; export default { /** - * Makes the main module to be tested accessible to nodejs - * @param moduleExports + * Expose a property in node on the global.bridge object + * @param key + * @param value */ - provideModule(moduleExports) { + setBridgeProperty(key, value) { + if (INTERNAL_KEYS.includes(key)) return; if (bridgeNode) { - bridgeNode.provideModule(moduleExports); - bridgeNode.ready(); - } - }, + bridgeNode.setBridgeProperty(key, value); - /** - * Makes the root component accessible to nodejs - e.g. bridge.root.setState({ ... }); - * @param rootComponent - */ - provideRoot(rootComponent) { - if (bridgeNode) { - bridgeNode.provideRoot(rootComponent); + // notify ready on first setBridgeProp + if (!hasInitialized) { + bridgeNode._ready(); + hasInitialized = true; + } } }, }; diff --git a/tests-new/e2e/bridge.spec.js b/tests-new/e2e/bridge.spec.js index e9b007f9..782d6eb0 100755 --- a/tests-new/e2e/bridge.spec.js +++ b/tests-new/e2e/bridge.spec.js @@ -1,8 +1,9 @@ const should = require('should'); describe('bridge', () => { - beforeEach(async () => { + beforeEach(async function beforeEach() { await device.reloadReactNative(); + bridge.root.setState({ message: this.currentTest.title }); }); it('should provide -> global.bridge', () => { @@ -10,19 +11,49 @@ describe('bridge', () => { return Promise.resolve(); }); - it('should provide -> global.bridge.module', () => { + // main react-native module you're testing on + // in our case react-native-firebase + it('should provide -> bridge.module', () => { should(bridge.module).not.be.undefined(); return Promise.resolve(); }); - it('should provide -> global.bridge.rn', () => { + // react-native module access + it('should provide -> bridge.rn', () => { should(bridge.rn).not.be.undefined(); should(bridge.rn.Platform.OS).be.a.String(); should(bridge.rn.Platform.OS).equal(device.getPlatform()); return Promise.resolve(); }); - it('should provide -> global.reload and allow reloadReactNative usage', async () => { + // 'global' context of the app's JS environment + it('should provide -> bridge.context', () => { + should(bridge.context).not.be.undefined(); + should(bridge.context.setTimeout).be.a.Function(); + should(bridge.context.window).be.a.Object(); + // etc ... e.g. __coverage__ is here also if covering + return Promise.resolve(); + }); + + // the apps root component + // allows you to read and set state if required + it('should provide -> bridge.root', async () => { + should(bridge.root).not.be.undefined(); + should(bridge.root.setState).be.a.Function(); + should(bridge.root.state).be.a.Object(); + + // test setting state + await new Promise(resolve => + bridge.root.setState({ message: 'hello world' }, resolve) + ); + should(bridge.root.state.message).equal('hello world'); + return Promise.resolve(); + }); + + // we shim our own reloadReactNative functionality as the detox reloadReactNative built-in + // hangs often and seems unpredictable - todo: investigate & PR if solution found + // reloadReactNative is replaced on init with bridge.root automatically + it('should allow reloadReactNative usage without breaking remote debug', async () => { should(bridge.reload).be.a.Function(); // and check it works without breaking anything await device.reloadReactNative(); @@ -30,8 +61,15 @@ describe('bridge', () => { return Promise.resolve(); }); - it('should allow detox to launchApp without breaking remote debug', async () => { + it('should allow launchApp usage without breaking remote debug', async () => { + should(bridge.module).not.be.undefined(); + should(bridge.reload).be.a.Function(); + should(bridge.rn).not.be.undefined(); + should(bridge.rn.Platform.OS).be.a.String(); + should(bridge.rn.Platform.OS).equal(device.getPlatform()); + await device.launchApp({ newInstance: true }); + should(bridge.module).not.be.undefined(); should(bridge.reload).be.a.Function(); should(bridge.rn).not.be.undefined(); diff --git a/tests-new/e2e/init.js b/tests-new/e2e/init.js index 0e5f6c4a..6d43f60b 100755 --- a/tests-new/e2e/init.js +++ b/tests-new/e2e/init.js @@ -12,3 +12,7 @@ before(async () => { after(async () => { await detox.cleanup(); }); + +bridge.beforeContextReset = () => { + console.log('reset'); +}; From fcdc63bd747ce98ec7a3c0fd2a51ce2f52fcc126 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 03:34:35 +0100 Subject: [PATCH 20/40] [tests] new test infra - start of bridge cleanup --- tests-new/bridge/env/node/ready.js | 6 +- tests-new/bridge/env/node/vm.js | 322 ++++++++--------------------- tests-new/bridge/env/node/ws.js | 2 +- tests-new/e2e/init.js | 2 +- 4 files changed, 95 insertions(+), 237 deletions(-) diff --git a/tests-new/bridge/env/node/ready.js b/tests-new/bridge/env/node/ready.js index 97426352..e2379012 100644 --- a/tests-new/bridge/env/node/ready.js +++ b/tests-new/bridge/env/node/ready.js @@ -1,8 +1,6 @@ +/* eslint-disable no-return-assign */ let ready = false; - -process.on('bridge-attached', () => { - ready = true; -}); +process.on('bridge-attached', () => (ready = true)); module.exports = { wait() { diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index 24de3ea0..dd0c2437 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -1,254 +1,114 @@ +/* eslint-disable guard-for-in,no-restricted-syntax,no-return-assign */ const url = require('url'); const http = require('http'); const invariant = require('assert'); -const { createContext, Script } = require('vm'); -const ws = require('./ws'); -const { merge } = require('deeps'); +const { Script } = require('vm'); +const context = require('./context'); -global.bridge.context = null; -global.__coverage__ = {}; -let scriptCached = null; +let send; +let bundle; -// this is a dummy file path - without a file name the source map is not used in the vm +const PREPARE = 'prepareJSRuntime'; +const EXECUTE = 'executeApplicationScript'; const TEMP_BUNDLE_PATH = '/tmp/bridge/react-native.js'; -// TODO ----------------------------------------------------------------------- -// TODO ----------------------------------------------------------------------- -// TODO ----------------------------------------------------------------------- -// TODO -// TODO -// TODO -// TODO -// TODO -// TODO -// TODO This is just dirty code created just as a proof of concept -// TODO - need to clean it all up / refactor -// TODO -// TODO -// TODO -// TODO -// TODO -// TODO -// TODO ----------------------------------------------------------------------- -// TODO ----------------------------------------------------------------------- -// TODO ----------------------------------------------------------------------- +function reply(id, result) { + send({ + replyID: id, + result, + }); +} -/** - * - * @param replyId - * @param result - */ -function sendResult(replyID, result) { - ws.send( - JSON.stringify({ - replyID, - result, - }) +function handleError(message) { + throw new Error(message); +} + +async function downloadBundle(bundleUrl) { + const res = await new Promise((resolve, reject) => + http.get(bundleUrl, resolve).on('error', reject) ); + + let buffer = ''; + + res.setEncoding('utf8'); + res.on('data', chunk => (buffer += chunk)); + await new Promise(resolve => res.on('end', resolve)); + + bundle = new Script(buffer, { + timeout: 120000, + displayErrors: true, + filename: TEMP_BUNDLE_PATH, + }); + + return bundle; } -/** - * TODO - * @param message - */ -function sendError(error) { - console.log('error'); - throw error; +async function getBundle(request) { + if (bundle) return bundle; + console.log('Downloading app bundle...'); + + const parsedUrl = url.parse(request.url, true); + invariant(parsedUrl.query); + parsedUrl.query.inlineSourceMap = true; + delete parsedUrl.search; + + return downloadBundle(url.format(parsedUrl)); } -/** - * - * @param src - * @param callback - */ -function getScript(src, callback) { - if (scriptCached) return callback(null, scriptCached); - return http - .get(src, res => { - let buff = ''; +module.exports = { + set send(fn) { + send = fn; + }, - res.setEncoding('utf8'); - - res.on('data', chunk => { - buff += chunk; - }); - - res.on('end', () => { - scriptCached = new Script(buff, { - // lineOffset: -1, - // columnOffset: -1, - timeout: 120000, - displayErrors: true, - produceCachedData: true, - filename: TEMP_BUNDLE_PATH, - }); - - callback(null, scriptCached); - }); - }) - .on('error', err => { - callback(err); - }); -} - -function consoleShim() { - return { - ...console, - log(...args) { - if ( - args[0] && - typeof args[0] === 'string' && - args[0].startsWith('Running application "') - ) { - return; - } - - if ( - args[0] && - typeof args[0] === 'string' && - args[0].startsWith('Deprecated') - ) { - return; - } - console.log(...args); - }, - warn(...args) { - if ( - args[0] && - typeof args[0] === 'string' && - args[0].startsWith('Running application "') - ) { - return; - } - - if ( - args[0] && - typeof args[0] === 'string' && - args[0].startsWith('Deprecated') - ) { - return; - } - console.log(...args); - }, - }; -} - -process.on('ws-message', request => { - // console.log(request.method); - switch (request.method) { - case 'prepareJSRuntime': - if (global.bridge.context) { - // todo __coverage__ not working - mostly empty statements in output - merge(global.__coverage__, global.bridge.context.__coverage__ || {}); - - try { - for (const name in global.bridge.context.__fbBatchedBridge) { - global.bridge.context.__fbBatchedBridge[name] = undefined; - delete global.bridge.context.__fbBatchedBridge[name]; - } - - for (const name in global.bridge.context.__fbGenNativeModule) { - global.bridge.context.__fbGenNativeModule[name] = undefined; - delete global.bridge.context.__fbGenNativeModule[name]; - } - - for (const name in global.bridge.context.__fbBatchedBridgeConfig) { - global.bridge.context.__fbBatchedBridgeConfig[name] = undefined; - delete global.bridge.context.__fbBatchedBridgeConfig[name]; - } - - for (const name in global.bridge.context) { - global.bridge.context[name] = undefined; - delete global.bridge.context[name]; - } - } catch (e) { - console.error(e); - } - } - global.bridge.context = undefined; - global.bridge.context = createContext({ - console: consoleShim(), - __bridgeNode: { - ready() { - process.emit('rn-ready'); - }, - provideReactNativeModule(rnModule) { - global.bridge.rn = undefined; - global.bridge.rn = rnModule; - }, - provideModule(moduleExports) { - global.bridge.module = undefined; - global.bridge.module = moduleExports; - }, - provideReload(reloadFn) { - global.bridge.reload = undefined; - global.bridge.reload = reloadFn; - }, - provideRoot(rootComponent) { - global.bridge.root = undefined; - global.bridge.root = rootComponent; - }, - }, - }); - sendResult(request.id); - return; - - case 'executeApplicationScript': - // Modify the URL to make sure we get the inline source map. - // TODO we shouldn't be reparsing if scriptCached is set - const parsedUrl = url.parse(request.url, /* parseQueryString */ true); - invariant(parsedUrl.query); - parsedUrl.query.inlineSourceMap = true; - delete parsedUrl.search; - // $FlowIssue url.format() does not accept what url.parse() returns. - const scriptUrl = url.format(parsedUrl); - - getScript(scriptUrl, (err, script) => { - if (err != null) { - sendError(`Failed to get script from packager: ${err.message}`); - return; - } + async message(request) { + const { method } = request; + // console.log(request.method); + switch (method) { + case PREPARE: + await context.cleanup(); + context.create(); + reply(request.id); + break; + case EXECUTE: { + const script = await getBundle(request); if (global.bridge.context == null) { - sendError('JS runtime not prepared'); - return; + throw new Error('VM context was not prepared.'); } - if (request.inject) { for (const name in request.inject) { global.bridge.context[name] = JSON.parse(request.inject[name]); } } - - try { - script.runInContext(global.bridge.context, TEMP_BUNDLE_PATH); - } catch (e) { - sendError(e); - } - sendResult(request.id); - }); - - return; - - default: - let returnValue = [[], [], [], 0]; - try { - if ( - global.bridge.context != null && - typeof global.bridge.context.__fbBatchedBridge === 'object' - ) { - returnValue = global.bridge.context.__fbBatchedBridge[ - request.method - ].apply(null, request.arguments); - } - } catch (e) { - if (request.method !== '$disconnected') { - sendError( - `Failed while making a call ${request.method}:::${e.message}` - ); - } - } finally { - sendResult(request.id, JSON.stringify(returnValue)); + script.runInContext(global.bridge.context, { + filename: TEMP_BUNDLE_PATH, + }); + reply(request.id); + break; } - } -}); + + default: { + let returnValue = [[], [], [], 0]; + try { + if ( + global.bridge.context != null && + typeof global.bridge.context.__fbBatchedBridge === 'object' + ) { + returnValue = global.bridge.context.__fbBatchedBridge[method].apply( + null, + request.arguments + ); + } + } catch (e) { + if (method !== '$disconnected') { + handleError( + `Failed while making a call bridge call ${method}::${e.message}` + ); + } + } finally { + reply(request.id, JSON.stringify(returnValue)); + } + } + } + }, +}; diff --git a/tests-new/bridge/env/node/ws.js b/tests-new/bridge/env/node/ws.js index 5c7829c0..aab69dec 100644 --- a/tests-new/bridge/env/node/ws.js +++ b/tests-new/bridge/env/node/ws.js @@ -5,7 +5,7 @@ const ws = new WebSocket( 'ws://localhost:8081/debugger-proxy?role=debugger&name=Chrome' ); -vm.reply = obj => ws.send(JSON.stringify(obj)); +vm.send = obj => ws.send(JSON.stringify(obj)); ws.onmessage = message => vm.message(JSON.parse(message.data)); diff --git a/tests-new/e2e/init.js b/tests-new/e2e/init.js index 6d43f60b..a55ea2da 100755 --- a/tests-new/e2e/init.js +++ b/tests-new/e2e/init.js @@ -14,5 +14,5 @@ after(async () => { }); bridge.beforeContextReset = () => { - console.log('reset'); + // console.dir(bridge.context.__coverage__); }; From 881a118d49bf6e89fe9b2fd6987d9386f9cba147 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 03:41:43 +0100 Subject: [PATCH 21/40] [tests] new test infra - start of bridge cleanup --- tests-new/bridge/env/node/console.js | 11 ++ tests-new/bridge/env/node/context.js | 2 +- tests-new/package-lock.json | 170 +++++++++++++++++++++++++-- tests-new/package.json | 1 + 4 files changed, 170 insertions(+), 14 deletions(-) diff --git a/tests-new/bridge/env/node/console.js b/tests-new/bridge/env/node/console.js index 105df4fb..0e06441a 100644 --- a/tests-new/bridge/env/node/console.js +++ b/tests-new/bridge/env/node/console.js @@ -1,3 +1,5 @@ +const chalk = require('chalk'); + module.exports = function consoleContext() { return { ...console, @@ -17,5 +19,14 @@ module.exports = function consoleContext() { console.log(...args); }, + + warn(...args) { + console.log( + ...[ + '⚠️ ', + ...args.map(a => (typeof a === 'string' ? chalk.yellowBright(a) : a)), + ] + ); + }, }; }; diff --git a/tests-new/bridge/env/node/context.js b/tests-new/bridge/env/node/context.js index 683e4cf6..e2fdc897 100644 --- a/tests-new/bridge/env/node/context.js +++ b/tests-new/bridge/env/node/context.js @@ -61,7 +61,7 @@ module.exports = { console: consoleContext(), __bridgeNode: { _ready() { - setTimeout(() => process.emit('bridge-attached'), 5); + setTimeout(() => process.emit('bridge-attached'), 1); }, setBridgeProperty(key, value) { diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index 7fe5e53a..b3acb575 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -90,9 +90,12 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "1.9.1" + } }, "ansi-wrap": { "version": "0.1.0", @@ -583,6 +586,30 @@ "chalk": "1.1.3", "esutils": "2.0.2", "js-tokens": "3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } } }, "babel-core": { @@ -1731,15 +1758,13 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", "requires": { - "ansi-styles": "2.2.1", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "supports-color": "5.3.0" } }, "chardet": { @@ -2842,6 +2867,25 @@ "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", "dev": true }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", @@ -2933,6 +2977,12 @@ "strip-ansi": "3.0.1" } }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, "user-home": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", @@ -4461,10 +4511,32 @@ "vinyl": "0.5.3" }, "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, "object-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" } } }, @@ -5589,6 +5661,11 @@ "yargs": "9.0.1" }, "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, "babel-preset-react-native": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/babel-preset-react-native/-/babel-preset-react-native-4.0.0.tgz", @@ -5627,6 +5704,18 @@ "react-transform-hmr": "1.0.4" } }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, "connect": { "version": "3.6.6", "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", @@ -5680,6 +5769,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6759,6 +6853,28 @@ "yargs": "9.0.1" }, "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, "whatwg-fetch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz", @@ -8131,9 +8247,12 @@ "dev": true }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "requires": { + "has-flag": "3.0.0" + } }, "table": { "version": "3.8.3", @@ -8158,6 +8277,31 @@ "co": "4.6.0", "json-stable-stringify": "1.0.1" } + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true } } }, diff --git a/tests-new/package.json b/tests-new/package.json index ad88d3c0..63c8e7ee 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -15,6 +15,7 @@ "dependencies": { "babel-preset-es2015-mod": "^6.6.0", "babel-preset-es3": "^1.0.1", + "chalk": "^2.3.2", "deeps": "^1.4.4", "detox": "^7.2.0", "mocha": "^4.0.1", From 2de9626e7436db9e9930a318d46e3d760a1decac Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 03:49:52 +0100 Subject: [PATCH 22/40] [tests] new test infra - start of bridge cleanup --- tests-new/bridge/env/node/vm.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index dd0c2437..a9e30545 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -1,6 +1,7 @@ /* eslint-disable guard-for-in,no-restricted-syntax,no-return-assign */ const url = require('url'); const http = require('http'); +const chalk = require('chalk'); const invariant = require('assert'); const { Script } = require('vm'); const context = require('./context'); @@ -45,7 +46,13 @@ async function downloadBundle(bundleUrl) { async function getBundle(request) { if (bundle) return bundle; - console.log('Downloading app bundle...'); + console.log(''); + console.log( + `${chalk.blue( + '[bridge]' + )} debugger has connected! Downloading app JS bundle...` + ); + console.log(''); const parsedUrl = url.parse(request.url, true); invariant(parsedUrl.query); From 8c4a0261cd7d4ce8df6ac12719aa557410ed6eac Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 03:50:10 +0100 Subject: [PATCH 23/40] [tests] new test infra - start of bridge cleanup --- tests-new/bridge/env/node/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-new/bridge/env/node/context.js b/tests-new/bridge/env/node/context.js index e2fdc897..daa050a7 100644 --- a/tests-new/bridge/env/node/context.js +++ b/tests-new/bridge/env/node/context.js @@ -53,7 +53,7 @@ module.exports = { }, /** - * Create a new context for a RN app to attach to, we addtionaly provide __bridgeNode for + * Create a new context for a RN app to attach to, we additionally provide __bridgeNode for * the counterpart RN bridge code to attach to and communicate back */ create() { From 2fa3ee006d7caf80607ad6c432815fdc5674513b Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 06:52:30 +0100 Subject: [PATCH 24/40] [tests] new test infra - coverage for days --- tests-new/.babelrc | 4 +- tests-new/bridge/env/node/context.js | 35 +- tests-new/bridge/env/node/coverage.js | 19 + tests-new/bridge/env/node/index.js | 14 +- tests-new/bridge/env/node/vm.js | 2 + tests-new/e2e/auth/module.e2e.js | 49 +- tests-new/e2e/init.js | 19 +- tests-new/e2e/mocha.opts | 3 +- tests-new/package-lock.json | 2783 ++++++++++++++++++++++++- tests-new/package.json | 36 +- 10 files changed, 2899 insertions(+), 65 deletions(-) create mode 100644 tests-new/bridge/env/node/coverage.js diff --git a/tests-new/.babelrc b/tests-new/.babelrc index 0c5dcafa..160a9c97 100644 --- a/tests-new/.babelrc +++ b/tests-new/.babelrc @@ -6,8 +6,10 @@ "development": { "plugins": [ ["istanbul", { + "useInlineSourceMaps": true, + "instrument": true, "include": [ - "**/firebase/**.js" + "firebase" ] }] ] diff --git a/tests-new/bridge/env/node/context.js b/tests-new/bridge/env/node/context.js index daa050a7..d096e068 100644 --- a/tests-new/bridge/env/node/context.js +++ b/tests-new/bridge/env/node/context.js @@ -16,28 +16,25 @@ module.exports = { if (global.bridge.beforeContextReset) { await global.bridge.beforeContextReset(); } - try { - for (const name in global.bridge.context.__fbBatchedBridge) { - global.bridge.context.__fbBatchedBridge[name] = undefined; - delete global.bridge.context.__fbBatchedBridge[name]; - } - for (const name in global.bridge.context.__fbGenNativeModule) { - global.bridge.context.__fbGenNativeModule[name] = undefined; - delete global.bridge.context.__fbGenNativeModule[name]; - } + for (const name in global.bridge.context.__fbBatchedBridge) { + global.bridge.context.__fbBatchedBridge[name] = undefined; + delete global.bridge.context.__fbBatchedBridge[name]; + } - for (const name in global.bridge.context.__fbBatchedBridgeConfig) { - global.bridge.context.__fbBatchedBridgeConfig[name] = undefined; - delete global.bridge.context.__fbBatchedBridgeConfig[name]; - } + for (const name in global.bridge.context.__fbGenNativeModule) { + global.bridge.context.__fbGenNativeModule[name] = undefined; + delete global.bridge.context.__fbGenNativeModule[name]; + } - for (const name in global.bridge.context) { - global.bridge.context[name] = undefined; - delete global.bridge.context[name]; - } - } catch (e) { - // do nothing; + for (const name in global.bridge.context.__fbBatchedBridgeConfig) { + global.bridge.context.__fbBatchedBridgeConfig[name] = undefined; + delete global.bridge.context.__fbBatchedBridgeConfig[name]; + } + + for (const name in global.bridge.context) { + global.bridge.context[name] = undefined; + delete global.bridge.context[name]; } global.bridge.context = undefined; diff --git a/tests-new/bridge/env/node/coverage.js b/tests-new/bridge/env/node/coverage.js new file mode 100644 index 00000000..aa949cd5 --- /dev/null +++ b/tests-new/bridge/env/node/coverage.js @@ -0,0 +1,19 @@ +const { createCoverageMap } = require('istanbul-lib-coverage'); + +const rootMap = createCoverageMap({}); + +module.exports = { + collect() { + if (bridge.context && bridge.context.__coverage__) { + rootMap.merge(Object.assign({}, bridge.context.__coverage__)); + global.__coverage__ = rootMap.toJSON(); + } + }, + summary() { + return rootMap.getCoverageSummary(); + }, + + json() { + return rootMap.toJSON(); + }, +}; diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js index 35f3e969..56dab45e 100644 --- a/tests-new/bridge/env/node/index.js +++ b/tests-new/bridge/env/node/index.js @@ -4,6 +4,7 @@ global.bridge = {}; const detox = require('detox'); const ws = require('./ws'); const ready = require('./ready'); +const coverage = require('./coverage'); /* --------------------- * DEVICE OVERRIDES @@ -54,6 +55,17 @@ detox.init = async (...args) => { // detox.cleanup() const detoxOriginalCleanup = detox.cleanup.bind(detox); detox.cleanup = async (...args) => { - ws.close(); + try { + ws.close(); + } catch (e) { + // do nothing + } await detoxOriginalCleanup(...args); }; + +// setup after hook to ensure final context coverage is captured +process.nextTick(() => { + after(() => { + coverage.collect(); + }); +}); diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index a9e30545..f8de7c81 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -5,6 +5,7 @@ const chalk = require('chalk'); const invariant = require('assert'); const { Script } = require('vm'); const context = require('./context'); +const coverage = require('./coverage'); let send; let bundle; @@ -72,6 +73,7 @@ module.exports = { // console.log(request.method); switch (method) { case PREPARE: + coverage.collect(); await context.cleanup(); context.create(); reply(request.id); diff --git a/tests-new/e2e/auth/module.e2e.js b/tests-new/e2e/auth/module.e2e.js index 4f4b09bc..0f0397b5 100644 --- a/tests-new/e2e/auth/module.e2e.js +++ b/tests-new/e2e/auth/module.e2e.js @@ -1,8 +1,7 @@ describe('.auth()', () => { - beforeEach(async function beforeEach() { + beforeEach(async () => { await device.reloadReactNative(); - // just an example of setting the root components state from inside a test :) - bridge.root.setState({ message: this.currentTest.title }); + // bridge.root.setState({ message: this.currentTest.title }); }); describe('.signInAnonymously()', () => { @@ -15,12 +14,12 @@ describe('.auth()', () => { currentUser.isAnonymous.should.equal(true); currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(bridge.module.auth().currentUser); + currentUser.should.equal(firebase.auth().currentUser); - return bridge.module.auth().signOut(); + return firebase.auth().signOut(); }; - return bridge.module + return firebase .auth() .signInAnonymously() .then(successCb); @@ -37,15 +36,15 @@ describe('.auth()', () => { should.equal(currentUser.toJSON().email, null); currentUser.isAnonymous.should.equal(true); currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(bridge.module.auth().currentUser); + currentUser.should.equal(firebase.auth().currentUser); const { additionalUserInfo } = currentUserCredential; additionalUserInfo.should.be.an.Object(); - return bridge.module.auth().signOut(); + return firebase.auth().signOut(); }; - return bridge.module + return firebase .auth() .signInAnonymouslyAndRetrieveData() .then(successCb); @@ -64,12 +63,12 @@ describe('.auth()', () => { currentUser.toJSON().email.should.eql('test@test.com'); currentUser.isAnonymous.should.equal(false); currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(bridge.module.auth().currentUser); + currentUser.should.equal(firebase.auth().currentUser); - return bridge.module.auth().signOut(); + return firebase.auth().signOut(); }; - return bridge.module + return firebase .auth() .signInWithEmailAndPassword(email, pass) .then(successCb); @@ -89,7 +88,7 @@ describe('.auth()', () => { return Promise.resolve(); }; - return bridge.module + return firebase .auth() .signInWithEmailAndPassword(email, pass) .then(successCb) @@ -110,7 +109,7 @@ describe('.auth()', () => { return Promise.resolve(); }; - return bridge.module + return firebase .auth() .signInWithEmailAndPassword(email, pass) .then(successCb) @@ -131,7 +130,7 @@ describe('.auth()', () => { return Promise.resolve(); }; - return bridge.module + return firebase .auth() .signInWithEmailAndPassword(email, pass) .then(successCb) @@ -141,25 +140,25 @@ describe('.auth()', () => { describe('.onAuthStateChanged()', () => { it('calls callback with the current user and when auth state changes', async () => { - await bridge.module.auth().signInAnonymouslyAndRetrieveData(); + await firebase.auth().signInAnonymouslyAndRetrieveData(); // Test const callback = sinon.spy(); let unsubscribe; await new Promise(resolve => { - unsubscribe = bridge.module.auth().onAuthStateChanged(user => { + unsubscribe = firebase.auth().onAuthStateChanged(user => { callback(user); resolve(); }); }); - callback.should.be.calledWith(bridge.module.auth().currentUser); + callback.should.be.calledWith(firebase.auth().currentUser); callback.should.be.calledOnce(); // Sign out - await bridge.module.auth().signOut(); + await firebase.auth().signOut(); await new Promise(resolve => { setTimeout(() => resolve(), 100); @@ -176,25 +175,25 @@ describe('.auth()', () => { }); it('stops listening when unsubscribe called', async () => { - await bridge.module.auth().signInAnonymouslyAndRetrieveData(); + await firebase.auth().signInAnonymouslyAndRetrieveData(); // Test const callback = sinon.spy(); let unsubscribe; await new Promise(resolve => { - unsubscribe = bridge.module.auth().onAuthStateChanged(user => { + unsubscribe = firebase.auth().onAuthStateChanged(user => { callback(user); resolve(); }); }); - callback.should.be.calledWith(bridge.module.auth().currentUser); + callback.should.be.calledWith(firebase.auth().currentUser); callback.should.be.calledOnce(); // Sign out - await bridge.module.auth().signOut(); + await firebase.auth().signOut(); await new Promise(resolve => { setTimeout(() => resolve(), 100); @@ -211,7 +210,7 @@ describe('.auth()', () => { // Sign back in - await bridge.module.auth().signInAnonymouslyAndRetrieveData(); + await firebase.auth().signInAnonymouslyAndRetrieveData(); // Assertions @@ -219,7 +218,7 @@ describe('.auth()', () => { // Tear down - await bridge.module.auth().signOut(); + await firebase.auth().signOut(); }); }); }); diff --git a/tests-new/e2e/init.js b/tests-new/e2e/init.js index a55ea2da..0ec9edc7 100755 --- a/tests-new/e2e/init.js +++ b/tests-new/e2e/init.js @@ -13,6 +13,19 @@ after(async () => { await detox.cleanup(); }); -bridge.beforeContextReset = () => { - // console.dir(bridge.context.__coverage__); -}; +// bridge.beforeContextReset = () => { +// console.log('hello'); +// }; + +Object.defineProperty(global, 'firebase', { + get() { + return bridge.module; + }, + set() { + // do nothing + }, +}); + +// Object.defineProperty(global, 'firebase', { value: undefined }); + +// delete global.firebase; diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index de4b6043..503e304c 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -1,6 +1,7 @@ --recursive --timeout 120000 ---slow 1400 +--slow 1 --bail --exit +--grep auth --require ./bridge/env/node diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index b3acb575..33d32686 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -807,7 +807,7 @@ "dev": true, "requires": { "babel-core": "6.26.0", - "babel-plugin-istanbul": "4.1.6", + "babel-plugin-istanbul": "4.1.5", "babel-preset-jest": "19.0.0" } }, @@ -848,12 +848,11 @@ } }, "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz", + "integrity": "sha1-Z2DN2Xf0EdPhdbsGTyvDJ9mbK24=", "dev": true, "requires": { - "babel-plugin-syntax-object-rest-spread": "6.13.0", "find-up": "2.1.0", "istanbul-lib-instrument": "1.10.1", "test-exclude": "4.2.1" @@ -5149,8 +5148,7 @@ "istanbul-lib-coverage": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", - "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", - "dev": true + "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==" }, "istanbul-lib-instrument": { "version": "1.10.1", @@ -6154,6 +6152,2777 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "nyc": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.6.0.tgz", + "integrity": "sha512-ZaXCh0wmbk2aSBH2B5hZGGvK2s9aM8DIm2rVY+BG3Fx8tUS+bpJSswUVZqOD1YfCmnYRFSqgYJSr7UeeUcW0jg==", + "dev": true, + "requires": { + "archy": "1.0.0", + "arrify": "1.0.1", + "caching-transform": "1.0.1", + "convert-source-map": "1.5.1", + "debug-log": "1.0.1", + "default-require-extensions": "1.0.0", + "find-cache-dir": "0.1.1", + "find-up": "2.1.0", + "foreground-child": "1.5.6", + "glob": "7.1.2", + "istanbul-lib-coverage": "1.2.0", + "istanbul-lib-hook": "1.1.0", + "istanbul-lib-instrument": "1.10.1", + "istanbul-lib-report": "1.1.3", + "istanbul-lib-source-maps": "1.2.3", + "istanbul-reports": "1.3.0", + "md5-hex": "1.3.0", + "merge-source-map": "1.1.0", + "micromatch": "2.3.11", + "mkdirp": "0.5.1", + "resolve-from": "2.0.0", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "spawn-wrap": "1.4.2", + "test-exclude": "4.2.1", + "yargs": "11.1.0", + "yargs-parser": "8.1.0" + }, + "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "atob": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.5", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.5" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.3", + "lodash": "4.17.5" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.5", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "base": { + "version": "0.11.2", + "bundled": true, + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "bundled": true, + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "class-utils": { + "version": "0.3.6", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.3", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.2", + "which": "1.3.0" + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } + }, + "define-property": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.2", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "bundled": true, + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "extend-shallow": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "extglob": { + "version": "0.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "bundled": true, + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "for-own": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "signal-exit": "3.0.2" + } + }, + "fragment-cache": { + "version": "0.2.1", + "bundled": true, + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "get-value": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "has-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "hosted-git-info": { + "version": "2.6.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.3", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-odd": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "bundled": true, + "dev": true + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "6.26.1", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.2.0", + "semver": "5.5.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.3", + "bundled": true, + "dev": true, + "requires": { + "debug": "3.1.0", + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "4.0.11" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "lodash": { + "version": "4.17.5", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "md5-o-matic": "0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "micromatch": { + "version": "2.3.11", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "nanomatch": { + "version": "1.2.9", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "2.6.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" + } + }, + "normalize-path": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "object.omit": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "parse-glob": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "pascalcase": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "preserve": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "regex-cache": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "ret": { + "version": "0.1.15", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-regex": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "set-value": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "atob": "2.0.3", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "1.5.6", + "mkdirp": "0.5.1", + "os-homedir": "1.0.2", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "which": "1.3.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "split-string": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + } + }, + "static-extend": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "3.1.9", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "braces": { + "version": "2.3.1", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "kind-of": "6.0.2", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + }, + "micromatch": { + "version": "3.1.9", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "to-regex": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + } + } + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "union-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "set-value": { + "version": "0.4.3", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "bundled": true, + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "bundled": true, + "dev": true + }, + "use": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.1.0", + "bundled": true, + "dev": true, + "requires": { + "cliui": "4.0.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "8.1.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + } + } + } + } + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", diff --git a/tests-new/package.json b/tests-new/package.json index 63c8e7ee..ff49e9c1 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -3,13 +3,20 @@ "version": "7.2.0", "private": true, "scripts": { - "start-local-debug": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow", - "start": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow", - "start-ci": "REACT_DEBUGGER='todo' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --skipflow --nonPersistent", - "android:dev": "react-native run-android", - "android:prod": "react-native run-android --configuration=release", - "ios:dev": "react-native run-ios", - "ios:prod": "react-native run-ios --configuration=release", + "packager-chrome": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android", + "packager-bridge": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android", + + "build-android": "detox build --configuration android.emu.debug", + + "test": "npm run test-android && test-ios", + + "test-android": "detox test --configuration android.emu.debug", + "test-android-reuse": "detox test --configuration android.emu.debug --reuse", + "test-android-cover": "nyc detox test --configuration android.emu.debug", + "test-android-cover-reuse": "nyc detox test --configuration android.emu.debug --reuse", + + "test-ios": "detox test --configuration ios.sim.debug", + "test-ios-cover": "nyc detox test --configuration ios.sim.debug", "ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .." }, "dependencies": { @@ -18,6 +25,7 @@ "chalk": "^2.3.2", "deeps": "^1.4.4", "detox": "^7.2.0", + "istanbul-lib-coverage": "^1.2.0", "mocha": "^4.0.1", "react": "^16.2.0", "react-native": "^0.52.3", @@ -39,7 +47,19 @@ "eslint-plugin-flowtype": "^2.46.1", "eslint-plugin-import": "^2.9.0", "eslint-plugin-jsx-a11y": "^4.0.0", - "eslint-plugin-react": "^6.10.0" + "eslint-plugin-react": "^6.10.0", + "nyc": "^11.6.0" + }, + "nyc": { + "check-coverage": false, + "lines": 95, + "statements": 95, + "functions": 95, + "branches": 95, + "include": ["firebase"], + "sourceMap": false, + "instrument": false, + "reporter": ["lcov", "text-summary"] }, "detox": { "test-runner": "mocha", From ebdea3d68ffc96609e2e393b8452aeb05cafa9fd Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 06:53:20 +0100 Subject: [PATCH 25/40] [tests] new test infra - coverage for days --- tests-new/e2e/mocha.opts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index 503e304c..fdb7df9f 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -1,7 +1,6 @@ --recursive --timeout 120000 ---slow 1 +--slow 2000 --bail --exit ---grep auth --require ./bridge/env/node From 1572aa10a2e25e35e705ed384f4c9eae2f138fab Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 07:45:19 +0100 Subject: [PATCH 26/40] [tests] new test infra --- tests-new/e2e/firestore/transactions.e2e.js | 1 - tests-new/e2e/init.js | 11 ----------- tests-new/e2e/mocha.opts | 2 +- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/tests-new/e2e/firestore/transactions.e2e.js b/tests-new/e2e/firestore/transactions.e2e.js index f6d716b1..9d8c625c 100644 --- a/tests-new/e2e/firestore/transactions.e2e.js +++ b/tests-new/e2e/firestore/transactions.e2e.js @@ -6,7 +6,6 @@ describe('firestore.runTransaction', () => { }); it('should set, update and delete transactionally and allow a return value', async () => { - const firebase = bridge.module; let deleteMe = false; const firestore = firebase.firestore(); diff --git a/tests-new/e2e/init.js b/tests-new/e2e/init.js index 0ec9edc7..c54e28f9 100755 --- a/tests-new/e2e/init.js +++ b/tests-new/e2e/init.js @@ -13,19 +13,8 @@ after(async () => { await detox.cleanup(); }); -// bridge.beforeContextReset = () => { -// console.log('hello'); -// }; - Object.defineProperty(global, 'firebase', { get() { return bridge.module; }, - set() { - // do nothing - }, }); - -// Object.defineProperty(global, 'firebase', { value: undefined }); - -// delete global.firebase; diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index fdb7df9f..bd8c68a6 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -1,6 +1,6 @@ --recursive --timeout 120000 ---slow 2000 +--slow 2200 --bail --exit --require ./bridge/env/node From c69c6b9082d295cca65d4f9170240ed6b2d2a323 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 19:24:18 +0100 Subject: [PATCH 27/40] [tests][bridge] added device time drift checks --- tests-new/bridge/env/node/context.js | 30 ++++++++++++++++++++++++++++ tests-new/bridge/env/node/vm.js | 2 -- tests-new/bridge/env/rn.js | 4 ++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tests-new/bridge/env/node/context.js b/tests-new/bridge/env/node/context.js index d096e068..0c81678e 100644 --- a/tests-new/bridge/env/node/context.js +++ b/tests-new/bridge/env/node/context.js @@ -3,8 +3,10 @@ global.bridge.context = null; const consoleContext = require('./console'); const { createContext } = require('vm'); +const chalk = require('chalk'); let customBridgeProps = []; +let driftCheckStart = null; module.exports = { /** @@ -58,6 +60,34 @@ module.exports = { console: consoleContext(), __bridgeNode: { _ready() { + if (!driftCheckStart) { + driftCheckStart = Date.now(); + global.bridge.context.__driftCheck(1); + } else { + setTimeout(() => process.emit('bridge-attached'), 1); + } + }, + + _callbackDriftCheck() { + const timeTaken = Date.now() - driftCheckStart; + if (timeTaken > 5000) { + console.log( + `${chalk.blue( + '[bridge] ⚠️ ' + )} It looks like there's an issue with device timer performance...` + ); + console.log( + `${chalk.blue( + '[bridge] ⚠️ ' + )} You may experience slow testing times as a result - ensure your device date/time correctly matches your debugger machine.` + ); + // todo android only + // fake the RN warning for this + global.bridge.context.console.warn( + `Debugger and device times have drifted. ` + + `Please correct this by running adb shell "date \`date +%m%d%H%M%Y.%S\`" on your debugger machine.` + ); + } setTimeout(() => process.emit('bridge-attached'), 1); }, diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index f8de7c81..ac95ea36 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -47,13 +47,11 @@ async function downloadBundle(bundleUrl) { async function getBundle(request) { if (bundle) return bundle; - console.log(''); console.log( `${chalk.blue( '[bridge]' )} debugger has connected! Downloading app JS bundle...` ); - console.log(''); const parsedUrl = url.parse(request.url, true); invariant(parsedUrl.query); diff --git a/tests-new/bridge/env/rn.js b/tests-new/bridge/env/rn.js index 4bd7d9ce..df32549d 100644 --- a/tests-new/bridge/env/rn.js +++ b/tests-new/bridge/env/rn.js @@ -36,6 +36,10 @@ if (Platform.OS === 'ios' && !bridgeNode) { } } +global.__driftCheck = delay => { + setTimeout(bridgeNode._callbackDriftCheck, delay); +}; + let hasInitialized = false; export default { From c9b313f23c2665e14b9a4f8c44bba8be80cadd6c Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 21:17:26 +0100 Subject: [PATCH 28/40] [tests][bridge] added source map support / stack trace conversion --- tests-new/bridge/env/node/coverage.js | 8 ++- tests-new/bridge/env/node/index.js | 1 + tests-new/bridge/env/node/source-map.js | 54 +++++++++++++++ tests-new/bridge/env/node/vm.js | 31 ++++++--- tests-new/package-lock.json | 88 +++++++++++++++++++++++-- tests-new/package.json | 8 +-- 6 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 tests-new/bridge/env/node/source-map.js diff --git a/tests-new/bridge/env/node/coverage.js b/tests-new/bridge/env/node/coverage.js index aa949cd5..bed58fcd 100644 --- a/tests-new/bridge/env/node/coverage.js +++ b/tests-new/bridge/env/node/coverage.js @@ -5,8 +5,12 @@ const rootMap = createCoverageMap({}); module.exports = { collect() { if (bridge.context && bridge.context.__coverage__) { - rootMap.merge(Object.assign({}, bridge.context.__coverage__)); - global.__coverage__ = rootMap.toJSON(); + try { + rootMap.merge(Object.assign({}, bridge.context.__coverage__)); + global.__coverage__ = rootMap.toJSON(); + } catch (e) { + // ignore + } } }, summary() { diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js index 56dab45e..de6a5a4a 100644 --- a/tests-new/bridge/env/node/index.js +++ b/tests-new/bridge/env/node/index.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ global.bridge = {}; +require('./source-map'); const detox = require('detox'); const ws = require('./ws'); diff --git a/tests-new/bridge/env/node/source-map.js b/tests-new/bridge/env/node/source-map.js new file mode 100644 index 00000000..17fac1e1 --- /dev/null +++ b/tests-new/bridge/env/node/source-map.js @@ -0,0 +1,54 @@ +/* eslint-disable no-param-reassign */ +const Mocha = require('mocha'); +const { parse } = require('stacktrace-parser'); +const { SourceMapConsumer } = require('source-map'); + +let bundleFileName = null; +const Runner = Mocha.Runner; +let sourceMapConsumer = null; +const originalFail = Runner.prototype.fail; + +/** + * Convert an error frame into a source mapped string + * @param parsed + * @returns {string} + */ +function frameToStr(parsed) { + const { name, line, column, source } = sourceMapConsumer.originalPositionFor({ + line: parsed.lineNumber, + column: parsed.column, + }); + return ` at ${name || parsed.methodName} (${source || + parsed.file}:${line || parsed.lineNumber}:${column || parsed.column})`; +} + +// override mocha fail so we can replace stack traces +Runner.prototype.fail = function fail(test, error) { + console.dir(error); + const original = error.stack.split('\n'); + const parsed = parse(error.stack); + + const newStack = [original[0]]; + + for (let i = 0; i < parsed.length; i++) { + const { file } = parsed[i]; + if (file === bundleFileName) newStack.push(frameToStr(parsed[i])); + else newStack.push(original[i + 1]); + } + + error.stack = newStack.join('\n'); + return originalFail.call(this, test, error); +}; + +module.exports = { + /** + * Build a source map consumer from source map bundle contents + * @param str + * @param fileName + * @returns {Promise} + */ + async buildSourceMap(str, fileName) { + bundleFileName = fileName; + sourceMapConsumer = await new SourceMapConsumer(str); + }, +}; diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index ac95ea36..5b4e6157 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -6,13 +6,14 @@ const invariant = require('assert'); const { Script } = require('vm'); const context = require('./context'); const coverage = require('./coverage'); +const { buildSourceMap } = require('./source-map'); let send; let bundle; const PREPARE = 'prepareJSRuntime'; +const BUNDLE_FILE_NAME = 'app.bundle.js'; const EXECUTE = 'executeApplicationScript'; -const TEMP_BUNDLE_PATH = '/tmp/bridge/react-native.js'; function reply(id, result) { send({ @@ -25,23 +26,35 @@ function handleError(message) { throw new Error(message); } -async function downloadBundle(bundleUrl) { +async function downloadUrl(url) { const res = await new Promise((resolve, reject) => - http.get(bundleUrl, resolve).on('error', reject) + http.get(url, resolve).on('error', reject) ); let buffer = ''; - res.setEncoding('utf8'); res.on('data', chunk => (buffer += chunk)); await new Promise(resolve => res.on('end', resolve)); + return buffer; +} - bundle = new Script(buffer, { +async function downloadBundle(bundleUrl) { + const bundleStr = await downloadUrl(bundleUrl); + + bundle = new Script(bundleStr, { timeout: 120000, displayErrors: true, - filename: TEMP_BUNDLE_PATH, + filename: BUNDLE_FILE_NAME, }); + const sourceMapUrl = bundleStr + .slice(bundleStr.lastIndexOf('\n')) + .replace('//# sourceMappingURL=', ''); + + const sourceMapSource = await downloadUrl(sourceMapUrl); + + await buildSourceMap(sourceMapSource, BUNDLE_FILE_NAME); + return bundle; } @@ -58,6 +71,7 @@ async function getBundle(request) { parsedUrl.query.inlineSourceMap = true; delete parsedUrl.search; + console.log(url.format(parsedUrl)); return downloadBundle(url.format(parsedUrl)); } @@ -87,9 +101,8 @@ module.exports = { global.bridge.context[name] = JSON.parse(request.inject[name]); } } - script.runInContext(global.bridge.context, { - filename: TEMP_BUNDLE_PATH, - }); + + script.runInContext(global.bridge.context, BUNDLE_FILE_NAME); reply(request.id); break; } diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index 33d32686..a192cc33 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -576,6 +576,14 @@ "slash": "1.0.0", "source-map": "0.5.7", "v8flags": "2.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "babel-code-frame": { @@ -636,6 +644,13 @@ "private": "0.1.8", "slash": "1.0.0", "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "babel-eslint": { @@ -663,6 +678,13 @@ "lodash": "4.17.5", "source-map": "0.5.7", "trim-right": "1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -1447,6 +1469,21 @@ "version": "2.5.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "requires": { + "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } } } }, @@ -2675,6 +2712,14 @@ "is-arrayish": "0.2.1" } }, + "error-stack-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.1.tgz", + "integrity": "sha1-oyArj7AxFKqbQKDjZp5IsrZaAQo=", + "requires": { + "stackframe": "1.0.4" + } + }, "errorhandler": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", @@ -5762,6 +5807,11 @@ "mime-db": "1.23.0" } }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, "statuses": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", @@ -5802,6 +5852,13 @@ "integrity": "sha512-12WEgolY5CGvHeHkF5QlM2qatdQC1DyjWkXLK9LzCqzd8YhUZww1+ZCM6E67rJwpeuCU9o1Mkiwd1h7dS+RBvA==", "requires": { "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "micromatch": { @@ -10728,6 +10785,11 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -10773,9 +10835,9 @@ } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.2.tgz", + "integrity": "sha512-NDJB/R2BS7YJG0tP9SbE4DKwKj1idLT5RJqfVYZ7dreFX7wulZT3xxVhbYKrQo9n0JkRptl51TrX/5VK3HodMA==" }, "source-map-resolve": { "version": "0.5.1", @@ -10790,11 +10852,18 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", "requires": { - "source-map": "0.5.7" + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } } }, "source-map-url": { @@ -10864,6 +10933,11 @@ "tweetnacl": "0.14.5" } }, + "stackframe": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.4.tgz", + "integrity": "sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==" + }, "stacktrace-parser": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.4.tgz", diff --git a/tests-new/package.json b/tests-new/package.json index ff49e9c1..c4039624 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -4,17 +4,13 @@ "private": true, "scripts": { "packager-chrome": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android", - "packager-bridge": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android", - + "packager-bridge": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --reset-cache", "build-android": "detox build --configuration android.emu.debug", - "test": "npm run test-android && test-ios", - "test-android": "detox test --configuration android.emu.debug", "test-android-reuse": "detox test --configuration android.emu.debug --reuse", "test-android-cover": "nyc detox test --configuration android.emu.debug", "test-android-cover-reuse": "nyc detox test --configuration android.emu.debug --reuse", - "test-ios": "detox test --configuration ios.sim.debug", "test-ios-cover": "nyc detox test --configuration ios.sim.debug", "ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .." @@ -33,6 +29,8 @@ "should": "^13.2.1", "should-sinon": "0.0.6", "sinon": "^4.4.8", + "source-map": "^0.7.2", + "stacktrace-parser": "^0.1.4", "ws": "^5.1.0" }, "devDependencies": { From c8e3be4388c993b2715e7cc338c9bd84729cdca9 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 22:06:28 +0100 Subject: [PATCH 29/40] [tests][bridge] added source map support / stack trace conversion --- tests-new/bridge/env/node/source-map.js | 14 +++++++------- tests-new/bridge/env/node/vm.js | 2 -- tests-new/firebase/modules/auth/User.js | 1 + tests-new/package-lock.json | 15 --------------- tests-new/package.json | 2 +- 5 files changed, 9 insertions(+), 25 deletions(-) diff --git a/tests-new/bridge/env/node/source-map.js b/tests-new/bridge/env/node/source-map.js index 17fac1e1..20361866 100644 --- a/tests-new/bridge/env/node/source-map.js +++ b/tests-new/bridge/env/node/source-map.js @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ const Mocha = require('mocha'); -const { parse } = require('stacktrace-parser'); +const ErrorStack = require('error-stack-parser'); const { SourceMapConsumer } = require('source-map'); let bundleFileName = null; @@ -18,21 +18,21 @@ function frameToStr(parsed) { line: parsed.lineNumber, column: parsed.column, }); - return ` at ${name || parsed.methodName} (${source || - parsed.file}:${line || parsed.lineNumber}:${column || parsed.column})`; + return ` at ${name || parsed.functionName || ''} (${source || + parsed.fileName}:${line || parsed.lineNumber}:${column || + parsed.columnNumber})`; } // override mocha fail so we can replace stack traces Runner.prototype.fail = function fail(test, error) { - console.dir(error); const original = error.stack.split('\n'); - const parsed = parse(error.stack); + const parsed = ErrorStack.parse(error); const newStack = [original[0]]; for (let i = 0; i < parsed.length; i++) { - const { file } = parsed[i]; - if (file === bundleFileName) newStack.push(frameToStr(parsed[i])); + const { fileName } = parsed[i]; + if (fileName === bundleFileName) newStack.push(frameToStr(parsed[i])); else newStack.push(original[i + 1]); } diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index 5b4e6157..22523f5c 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -71,7 +71,6 @@ async function getBundle(request) { parsedUrl.query.inlineSourceMap = true; delete parsedUrl.search; - console.log(url.format(parsedUrl)); return downloadBundle(url.format(parsedUrl)); } @@ -82,7 +81,6 @@ module.exports = { async message(request) { const { method } = request; - // console.log(request.method); switch (method) { case PREPARE: coverage.collect(); diff --git a/tests-new/firebase/modules/auth/User.js b/tests-new/firebase/modules/auth/User.js index e509bb94..b6cc0c7f 100644 --- a/tests-new/firebase/modules/auth/User.js +++ b/tests-new/firebase/modules/auth/User.js @@ -32,6 +32,7 @@ export default class User { constructor(auth: Auth, user: NativeUser) { this._auth = auth; this._user = user; + throw new Error('Woops') } /** diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index a192cc33..13c1c249 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -10851,21 +10851,6 @@ "urix": "0.1.0" } }, - "source-map-support": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", - "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", - "requires": { - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", diff --git a/tests-new/package.json b/tests-new/package.json index c4039624..d99c8de9 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -21,6 +21,7 @@ "chalk": "^2.3.2", "deeps": "^1.4.4", "detox": "^7.2.0", + "error-stack-parser": "^2.0.1", "istanbul-lib-coverage": "^1.2.0", "mocha": "^4.0.1", "react": "^16.2.0", @@ -30,7 +31,6 @@ "should-sinon": "0.0.6", "sinon": "^4.4.8", "source-map": "^0.7.2", - "stacktrace-parser": "^0.1.4", "ws": "^5.1.0" }, "devDependencies": { From 76b439979559b1ac7d79d208966614b5a85b2fad Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 22:23:02 +0100 Subject: [PATCH 30/40] [tests][bridge] added source map support / stack trace conversion --- tests-new/firebase/modules/auth/User.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests-new/firebase/modules/auth/User.js b/tests-new/firebase/modules/auth/User.js index b6cc0c7f..e509bb94 100644 --- a/tests-new/firebase/modules/auth/User.js +++ b/tests-new/firebase/modules/auth/User.js @@ -32,7 +32,6 @@ export default class User { constructor(auth: Auth, user: NativeUser) { this._auth = auth; this._user = user; - throw new Error('Woops') } /** From e19e87b0aaace977f2f127c7f74d273375ef1c01 Mon Sep 17 00:00:00 2001 From: Salakar Date: Mon, 26 Mar 2018 19:47:54 +0100 Subject: [PATCH 31/40] [tests] misc type coercion --- tests-new/bridge/env/node/vm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index 22523f5c..0801c5da 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -91,7 +91,7 @@ module.exports = { case EXECUTE: { const script = await getBundle(request); - if (global.bridge.context == null) { + if (global.bridge.context === null) { throw new Error('VM context was not prepared.'); } if (request.inject) { @@ -109,7 +109,7 @@ module.exports = { let returnValue = [[], [], [], 0]; try { if ( - global.bridge.context != null && + global.bridge.context !== null && typeof global.bridge.context.__fbBatchedBridge === 'object' ) { returnValue = global.bridge.context.__fbBatchedBridge[method].apply( From 87e822cb53049c2fb038bfd0926c4542e1e8bdf0 Mon Sep 17 00:00:00 2001 From: Salakar Date: Mon, 26 Mar 2018 20:13:51 +0100 Subject: [PATCH 32/40] [tests] make detox + mocha optional peer deps --- tests-new/bridge/env/node/index.js | 121 +++++++++++++----------- tests-new/bridge/env/node/source-map.js | 39 ++++++-- 2 files changed, 94 insertions(+), 66 deletions(-) diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js index de6a5a4a..ec278211 100644 --- a/tests-new/bridge/env/node/index.js +++ b/tests-new/bridge/env/node/index.js @@ -1,72 +1,81 @@ -/* eslint-disable no-param-reassign */ +/* eslint-disable no-param-reassign,global-require */ global.bridge = {}; require('./source-map'); - -const detox = require('detox'); const ws = require('./ws'); const ready = require('./ready'); const coverage = require('./coverage'); -/* --------------------- - * DEVICE OVERRIDES - * --------------------- */ +let detox; +try { + detox = require('detox'); +} catch (e) { + // ignore +} -let device; -Object.defineProperty(global, 'device', { - get() { - return device; - }, - set(originalDevice) { - // device.reloadReactNative({ ... }) - // todo detoxOriginalReloadReactNative currently broken it seems - // const detoxOriginalReloadReactNative = originalDevice.reloadReactNative.bind(originalDevice); - originalDevice.reloadReactNative = async () => { - ready.reset(); - global.bridge.reload(); - return ready.wait(); - }; +if (detox) { + /* --------------------- + * DEVICE OVERRIDES + * --------------------- */ - // device.launchApp({ ... }) - const detoxOriginalLaunchApp = originalDevice.launchApp.bind( - originalDevice - ); - originalDevice.launchApp = async (...args) => { - ready.reset(); - await detoxOriginalLaunchApp(...args); - return ready.wait(); - }; + let device; + Object.defineProperty(global, 'device', { + get() { + return device; + }, + set(originalDevice) { + // device.reloadReactNative({ ... }) + // todo detoxOriginalReloadReactNative currently broken it seems + // const detoxOriginalReloadReactNative = originalDevice.reloadReactNative.bind(originalDevice); + originalDevice.reloadReactNative = async () => { + ready.reset(); + global.bridge.reload(); + return ready.wait(); + }; - device = originalDevice; - return originalDevice; - }, -}); + // device.launchApp({ ... }) + const detoxOriginalLaunchApp = originalDevice.launchApp.bind( + originalDevice + ); + originalDevice.launchApp = async (...args) => { + ready.reset(); + await detoxOriginalLaunchApp(...args); + return ready.wait(); + }; -/* ------------------- - * DETOX OVERRIDES - * ------------------- */ + device = originalDevice; + return originalDevice; + }, + }); -// detox.init() -const detoxOriginalInit = detox.init.bind(detox); -detox.init = async (...args) => { - ready.reset(); - await detoxOriginalInit(...args); - return ready.wait(); -}; + /* ------------------- + * DETOX OVERRIDES + * ------------------- */ -// detox.cleanup() -const detoxOriginalCleanup = detox.cleanup.bind(detox); -detox.cleanup = async (...args) => { - try { - ws.close(); - } catch (e) { - // do nothing - } - await detoxOriginalCleanup(...args); -}; + // detox.init() + const detoxOriginalInit = detox.init.bind(detox); + detox.init = async (...args) => { + ready.reset(); + await detoxOriginalInit(...args); + return ready.wait(); + }; + + // detox.cleanup() + const detoxOriginalCleanup = detox.cleanup.bind(detox); + detox.cleanup = async (...args) => { + try { + ws.close(); + } catch (e) { + // do nothing + } + await detoxOriginalCleanup(...args); + }; +} // setup after hook to ensure final context coverage is captured process.nextTick(() => { - after(() => { - coverage.collect(); - }); + if (global.after) { + after(() => { + coverage.collect(); + }); + } }); diff --git a/tests-new/bridge/env/node/source-map.js b/tests-new/bridge/env/node/source-map.js index 20361866..565691c1 100644 --- a/tests-new/bridge/env/node/source-map.js +++ b/tests-new/bridge/env/node/source-map.js @@ -1,12 +1,16 @@ -/* eslint-disable no-param-reassign */ -const Mocha = require('mocha'); -const ErrorStack = require('error-stack-parser'); -const { SourceMapConsumer } = require('source-map'); +/* eslint-disable no-param-reassign,global-require */ +let Mocha; +try { + Mocha = require('mocha'); +} catch (e) { + // ignore +} let bundleFileName = null; -const Runner = Mocha.Runner; let sourceMapConsumer = null; -const originalFail = Runner.prototype.fail; + +const ErrorStack = require('error-stack-parser'); +const { SourceMapConsumer } = require('source-map'); /** * Convert an error frame into a source mapped string @@ -23,8 +27,13 @@ function frameToStr(parsed) { parsed.columnNumber})`; } -// override mocha fail so we can replace stack traces -Runner.prototype.fail = function fail(test, error) { +/** + * Convert an errors stack frames to their original source mapped positions + * + * @param error + * @return {*} + */ +function sourceMappedError(error) { const original = error.stack.split('\n'); const parsed = ErrorStack.parse(error); @@ -37,10 +46,20 @@ Runner.prototype.fail = function fail(test, error) { } error.stack = newStack.join('\n'); - return originalFail.call(this, test, error); -}; + return error; +} + +if (Mocha) { + // override mocha fail so we can replace stack traces + const Runner = Mocha.Runner; + const originalFail = Runner.prototype.fail; + Runner.prototype.fail = function fail(test, error) { + return originalFail.call(this, test, sourceMappedError(error)); + }; +} module.exports = { + sourceMappedError, /** * Build a source map consumer from source map bundle contents * @param str From a639d360d1c4dee8941ae8134a0108082dfd47ab Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Tue, 27 Mar 2018 12:51:27 +0100 Subject: [PATCH 33/40] Update gradle.properties --- tests-new/android/gradle.properties | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests-new/android/gradle.properties b/tests-new/android/gradle.properties index 135b0123..351e2fd3 100755 --- a/tests-new/android/gradle.properties +++ b/tests-new/android/gradle.properties @@ -10,7 +10,10 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit @@ -19,4 +22,3 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro android.useDeprecatedNdk=true android.enableAapt2=false -org.gradle.jvmargs=-Xmx1536M From 2537ce127697997e3e835645a7fb46a8e8425075 Mon Sep 17 00:00:00 2001 From: Salakar Date: Tue, 27 Mar 2018 23:00:18 +0100 Subject: [PATCH 34/40] [tests][bridge][android] wip switch to npm version --- tests-new/android/app/build.gradle | 4 +--- .../app/src/main/java/com/testing/MainApplication.java | 5 ++--- tests-new/android/settings.gradle | 4 ++-- tests-new/app.js | 2 +- tests-new/bridge/env/node/vm.js | 10 +++------- tests-new/e2e/mocha.opts | 2 +- tests-new/package.json | 1 - 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/tests-new/android/app/build.gradle b/tests-new/android/app/build.gradle index fce8183a..e50e6c2d 100755 --- a/tests-new/android/app/build.gradle +++ b/tests-new/android/app/build.gradle @@ -87,9 +87,7 @@ dependencies { transitive = false } - implementation(project(':react-native-restart')) { - transitive = false - } + implementation project(':bridge') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.google.android.gms:play-services-base:$firebaseVersion" diff --git a/tests-new/android/app/src/main/java/com/testing/MainApplication.java b/tests-new/android/app/src/main/java/com/testing/MainApplication.java index 653ceee8..112ffef7 100755 --- a/tests-new/android/app/src/main/java/com/testing/MainApplication.java +++ b/tests-new/android/app/src/main/java/com/testing/MainApplication.java @@ -8,8 +8,8 @@ import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import com.facebook.soloader.SoLoader; +import io.invertase.bridge.RNBridgePackage; import io.invertase.firebase.RNFirebasePackage; -import com.avishayil.rnrestart.ReactNativeRestartPackage; import io.invertase.firebase.admob.RNFirebaseAdMobPackage; import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage; import io.invertase.firebase.auth.RNFirebaseAuthPackage; @@ -41,7 +41,7 @@ public class MainApplication extends Application implements ReactApplication { protected List getPackages() { return Arrays.asList( new MainReactPackage(), - new ReactNativeRestartPackage(), + new RNBridgePackage(), new RNFirebasePackage(), new RNFirebaseAdMobPackage(), new RNFirebaseAnalyticsPackage(), @@ -70,7 +70,6 @@ public class MainApplication extends Application implements ReactApplication { @Override public void onCreate() { super.onCreate(); - getReactNativeHost().getReactInstanceManager().getDevSupportManager().getDevSettings().setRemoteJSDebugEnabled(true); SoLoader.init(this, /* native exopackage */ false); } diff --git a/tests-new/android/settings.gradle b/tests-new/android/settings.gradle index 3a8b98ad..51871257 100755 --- a/tests-new/android/settings.gradle +++ b/tests-new/android/settings.gradle @@ -3,8 +3,8 @@ rootProject.name = 'RNFTests' include ':react-native-firebase' project(':react-native-firebase').projectDir = new File(rootProject.projectDir, './../../android') -include ':react-native-restart' -project(':react-native-restart').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-restart/android') +include ':bridge' +project(':bridge').projectDir = new File(rootProject.projectDir, '../node_modules/bridge/android') include ':app' diff --git a/tests-new/app.js b/tests-new/app.js index 51438121..f5ccf182 100755 --- a/tests-new/app.js +++ b/tests-new/app.js @@ -9,7 +9,7 @@ import React, { Component } from 'react'; import { AppRegistry, Text, View } from 'react-native'; -import bridge from './bridge/env/rn'; +import bridge from 'bridge/platform/react-native'; import firebase from './firebase'; require('sinon'); diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index 22523f5c..f5b1c578 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -26,9 +26,9 @@ function handleError(message) { throw new Error(message); } -async function downloadUrl(url) { +async function downloadUrl(fileUrl) { const res = await new Promise((resolve, reject) => - http.get(url, resolve).on('error', reject) + http.get(fileUrl, resolve).on('error', reject) ); let buffer = ''; @@ -60,11 +60,7 @@ async function downloadBundle(bundleUrl) { async function getBundle(request) { if (bundle) return bundle; - console.log( - `${chalk.blue( - '[bridge]' - )} debugger has connected! Downloading app JS bundle...` - ); + console.log(`${chalk.blue('[bridge]')} debugger connected`); const parsedUrl = url.parse(request.url, true); invariant(parsedUrl.query); diff --git a/tests-new/e2e/mocha.opts b/tests-new/e2e/mocha.opts index bd8c68a6..41cc1066 100755 --- a/tests-new/e2e/mocha.opts +++ b/tests-new/e2e/mocha.opts @@ -3,4 +3,4 @@ --slow 2200 --bail --exit ---require ./bridge/env/node +--require bridge/platform/node diff --git a/tests-new/package.json b/tests-new/package.json index d99c8de9..c3cec87c 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -26,7 +26,6 @@ "mocha": "^4.0.1", "react": "^16.2.0", "react-native": "^0.52.3", - "react-native-restart": "0.0.6", "should": "^13.2.1", "should-sinon": "0.0.6", "sinon": "^4.4.8", From c9f01591eb76614c8c6da0d390ad7adc3152f4cb Mon Sep 17 00:00:00 2001 From: Salakar Date: Tue, 27 Mar 2018 23:48:43 +0100 Subject: [PATCH 35/40] [tests][bridge][ios] update ios project --- tests-new/ios/GoogleService-Info.plist | 40 +++++++++++ tests-new/ios/Podfile | 52 ++++++++++++++ .../project.pbxproj | 68 ++++++++++--------- .../xcschemes/testing Release.xcscheme} | 32 +++++---- .../xcshareddata/xcschemes/testing.xcscheme} | 33 +++++---- .../ios/{example => testing}/AppDelegate.h | 0 .../ios/{example => testing}/AppDelegate.m | 0 .../Base.lproj/LaunchScreen.xib | 12 ++-- .../AppIcon.appiconset/Contents.json | 5 ++ tests-new/ios/{example => testing}/Info.plist | 0 tests-new/ios/{example => testing}/main.m | 0 .../{exampleTests => testingTests}/Info.plist | 0 .../exampleTests.m | 0 13 files changed, 173 insertions(+), 69 deletions(-) create mode 100644 tests-new/ios/GoogleService-Info.plist create mode 100644 tests-new/ios/Podfile rename tests-new/ios/{example.xcodeproj => testing.xcodeproj}/project.pbxproj (95%) rename tests-new/ios/{example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme => testing.xcodeproj/xcshareddata/xcschemes/testing Release.xcscheme} (84%) mode change 100755 => 100644 rename tests-new/ios/{example.xcodeproj/xcshareddata/xcschemes/example.xcscheme => testing.xcodeproj/xcshareddata/xcschemes/testing.xcscheme} (83%) mode change 100755 => 100644 rename tests-new/ios/{example => testing}/AppDelegate.h (100%) rename tests-new/ios/{example => testing}/AppDelegate.m (100%) rename tests-new/ios/{example => testing}/Base.lproj/LaunchScreen.xib (86%) rename tests-new/ios/{example => testing}/Images.xcassets/AppIcon.appiconset/Contents.json (88%) rename tests-new/ios/{example => testing}/Info.plist (100%) rename tests-new/ios/{example => testing}/main.m (100%) rename tests-new/ios/{exampleTests => testingTests}/Info.plist (100%) rename tests-new/ios/{exampleTests => testingTests}/exampleTests.m (100%) diff --git a/tests-new/ios/GoogleService-Info.plist b/tests-new/ios/GoogleService-Info.plist new file mode 100644 index 00000000..0b667f62 --- /dev/null +++ b/tests-new/ios/GoogleService-Info.plist @@ -0,0 +1,40 @@ + + + + + AD_UNIT_ID_FOR_BANNER_TEST + ca-app-pub-3940256099942544/2934735716 + AD_UNIT_ID_FOR_INTERSTITIAL_TEST + ca-app-pub-3940256099942544/4411468910 + CLIENT_ID + 305229645282-9i07a33r4sj2fmrshnmh8jkh52dgpdve.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.305229645282-9i07a33r4sj2fmrshnmh8jkh52dgpdve + API_KEY + AIzaSyAcdVLG5dRzA1ck_fa_xd4Z0cY7cga7S5A + GCM_SENDER_ID + 305229645282 + PLIST_VERSION + 1 + BUNDLE_ID + com.testing + PROJECT_ID + rnfirebase-b9ad4 + STORAGE_BUCKET + rnfirebase-b9ad4.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:305229645282:ios:af36d4d29a83e04c + DATABASE_URL + https://rnfirebase-b9ad4.firebaseio.com + + \ No newline at end of file diff --git a/tests-new/ios/Podfile b/tests-new/ios/Podfile new file mode 100644 index 00000000..36cf41c5 --- /dev/null +++ b/tests-new/ios/Podfile @@ -0,0 +1,52 @@ +install! 'cocoapods', :deterministic_uuids => false +# Uncomment this line to define a global platform for your project +# platform :ios, '8.0' + +target 'ReactNativeFirebaseDemo' do + platform :ios, '9.0' + # Uncomment this line if you're using Swift or would like to use dynamic frameworks + # use_frameworks! + + react_native_path = "../node_modules/react-native" + pod "yoga", :path => "#{react_native_path}/ReactCommon/yoga" + + # Pods for ReactNativeFirebaseDemo + pod 'React', :path => '../node_modules/react-native', :subspecs => [ + 'Core', + 'BatchedBridge', + 'RCTText', + 'RCTNetwork', + 'RCTWebSocket', + ] + + pod 'Firebase/AdMob' + pod 'Firebase/Auth' + pod 'Firebase/Core' + pod 'Firebase/Crash' + pod 'Firebase/Database' + pod 'Firebase/DynamicLinks' + pod 'Firebase/Firestore' + pod 'Firebase/Invites' + pod 'Firebase/Messaging' + pod 'Firebase/RemoteConfig' + pod 'Firebase/Storage' + pod 'Firebase/Performance' + pod 'Fabric', '~> 1.7.5' + pod 'Crashlytics', '~> 3.10.1' + + pod 'RNFirebase', :path => '../../ios/RNFirebase.podspec' + + post_install do |installer| + installer.pods_project.targets.each do |target| + if target.name == "React" + target.remove_from_project + end + if target.name == 'yoga' + target.build_configurations.each do |config| + config.build_settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'NO' + config.build_settings['GCC_WARN_64_TO_32_BIT_CONVERSION'] = 'NO' + end + end + end + end +end diff --git a/tests-new/ios/example.xcodeproj/project.pbxproj b/tests-new/ios/testing.xcodeproj/project.pbxproj similarity index 95% rename from tests-new/ios/example.xcodeproj/project.pbxproj rename to tests-new/ios/testing.xcodeproj/project.pbxproj index efee9ce1..f01ed070 100755 --- a/tests-new/ios/example.xcodeproj/project.pbxproj +++ b/tests-new/ios/testing.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 271CB185206AFCD300EBADF4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 271CB184206AFCD300EBADF4 /* GoogleService-Info.plist */; }; 46EE18632047465300FAAB0E /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 46EE18602047463E00FAAB0E /* libRCTAnimation.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; /* End PBXBuildFile section */ @@ -286,24 +287,24 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 00E356F21AD99517003FC87E /* exampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = exampleTests.m; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* testingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = testingTests.m; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; - 13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = example/AppDelegate.h; sourceTree = ""; }; - 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = example/AppDelegate.m; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* testing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testing.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = testing/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = testing/AppDelegate.m; sourceTree = ""; }; 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = ""; }; - 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = ""; }; - 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = example/main.m; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = testing/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = testing/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = testing/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 271CB184206AFCD300EBADF4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 46EE185A2047463E00FAAB0E /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; @@ -373,13 +374,13 @@ name = Products; sourceTree = ""; }; - 00E356EF1AD99517003FC87E /* exampleTests */ = { + 00E356EF1AD99517003FC87E /* testingTests */ = { isa = PBXGroup; children = ( - 00E356F21AD99517003FC87E /* exampleTests.m */, + 00E356F21AD99517003FC87E /* testingTests.m */, 00E356F01AD99517003FC87E /* Supporting Files */, ); - path = exampleTests; + path = testingTests; sourceTree = ""; }; 00E356F01AD99517003FC87E /* Supporting Files */ = { @@ -410,10 +411,10 @@ name = Products; sourceTree = ""; }; - 13B07FAE1A68108700A75B9A /* example */ = { + 13B07FAE1A68108700A75B9A /* testing */ = { isa = PBXGroup; children = ( - 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 271CB184206AFCD300EBADF4 /* GoogleService-Info.plist */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, 13B07FB51A68108700A75B9A /* Images.xcassets */, @@ -421,7 +422,7 @@ 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 13B07FB71A68108700A75B9A /* main.m */, ); - name = example; + name = testing; sourceTree = ""; }; 146834001AC3E56700842450 /* Products */ = { @@ -495,10 +496,10 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( - 13B07FAE1A68108700A75B9A /* example */, + 13B07FAE1A68108700A75B9A /* testing */, CCFA7DE41D11D22600E15EDF /* Frameworks */, 832341AE1AAA6A7D00B99B32 /* Libraries */, - 00E356EF1AD99517003FC87E /* exampleTests */, + 00E356EF1AD99517003FC87E /* testingTests */, 83CBBA001A601CBA00E9B192 /* Products */, ); indentWidth = 2; @@ -508,7 +509,7 @@ 83CBBA001A601CBA00E9B192 /* Products */ = { isa = PBXGroup; children = ( - 13B07F961A680F5B00A75B9A /* example.app */, + 13B07F961A680F5B00A75B9A /* testing.app */, ); name = Products; sourceTree = ""; @@ -523,9 +524,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 13B07F861A680F5B00A75B9A /* example */ = { + 13B07F861A680F5B00A75B9A /* testing */ = { isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "testing" */; buildPhases = ( 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, @@ -538,9 +539,9 @@ ); dependencies = ( ); - name = example; + name = testing; productName = "Hello World"; - productReference = 13B07F961A680F5B00A75B9A /* example.app */; + productReference = 13B07F961A680F5B00A75B9A /* testing.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -549,10 +550,10 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0910; ORGANIZATIONNAME = Facebook; }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "example" */; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testing" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; @@ -611,7 +612,7 @@ ); projectRoot = ""; targets = ( - 13B07F861A680F5B00A75B9A /* example */, + 13B07F861A680F5B00A75B9A /* testing */, ); }; /* End PBXProject section */ @@ -871,6 +872,7 @@ files = ( 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + 271CB185206AFCD300EBADF4 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -926,7 +928,7 @@ 13B07FB21A68108700A75B9A /* Base */, ); name = LaunchScreen.xib; - path = example; + path = testing; sourceTree = ""; }; /* End PBXVariantGroup section */ @@ -937,15 +939,15 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEAD_CODE_STRIPPING = NO; - INFOPLIST_FILE = example/Info.plist; + INFOPLIST_FILE = testing/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.wix.demo.react.native; - PRODUCT_NAME = example; + PRODUCT_BUNDLE_IDENTIFIER = com.testing; + PRODUCT_NAME = testing; }; name = Debug; }; @@ -953,15 +955,15 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = example/Info.plist; + INFOPLIST_FILE = testing/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.wix.demo.react.native; - PRODUCT_NAME = example; + PRODUCT_BUNDLE_IDENTIFIER = com.testing; + PRODUCT_NAME = testing; }; name = Release; }; @@ -1053,7 +1055,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */ = { + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "testing" */ = { isa = XCConfigurationList; buildConfigurations = ( 13B07F941A680F5B00A75B9A /* Debug */, @@ -1062,7 +1064,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "example" */ = { + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testing" */ = { isa = XCConfigurationList; buildConfigurations = ( 83CBBA201A601CBA00E9B192 /* Debug */, diff --git a/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme b/tests-new/ios/testing.xcodeproj/xcshareddata/xcschemes/testing Release.xcscheme old mode 100755 new mode 100644 similarity index 84% rename from tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme rename to tests-new/ios/testing.xcodeproj/xcshareddata/xcschemes/testing Release.xcscheme index 944802f2..ca97e114 --- a/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example Release.xcscheme +++ b/tests-new/ios/testing.xcodeproj/xcshareddata/xcschemes/testing Release.xcscheme @@ -1,6 +1,6 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> + ReferencedContainer = "container:testing.xcodeproj"> @@ -54,6 +54,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> + ReferencedContainer = "container:testing.xcodeproj"> @@ -71,9 +72,9 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> @@ -83,6 +84,7 @@ buildConfiguration = "Release" selectedDebuggerIdentifier = "" selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" @@ -94,9 +96,9 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> @@ -113,9 +115,9 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> diff --git a/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme b/tests-new/ios/testing.xcodeproj/xcshareddata/xcschemes/testing.xcscheme old mode 100755 new mode 100644 similarity index 83% rename from tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme rename to tests-new/ios/testing.xcodeproj/xcshareddata/xcschemes/testing.xcscheme index effa046b..ee0a3eee --- a/tests-new/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme +++ b/tests-new/ios/testing.xcodeproj/xcshareddata/xcschemes/testing.xcscheme @@ -1,6 +1,6 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> + ReferencedContainer = "container:testing.xcodeproj"> @@ -54,6 +54,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> + ReferencedContainer = "container:testing.xcodeproj"> @@ -71,9 +72,9 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> @@ -83,6 +84,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" @@ -94,9 +96,9 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> @@ -113,9 +115,9 @@ + BuildableName = "testing.app" + BlueprintName = "testing" + ReferencedContainer = "container:testing.xcodeproj"> @@ -124,6 +126,7 @@ diff --git a/tests-new/ios/example/AppDelegate.h b/tests-new/ios/testing/AppDelegate.h similarity index 100% rename from tests-new/ios/example/AppDelegate.h rename to tests-new/ios/testing/AppDelegate.h diff --git a/tests-new/ios/example/AppDelegate.m b/tests-new/ios/testing/AppDelegate.m similarity index 100% rename from tests-new/ios/example/AppDelegate.m rename to tests-new/ios/testing/AppDelegate.m diff --git a/tests-new/ios/example/Base.lproj/LaunchScreen.xib b/tests-new/ios/testing/Base.lproj/LaunchScreen.xib similarity index 86% rename from tests-new/ios/example/Base.lproj/LaunchScreen.xib rename to tests-new/ios/testing/Base.lproj/LaunchScreen.xib index 9e04807a..1d9d023c 100755 --- a/tests-new/ios/example/Base.lproj/LaunchScreen.xib +++ b/tests-new/ios/testing/Base.lproj/LaunchScreen.xib @@ -1,8 +1,8 @@ - - + + - - + + @@ -18,14 +18,14 @@ -