diff --git a/README.md b/README.md index 2e4d5b03..e802c21a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Package Quality](http://npm.packagequality.com/shield/react-native-firebase.svg?style=flat-square)](http://packagequality.com/#?package=react-native-firebase) [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square)](https://discord.gg/t6bdqMs) [![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](https://www.patreon.com/invertase) +[![Twitter Follow](https://img.shields.io/twitter/follow/rnfirebase.svg?style=social&label=Follow)](https://twitter.com/rnfirebase) **RNFirebase** makes using [Firebase](http://firebase.com) with React Native simple. @@ -57,6 +58,7 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a | **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ |**?**| | **Crash Reporting** | ✅ | ✅ | ✅ | ❌ | | **Dynamic Links** | ❌ | ❌ | ❌ | ❌ | +| **Firestore** | ❌ | ❌ | ✅ | ❌ | | **Invites** | ❌ | ❌ | ❌ | ❌ | | **Performance Monitoring** | ✅ | ✅ | ✅ | ❌ | | **Realtime Database** | ✅ | ✅ | ✅ | ✅ | @@ -68,18 +70,13 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a --- ### Supported versions - React Native / Firebase -> The table below shows the supported version of `react-native-firebase` for different React Native versions +> The table below shows the supported versions of React Native and the Firebase SDKs for different versions of `react-native-firebase` -| | v0.36 - v0.39 | v0.40 - v0.46 | v0.47 + -| ------------------------------- | :---: | :---: | :---: | -| react-native-firebase | 1.X.X | 2.X.X | 2.1.X | - -> The table below shows the minimum supported versions of the Firebase SDKs for each version of `react-native-firebase` - -| | v1 | v2 | v3 | -| ---------------------- | :---: | :---: | :---: | -| Firebase Android SDK | 10.2.0+ | 11.0.0 + | 11.2.0 + | -| Firebase iOS SDK | 3.15.0+ | 4.0.0 + | 4.0.0 + | +| | 1.X.X | 2.0.X | 2.1.X / 2.2.X | 3.0.X | +|------------------------|-------------|-------------|-----------------|----------| +| React Native | 0.36 - 0.39 | 0.40 - 0.46 | 0.47 + | 0.48 + | +| Firebase Android SDK | 10.2.0 + | 11.0.0 + | 11.0.0 + | 11.4.2 + | +| Firebase iOS SDK | 3.15.0 + | 4.0.0 + | 4.0.0 + | 4.3.0 + | --- diff --git a/android/build.gradle b/android/build.gradle index 7d1f6a3b..121c6f3c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.firebaseVersion = '11.2.0' + ext.firebaseVersion = '11.4.2' repositories { jcenter() } @@ -33,6 +33,10 @@ android { allprojects { repositories { jcenter() + mavenLocal() + maven { + url "https://maven.google.com" + } } } @@ -85,4 +89,5 @@ dependencies { compile "com.google.firebase:firebase-crash:$firebaseVersion" compile "com.google.firebase:firebase-perf:$firebaseVersion" compile "com.google.firebase:firebase-ads:$firebaseVersion" + compile "com.google.firebase:firebase-firestore:$firebaseVersion" } diff --git a/android/src/main/java/io/invertase/firebase/ErrorUtils.java b/android/src/main/java/io/invertase/firebase/ErrorUtils.java new file mode 100644 index 00000000..85d4fdb0 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/ErrorUtils.java @@ -0,0 +1,27 @@ +package io.invertase.firebase; + +public class ErrorUtils { + /** + * Wrap a message string with the specified service name e.g. 'Database' + * + * @param message + * @param service + * @param fullCode + * @return + */ + public static String getMessageWithService(String message, String service, String fullCode) { + // Service: Error message (service/code). + return service + ": " + message + " (" + fullCode.toLowerCase() + ")."; + } + + /** + * Generate a service error code string e.g. 'DATABASE/PERMISSION-DENIED' + * + * @param service + * @param code + * @return + */ + public static String getCodeWithService(String service, String code) { + return service.toLowerCase() + "/" + code.toLowerCase(); + } +} diff --git a/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java b/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java index b960b9c6..8bf7fcbd 100644 --- a/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java +++ b/android/src/main/java/io/invertase/firebase/RNFirebasePackage.java @@ -31,18 +31,6 @@ public class RNFirebasePackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/Utils.java b/android/src/main/java/io/invertase/firebase/Utils.java index 15f2d670..69c82a75 100644 --- a/android/src/main/java/io/invertase/firebase/Utils.java +++ b/android/src/main/java/io/invertase/firebase/Utils.java @@ -78,7 +78,6 @@ public class Utils { /** * @param dataSnapshot - * @param registration * @param previousChildName * @return */ diff --git a/android/src/main/java/io/invertase/firebase/admob/RNFirebaseAdMobPackage.java b/android/src/main/java/io/invertase/firebase/admob/RNFirebaseAdMobPackage.java index 6715affc..2bfb6109 100644 --- a/android/src/main/java/io/invertase/firebase/admob/RNFirebaseAdMobPackage.java +++ b/android/src/main/java/io/invertase/firebase/admob/RNFirebaseAdMobPackage.java @@ -29,18 +29,6 @@ public class RNFirebaseAdMobPackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/analytics/RNFirebaseAnalyticsPackage.java b/android/src/main/java/io/invertase/firebase/analytics/RNFirebaseAnalyticsPackage.java index 15fe7c9e..b21a3082 100644 --- a/android/src/main/java/io/invertase/firebase/analytics/RNFirebaseAnalyticsPackage.java +++ b/android/src/main/java/io/invertase/firebase/analytics/RNFirebaseAnalyticsPackage.java @@ -33,18 +33,6 @@ public class RNFirebaseAnalyticsPackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuthPackage.java b/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuthPackage.java index 0fa905dc..909b9312 100644 --- a/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuthPackage.java +++ b/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuthPackage.java @@ -28,18 +28,6 @@ public class RNFirebaseAuthPackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfigPackage.java b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfigPackage.java index e794313c..dce06b80 100644 --- a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfigPackage.java +++ b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfigPackage.java @@ -28,18 +28,6 @@ public class RNFirebaseRemoteConfigPackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/crash/RNFirebaseCrashPackage.java b/android/src/main/java/io/invertase/firebase/crash/RNFirebaseCrashPackage.java index 8eeb6174..4ab83e78 100644 --- a/android/src/main/java/io/invertase/firebase/crash/RNFirebaseCrashPackage.java +++ b/android/src/main/java/io/invertase/firebase/crash/RNFirebaseCrashPackage.java @@ -28,18 +28,6 @@ public class RNFirebaseCrashPackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabase.java b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabase.java index 07a91893..6612f491 100644 --- a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabase.java +++ b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabase.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import io.invertase.firebase.ErrorUtils; import io.invertase.firebase.Utils; @@ -522,30 +523,6 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule { return existingRef; } - /** - * Wrap a message string with the specified service name e.g. 'Database' - * - * @param message - * @param service - * @param fullCode - * @return - */ - private static String getMessageWithService(String message, String service, String fullCode) { - // Service: Error message (service/code). - return service + ": " + message + " (" + fullCode.toLowerCase() + ")."; - } - - /** - * Generate a service error code string e.g. 'DATABASE/PERMISSION-DENIED' - * - * @param service - * @param code - * @return - */ - private static String getCodeWithService(String service, String code) { - return service.toLowerCase() + "/" + code.toLowerCase(); - } - /** * Convert as firebase DatabaseError instance into a writable map * with the correct web-like error codes. @@ -564,56 +541,56 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule { switch (nativeError.getCode()) { case DatabaseError.DATA_STALE: - code = getCodeWithService(service, "data-stale"); - message = getMessageWithService("The transaction needs to be run again with current data.", service, code); + code = ErrorUtils.getCodeWithService(service, "data-stale"); + message = ErrorUtils.getMessageWithService("The transaction needs to be run again with current data.", service, code); break; case DatabaseError.OPERATION_FAILED: - code = getCodeWithService(service, "failure"); - message = getMessageWithService("The server indicated that this operation failed.", service, code); + code = ErrorUtils.getCodeWithService(service, "failure"); + message = ErrorUtils.getMessageWithService("The server indicated that this operation failed.", service, code); break; case DatabaseError.PERMISSION_DENIED: - code = getCodeWithService(service, "permission-denied"); - message = getMessageWithService("Client doesn't have permission to access the desired data.", service, code); + code = ErrorUtils.getCodeWithService(service, "permission-denied"); + message = ErrorUtils.getMessageWithService("Client doesn't have permission to access the desired data.", service, code); break; case DatabaseError.DISCONNECTED: - code = getCodeWithService(service, "disconnected"); - message = getMessageWithService("The operation had to be aborted due to a network disconnect.", service, code); + code = ErrorUtils.getCodeWithService(service, "disconnected"); + message = ErrorUtils.getMessageWithService("The operation had to be aborted due to a network disconnect.", service, code); break; case DatabaseError.EXPIRED_TOKEN: - code = getCodeWithService(service, "expired-token"); - message = getMessageWithService("The supplied auth token has expired.", service, code); + code = ErrorUtils.getCodeWithService(service, "expired-token"); + message = ErrorUtils.getMessageWithService("The supplied auth token has expired.", service, code); break; case DatabaseError.INVALID_TOKEN: - code = getCodeWithService(service, "invalid-token"); - message = getMessageWithService("The supplied auth token was invalid.", service, code); + code = ErrorUtils.getCodeWithService(service, "invalid-token"); + message = ErrorUtils.getMessageWithService("The supplied auth token was invalid.", service, code); break; case DatabaseError.MAX_RETRIES: - code = getCodeWithService(service, "max-retries"); - message = getMessageWithService("The transaction had too many retries.", service, code); + code = ErrorUtils.getCodeWithService(service, "max-retries"); + message = ErrorUtils.getMessageWithService("The transaction had too many retries.", service, code); break; case DatabaseError.OVERRIDDEN_BY_SET: - code = getCodeWithService(service, "overridden-by-set"); - message = getMessageWithService("The transaction was overridden by a subsequent set.", service, code); + code = ErrorUtils.getCodeWithService(service, "overridden-by-set"); + message = ErrorUtils.getMessageWithService("The transaction was overridden by a subsequent set.", service, code); break; case DatabaseError.UNAVAILABLE: - code = getCodeWithService(service, "unavailable"); - message = getMessageWithService("The service is unavailable.", service, code); + code = ErrorUtils.getCodeWithService(service, "unavailable"); + message = ErrorUtils.getMessageWithService("The service is unavailable.", service, code); break; case DatabaseError.USER_CODE_EXCEPTION: - code = getCodeWithService(service, "user-code-exception"); - message = getMessageWithService("User code called from the Firebase Database runloop threw an exception.", service, code); + code = ErrorUtils.getCodeWithService(service, "user-code-exception"); + message = ErrorUtils.getMessageWithService("User code called from the Firebase Database runloop threw an exception.", service, code); break; case DatabaseError.NETWORK_ERROR: - code = getCodeWithService(service, "network-error"); - message = getMessageWithService("The operation could not be performed due to a network error.", service, code); + code = ErrorUtils.getCodeWithService(service, "network-error"); + message = ErrorUtils.getMessageWithService("The operation could not be performed due to a network error.", service, code); break; case DatabaseError.WRITE_CANCELED: - code = getCodeWithService(service, "write-cancelled"); - message = getMessageWithService("The write was canceled by the user.", service, code); + code = ErrorUtils.getCodeWithService(service, "write-cancelled"); + message = ErrorUtils.getMessageWithService("The write was canceled by the user.", service, code); break; default: - code = getCodeWithService(service, "unknown"); - message = getMessageWithService("An unknown error occurred.", service, code); + code = ErrorUtils.getCodeWithService(service, "unknown"); + message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code); } errorMap.putString("code", code); diff --git a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabasePackage.java b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabasePackage.java index 7045aa53..c4e9808d 100644 --- a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabasePackage.java +++ b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabasePackage.java @@ -28,18 +28,6 @@ public class RNFirebaseDatabasePackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java index e6c2216b..60a6cbe5 100644 --- a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java +++ b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java @@ -29,8 +29,8 @@ class RNFirebaseDatabaseReference { private String appName; private ReactContext reactContext; private static final String TAG = "RNFirebaseDBReference"; - private HashMap childEventListeners; - private HashMap valueEventListeners; + private HashMap childEventListeners = new HashMap<>(); + private HashMap valueEventListeners = new HashMap<>(); /** * RNFirebase wrapper around FirebaseDatabaseReference, @@ -47,8 +47,6 @@ class RNFirebaseDatabaseReference { query = null; appName = app; reactContext = context; - childEventListeners = new HashMap<>(); - valueEventListeners = new HashMap<>(); buildDatabaseQueryAtPathAndModifiers(refPath, modifiersArray); } diff --git a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java new file mode 100644 index 00000000..d665ed6a --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java @@ -0,0 +1,197 @@ +package io.invertase.firebase.firestore; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.google.firebase.firestore.DocumentChange; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.QuerySnapshot; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class FirestoreSerialize { + private static final String KEY_CHANGES = "changes"; + private static final String KEY_DATA = "data"; + private static final String KEY_DOC_CHANGE_DOCUMENT = "document"; + private static final String KEY_DOC_CHANGE_NEW_INDEX = "newIndex"; + private static final String KEY_DOC_CHANGE_OLD_INDEX = "oldIndex"; + private static final String KEY_DOC_CHANGE_TYPE = "type"; + private static final String KEY_DOCUMENTS = "documents"; + private static final String KEY_PATH = "path"; + + /** + * Convert a DocumentSnapshot instance into a React Native WritableMap + * + * @param documentSnapshot DocumentSnapshot + * @return WritableMap + */ + static WritableMap snapshotToWritableMap(DocumentSnapshot documentSnapshot) { + WritableMap documentMap = Arguments.createMap(); + + documentMap.putString(KEY_PATH, documentSnapshot.getReference().getPath()); + if (documentSnapshot.exists()) { + documentMap.putMap(KEY_DATA, objectMapToWritable(documentSnapshot.getData())); + } + // Missing fields from web SDK + // createTime + // readTime + // updateTime + + return documentMap; + } + + public static WritableMap snapshotToWritableMap(QuerySnapshot querySnapshot) { + WritableMap queryMap = Arguments.createMap(); + + List documentChanges = querySnapshot.getDocumentChanges(); + queryMap.putArray(KEY_CHANGES, documentChangesToWritableArray(documentChanges)); + + // documents + WritableArray documents = Arguments.createArray(); + List documentSnapshots = querySnapshot.getDocuments(); + for (DocumentSnapshot documentSnapshot : documentSnapshots) { + documents.pushMap(snapshotToWritableMap(documentSnapshot)); + } + queryMap.putArray(KEY_DOCUMENTS, documents); + + return queryMap; + } + + /** + * Convert a List of DocumentChange instances into a React Native WritableArray + * + * @param documentChanges List + * @return WritableArray + */ + static WritableArray documentChangesToWritableArray(List documentChanges) { + WritableArray documentChangesWritable = Arguments.createArray(); + for (DocumentChange documentChange : documentChanges) { + documentChangesWritable.pushMap(documentChangeToWritableMap(documentChange)); + } + return documentChangesWritable; + } + + /** + * Convert a DocumentChange instance into a React Native WritableMap + * + * @param documentChange DocumentChange + * @return WritableMap + */ + static WritableMap documentChangeToWritableMap(DocumentChange documentChange) { + WritableMap documentChangeMap = Arguments.createMap(); + + switch (documentChange.getType()) { + case ADDED: + documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "added"); + break; + case REMOVED: + documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "removed"); + break; + case MODIFIED: + documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "modified"); + } + + documentChangeMap.putMap(KEY_DOC_CHANGE_DOCUMENT, + snapshotToWritableMap(documentChange.getDocument())); + documentChangeMap.putInt(KEY_DOC_CHANGE_NEW_INDEX, documentChange.getNewIndex()); + documentChangeMap.putInt(KEY_DOC_CHANGE_OLD_INDEX, documentChange.getOldIndex()); + + return documentChangeMap; + } + + /** + * Converts an Object Map into a React Native WritableMap. + * + * @param map Map + * @return WritableMap + */ + static WritableMap objectMapToWritable(Map map) { + WritableMap writableMap = Arguments.createMap(); + for (Map.Entry entry : map.entrySet()) { + putValue(writableMap, entry.getKey(), entry.getValue()); + } + return writableMap; + } + + /** + * Converts an Object array into a React Native WritableArray. + * + * @param array Object[] + * @return WritableArray + */ + static WritableArray objectArrayToWritable(Object[] array) { + WritableArray writableArray = Arguments.createArray(); + + for (Object item : array) { + if (item == null) { + writableArray.pushNull(); + continue; + } + + Class itemClass = item.getClass(); + + if (itemClass == Boolean.class) { + writableArray.pushBoolean((Boolean) item); + } else if (itemClass == Integer.class) { + writableArray.pushDouble(((Integer) item).doubleValue()); + } else if (itemClass == Double.class) { + writableArray.pushDouble((Double) item); + } else if (itemClass == Float.class) { + writableArray.pushDouble(((Float) item).doubleValue()); + } else if (itemClass == String.class) { + writableArray.pushString(item.toString()); + } else if (itemClass == Map.class) { + writableArray.pushMap((objectMapToWritable((Map) item))); + } else if (itemClass == Arrays.class) { + writableArray.pushArray(objectArrayToWritable((Object[]) item)); + } else if (itemClass == List.class) { + List list = (List) item; + Object[] listAsArray = list.toArray(new Object[list.size()]); + writableArray.pushArray(objectArrayToWritable(listAsArray)); + } else { + throw new RuntimeException("Cannot convert object of type " + item); + } + } + + return writableArray; + } + + /** + * Detects an objects type and calls the relevant WritableMap setter method to add the value. + * + * @param map WritableMap + * @param key String + * @param value Object + */ + static void putValue(WritableMap map, String key, Object value) { + if (value == null) { + map.putNull(key); + } else { + Class valueClass = value.getClass(); + + if (valueClass == Boolean.class) { + map.putBoolean(key, (Boolean) value); + } else if (valueClass == Integer.class) { + map.putDouble(key, ((Integer) value).doubleValue()); + } else if (valueClass == Double.class) { + map.putDouble(key, (Double) value); + } else if (valueClass == Float.class) { + map.putDouble(key, ((Float) value).doubleValue()); + } else if (valueClass == String.class) { + map.putString(key, value.toString()); + } else if (valueClass == Map.class) { + map.putMap(key, (objectMapToWritable((Map) value))); + } else if (valueClass == Arrays.class) { + map.putArray(key, objectArrayToWritable((Object[]) value)); + } else if (valueClass == List.class) { + List list = (List) value; + Object[] array = list.toArray(new Object[list.size()]); + map.putArray(key, objectArrayToWritable(array)); + } else { + throw new RuntimeException("Cannot convert object of type " + value); + } + } + } +} diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java new file mode 100644 index 00000000..180d286a --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java @@ -0,0 +1,342 @@ +package io.invertase.firebase.firestore; + +import android.support.annotation.NonNull; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.FirebaseApp; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.FieldValue; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.SetOptions; +import com.google.firebase.firestore.WriteBatch; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.invertase.firebase.ErrorUtils; +import io.invertase.firebase.Utils; + + +public class RNFirebaseFirestore extends ReactContextBaseJavaModule { + private static final String TAG = "RNFirebaseFirestore"; + // private SparseArray transactionHandlers = new SparseArray<>(); + + RNFirebaseFirestore(ReactApplicationContext reactContext) { + super(reactContext); + } + + + /* + * REACT NATIVE METHODS + */ + @ReactMethod + public void collectionGet(String appName, String path, ReadableArray filters, + ReadableArray orders, ReadableMap options, final Promise promise) { + RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); + ref.get(promise); + } + + @ReactMethod + public void collectionOffSnapshot(String appName, String path, ReadableArray filters, + ReadableArray orders, ReadableMap options, String listenerId) { + RNFirebaseFirestoreCollectionReference.offSnapshot(listenerId); + } + + @ReactMethod + public void collectionOnSnapshot(String appName, String path, ReadableArray filters, + ReadableArray orders, ReadableMap options, String listenerId) { + RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); + ref.onSnapshot(listenerId); + } + + + @ReactMethod + public void documentBatch(final String appName, final ReadableArray writes, + final ReadableMap commitOptions, final Promise promise) { + FirebaseFirestore firestore = getFirestoreForApp(appName); + WriteBatch batch = firestore.batch(); + final List writesArray = Utils.recursivelyDeconstructReadableArray(writes); + + for (Object w : writesArray) { + Map write = (Map) w; + String type = (String) write.get("type"); + String path = (String) write.get("path"); + Map data = (Map) write.get("data"); + + DocumentReference ref = firestore.document(path); + switch (type) { + case "DELETE": + batch = batch.delete(ref); + break; + case "SET": + Map options = (Map) write.get("options"); + if (options != null && options.containsKey("merge") && (boolean)options.get("merge")) { + batch = batch.set(ref, data, SetOptions.merge()); + } else { + batch = batch.set(ref, data); + } + + break; + case "UPDATE": + batch = batch.update(ref, data); + break; + } + } + + batch.commit().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "set:onComplete:success"); + WritableArray result = Arguments.createArray(); + for (Object w : writesArray) { + // Missing fields from web SDK + // writeTime + result.pushMap(Arguments.createMap()); + } + promise.resolve(result); + } else { + Log.e(TAG, "set:onComplete:failure", task.getException()); + RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + } + } + }); + } + + @ReactMethod + public void documentCollections(String appName, String path, final Promise promise) { + RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); + ref.collections(promise); + } + + @ReactMethod + public void documentCreate(String appName, String path, ReadableMap data, final Promise promise) { + RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); + ref.create(data, promise); + } + + @ReactMethod + public void documentDelete(String appName, String path, ReadableMap options, final Promise promise) { + RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); + ref.delete(options, promise); + } + + @ReactMethod + public void documentGet(String appName, String path, final Promise promise) { + RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); + ref.get(promise); + } + + @ReactMethod + public void documentGetAll(String appName, ReadableArray documents, final Promise promise) { + // Not supported on Android out of the box + } + + @ReactMethod + public void documentOffSnapshot(String appName, String path, String listenerId) { + RNFirebaseFirestoreDocumentReference.offSnapshot(listenerId); + } + + @ReactMethod + public void documentOnSnapshot(String appName, String path, String listenerId) { + RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); + ref.onSnapshot(listenerId); + } + + @ReactMethod + public void documentSet(String appName, String path, ReadableMap data, ReadableMap options, final Promise promise) { + RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); + ref.set(data, options, promise); + } + + @ReactMethod + public void documentUpdate(String appName, String path, ReadableMap data, final Promise promise) { + RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); + ref.update(data, promise); + } + + /* + * INTERNALS/UTILS + */ + + /** + * Generates a js-like error from an exception and rejects the provided promise with it. + * + * @param exception Exception Exception normally from a task result. + * @param promise Promise react native promise + */ + static void promiseRejectException(Promise promise, FirebaseFirestoreException exception) { + WritableMap jsError = getJSError(exception); + promise.reject( + jsError.getString("code"), + jsError.getString("message"), + exception + ); + } + + /** + * Get a database instance for a specific firebase app instance + * + * @param appName + * @return + */ + static FirebaseFirestore getFirestoreForApp(String appName) { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + return FirebaseFirestore.getInstance(firebaseApp); + } + + /** + * Get a collection reference for a specific app and path + * + * @param appName + * @param filters + * @param orders + * @param options + * @param path @return + */ + private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(String appName, String path, + ReadableArray filters, + ReadableArray orders, + ReadableMap options) { + return new RNFirebaseFirestoreCollectionReference(this.getReactApplicationContext(), appName, path, filters, orders, options); + } + + /** + * Get a document reference for a specific app and path + * + * @param appName + * @param path + * @return + */ + private RNFirebaseFirestoreDocumentReference getDocumentForAppPath(String appName, String path) { + return new RNFirebaseFirestoreDocumentReference(this.getReactApplicationContext(), appName, path); + } + + /** + * Convert as firebase DatabaseError instance into a writable map + * with the correct web-like error codes. + * + * @param nativeException + * @return + */ + static WritableMap getJSError(FirebaseFirestoreException nativeException) { + WritableMap errorMap = Arguments.createMap(); + errorMap.putInt("nativeErrorCode", nativeException.getCode().value()); + errorMap.putString("nativeErrorMessage", nativeException.getMessage()); + + String code; + String message; + String service = "Firestore"; + + // TODO: Proper error mappings + switch (nativeException.getCode()) { + case OK: + code = ErrorUtils.getCodeWithService(service, "ok"); + message = ErrorUtils.getMessageWithService("Ok.", service, code); + break; + case CANCELLED: + code = ErrorUtils.getCodeWithService(service, "cancelled"); + message = ErrorUtils.getMessageWithService("Cancelled.", service, code); + break; + case UNKNOWN: + code = ErrorUtils.getCodeWithService(service, "unknown"); + message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code); + break; + case INVALID_ARGUMENT: + code = ErrorUtils.getCodeWithService(service, "invalid-argument"); + message = ErrorUtils.getMessageWithService("Invalid argument.", service, code); + break; + case NOT_FOUND: + code = ErrorUtils.getCodeWithService(service, "not-found"); + message = ErrorUtils.getMessageWithService("Not found.", service, code); + break; + case ALREADY_EXISTS: + code = ErrorUtils.getCodeWithService(service, "already-exists"); + message = ErrorUtils.getMessageWithService("Already exists.", service, code); + break; + case PERMISSION_DENIED: + code = ErrorUtils.getCodeWithService(service, "permission-denied"); + message = ErrorUtils.getMessageWithService("Permission denied.", service, code); + break; + case RESOURCE_EXHAUSTED: + code = ErrorUtils.getCodeWithService(service, "resource-exhausted"); + message = ErrorUtils.getMessageWithService("Resource exhausted.", service, code); + break; + case FAILED_PRECONDITION: + code = ErrorUtils.getCodeWithService(service, "failed-precondition"); + message = ErrorUtils.getMessageWithService("Failed precondition.", service, code); + break; + case ABORTED: + code = ErrorUtils.getCodeWithService(service, "aborted"); + message = ErrorUtils.getMessageWithService("Aborted.", service, code); + break; + case OUT_OF_RANGE: + code = ErrorUtils.getCodeWithService(service, "out-of-range"); + message = ErrorUtils.getMessageWithService("Out of range.", service, code); + break; + case UNIMPLEMENTED: + code = ErrorUtils.getCodeWithService(service, "unimplemented"); + message = ErrorUtils.getMessageWithService("Unimplemented.", service, code); + break; + case INTERNAL: + code = ErrorUtils.getCodeWithService(service, "internal"); + message = ErrorUtils.getMessageWithService("Internal.", service, code); + break; + case UNAVAILABLE: + code = ErrorUtils.getCodeWithService(service, "unavailable"); + message = ErrorUtils.getMessageWithService("Unavailable.", service, code); + break; + case DATA_LOSS: + code = ErrorUtils.getCodeWithService(service, "data-loss"); + message = ErrorUtils.getMessageWithService("Data loss.", service, code); + break; + case UNAUTHENTICATED: + code = ErrorUtils.getCodeWithService(service, "unauthenticated"); + message = ErrorUtils.getMessageWithService("Unauthenticated.", service, code); + break; + default: + code = ErrorUtils.getCodeWithService(service, "unknown"); + message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code); + } + + errorMap.putString("code", code); + errorMap.putString("message", message); + return errorMap; + } + + /** + * React Method - returns this module name + * + * @return + */ + @Override + public String getName() { + return "RNFirebaseFirestore"; + } + + /** + * React Native constants for RNFirebaseFirestore + * + * @return + */ + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + constants.put("deleteFieldValue", FieldValue.delete().toString()); + constants.put("serverTimestampFieldValue", FieldValue.serverTimestamp().toString()); + return constants; + } +} diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java new file mode 100644 index 00000000..e8512d19 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java @@ -0,0 +1,218 @@ +package io.invertase.firebase.firestore; + + +import android.support.annotation.NonNull; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.EventListener; +import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.ListenerRegistration; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QuerySnapshot; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.invertase.firebase.Utils; + +public class RNFirebaseFirestoreCollectionReference { + private static final String TAG = "RNFSCollectionReference"; + private static Map collectionSnapshotListeners = new HashMap<>(); + + private final String appName; + private final String path; + private final ReadableArray filters; + private final ReadableArray orders; + private final ReadableMap options; + private final Query query; + private ReactContext reactContext; + + RNFirebaseFirestoreCollectionReference(ReactContext reactContext, String appName, String path, + ReadableArray filters, ReadableArray orders, + ReadableMap options) { + this.appName = appName; + this.path = path; + this.filters = filters; + this.orders = orders; + this.options = options; + this.query = buildQuery(); + this.reactContext = reactContext; + } + + void get(final Promise promise) { + query.get().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "get:onComplete:success"); + WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult()); + promise.resolve(data); + } else { + Log.e(TAG, "get:onComplete:failure", task.getException()); + RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + } + } + }); + } + + public static void offSnapshot(final String listenerId) { + ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId); + if (listenerRegistration != null) { + listenerRegistration.remove(); + } + } + + public void onSnapshot(final String listenerId) { + if (!collectionSnapshotListeners.containsKey(listenerId)) { + final EventListener listener = new EventListener() { + @Override + public void onEvent(QuerySnapshot querySnapshot, FirebaseFirestoreException exception) { + if (exception == null) { + handleQuerySnapshotEvent(listenerId, querySnapshot); + } else { + ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId); + if (listenerRegistration != null) { + listenerRegistration.remove(); + } + handleQuerySnapshotError(listenerId, exception); + } + } + }; + ListenerRegistration listenerRegistration = this.query.addSnapshotListener(listener); + collectionSnapshotListeners.put(listenerId, listenerRegistration); + } + } + + /* + * INTERNALS/UTILS + */ + + boolean hasListeners() { + return !collectionSnapshotListeners.isEmpty(); + } + + private Query buildQuery() { + Query query = RNFirebaseFirestore.getFirestoreForApp(appName).collection(path); + query = applyFilters(query); + query = applyOrders(query); + query = applyOptions(query); + + return query; + } + + private Query applyFilters(Query query) { + List filtersList = Utils.recursivelyDeconstructReadableArray(filters); + + for (Object f : filtersList) { + Map filter = (Map) f; + String fieldPath = (String) filter.get("fieldPath"); + String operator = (String) filter.get("operator"); + Object value = filter.get("value"); + + switch (operator) { + case "EQUAL": + query = query.whereEqualTo(fieldPath, value); + break; + case "GREATER_THAN": + query = query.whereGreaterThan(fieldPath, value); + break; + case "GREATER_THAN_OR_EQUAL": + query = query.whereGreaterThanOrEqualTo(fieldPath, value); + break; + case "LESS_THAN": + query = query.whereLessThan(fieldPath, value); + break; + case "LESS_THAN_OR_EQUAL": + query = query.whereLessThanOrEqualTo(fieldPath, value); + break; + } + } + return query; + } + + private Query applyOrders(Query query) { + List ordersList = Utils.recursivelyDeconstructReadableArray(orders); + for (Object o : ordersList) { + Map order = (Map) o; + String direction = (String) order.get("direction"); + String fieldPath = (String) order.get("fieldPath"); + + query = query.orderBy(fieldPath, Query.Direction.valueOf(direction)); + } + return query; + } + + private Query applyOptions(Query query) { + if (options.hasKey("endAt")) { + ReadableArray endAtArray = options.getArray("endAt"); + query = query.endAt(Utils.recursivelyDeconstructReadableArray(endAtArray)); + } + if (options.hasKey("endBefore")) { + ReadableArray endBeforeArray = options.getArray("endBefore"); + query = query.endBefore(Utils.recursivelyDeconstructReadableArray(endBeforeArray)); + } + if (options.hasKey("limit")) { + int limit = options.getInt("limit"); + query = query.limit(limit); + } + if (options.hasKey("offset")) { + // Android doesn't support offset + } + if (options.hasKey("selectFields")) { + // Android doesn't support selectFields + } + if (options.hasKey("startAfter")) { + ReadableArray startAfterArray = options.getArray("startAfter"); + query = query.startAfter(Utils.recursivelyDeconstructReadableArray(startAfterArray)); + } + if (options.hasKey("startAt")) { + ReadableArray startAtArray = options.getArray("startAt"); + query = query.startAt(Utils.recursivelyDeconstructReadableArray(startAtArray)); + } + return query; + } + + /** + * Handles documentSnapshot events. + * + * @param listenerId + * @param querySnapshot + */ + private void handleQuerySnapshotEvent(String listenerId, QuerySnapshot querySnapshot) { + WritableMap event = Arguments.createMap(); + WritableMap data = FirestoreSerialize.snapshotToWritableMap(querySnapshot); + + event.putString("appName", appName); + event.putString("path", path); + event.putString("listenerId", listenerId); + event.putMap("querySnapshot", data); + + Utils.sendEvent(reactContext, "firestore_collection_sync_event", event); + } + + /** + * Handles a documentSnapshot error event + * + * @param listenerId + * @param exception + */ + private void handleQuerySnapshotError(String listenerId, FirebaseFirestoreException exception) { + WritableMap event = Arguments.createMap(); + + event.putString("appName", appName); + event.putString("path", path); + event.putString("listenerId", listenerId); + event.putMap("error", RNFirebaseFirestore.getJSError(exception)); + + Utils.sendEvent(reactContext, "firestore_collection_sync_event", event); + } +} diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java new file mode 100644 index 00000000..8167f281 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java @@ -0,0 +1,196 @@ +package io.invertase.firebase.firestore; + +import android.support.annotation.NonNull; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.EventListener; +import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.ListenerRegistration; +import com.google.firebase.firestore.SetOptions; + +import java.util.HashMap; +import java.util.Map; + +import io.invertase.firebase.Utils; + + +public class RNFirebaseFirestoreDocumentReference { + private static final String TAG = "RNFBFSDocumentReference"; + private static Map documentSnapshotListeners = new HashMap<>(); + + private final String appName; + private final String path; + private ReactContext reactContext; + private final DocumentReference ref; + + RNFirebaseFirestoreDocumentReference(ReactContext reactContext, String appName, String path) { + this.appName = appName; + this.path = path; + this.reactContext = reactContext; + this.ref = RNFirebaseFirestore.getFirestoreForApp(appName).document(path); + } + + public void collections(Promise promise) { + // Not supported on Android + } + + public void create(ReadableMap data, Promise promise) { + // Not supported on Android out of the box + } + + public void delete(final ReadableMap options, final Promise promise) { + this.ref.delete().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "delete:onComplete:success"); + // Missing fields from web SDK + // writeTime + promise.resolve(Arguments.createMap()); + } else { + Log.e(TAG, "delete:onComplete:failure", task.getException()); + RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + } + } + }); + } + + void get(final Promise promise) { + this.ref.get().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "get:onComplete:success"); + WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult()); + promise.resolve(data); + } else { + Log.e(TAG, "get:onComplete:failure", task.getException()); + RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + } + } + }); + } + + public static void offSnapshot(final String listenerId) { + ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId); + if (listenerRegistration != null) { + listenerRegistration.remove(); + } + } + + public void onSnapshot(final String listenerId) { + if (!documentSnapshotListeners.containsKey(listenerId)) { + final EventListener listener = new EventListener() { + @Override + public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException exception) { + if (exception == null) { + handleDocumentSnapshotEvent(listenerId, documentSnapshot); + } else { + ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId); + if (listenerRegistration != null) { + listenerRegistration.remove(); + } + handleDocumentSnapshotError(listenerId, exception); + } + } + }; + ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(listener); + documentSnapshotListeners.put(listenerId, listenerRegistration); + } + } + + public void set(final ReadableMap data, final ReadableMap options, final Promise promise) { + Map map = Utils.recursivelyDeconstructReadableMap(data); + Task task; + SetOptions setOptions = null; + if (options != null && options.hasKey("merge") && options.getBoolean("merge")) { + task = this.ref.set(map, SetOptions.merge()); + } else { + task = this.ref.set(map); + } + task.addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "set:onComplete:success"); + // Missing fields from web SDK + // writeTime + promise.resolve(Arguments.createMap()); + } else { + Log.e(TAG, "set:onComplete:failure", task.getException()); + RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + } + } + }); + } + + public void update(final ReadableMap data, final Promise promise) { + Map map = Utils.recursivelyDeconstructReadableMap(data); + this.ref.update(map).addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "update:onComplete:success"); + // Missing fields from web SDK + // writeTime + promise.resolve(Arguments.createMap()); + } else { + Log.e(TAG, "update:onComplete:failure", task.getException()); + RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + } + } + }); + } + + /* + * INTERNALS/UTILS + */ + + boolean hasListeners() { + return !documentSnapshotListeners.isEmpty(); + } + + /** + * Handles documentSnapshot events. + * + * @param listenerId + * @param documentSnapshot + */ + private void handleDocumentSnapshotEvent(String listenerId, DocumentSnapshot documentSnapshot) { + WritableMap event = Arguments.createMap(); + WritableMap data = FirestoreSerialize.snapshotToWritableMap(documentSnapshot); + + event.putString("appName", appName); + event.putString("path", path); + event.putString("listenerId", listenerId); + event.putMap("documentSnapshot", data); + + Utils.sendEvent(reactContext, "firestore_document_sync_event", event); + } + + /** + * Handles a documentSnapshot error event + * + * @param listenerId + * @param exception + */ + private void handleDocumentSnapshotError(String listenerId, FirebaseFirestoreException exception) { + WritableMap event = Arguments.createMap(); + + event.putString("appName", appName); + event.putString("path", path); + event.putString("listenerId", listenerId); + event.putMap("error", RNFirebaseFirestore.getJSError(exception)); + + Utils.sendEvent(reactContext, "firestore_document_sync_event", event); + } +} diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestorePackage.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestorePackage.java new file mode 100644 index 00000000..f971daea --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestorePackage.java @@ -0,0 +1,39 @@ +package io.invertase.firebase.firestore; + +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; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("unused") +public class RNFirebaseFirestorePackage implements ReactPackage { + public RNFirebaseFirestorePackage() { + } + + /** + * @param reactContext react application context that can be used to create modules + * @return list of native modules to register with the newly created catalyst instance + */ + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new RNFirebaseFirestore(reactContext)); + + return modules; + } + + /** + * @param reactContext + * @return a list of view managers that should be registered with {@link UIManagerModule} + */ + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java index 70ac5c94..1a88b657 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java @@ -28,18 +28,6 @@ public class RNFirebaseMessagingPackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/android/src/main/java/io/invertase/firebase/perf/RNFirebasePerformancePackage.java b/android/src/main/java/io/invertase/firebase/perf/RNFirebasePerformancePackage.java index 04e52835..2ef2314b 100644 --- a/android/src/main/java/io/invertase/firebase/perf/RNFirebasePerformancePackage.java +++ b/android/src/main/java/io/invertase/firebase/perf/RNFirebasePerformancePackage.java @@ -28,18 +28,6 @@ public class RNFirebasePerformancePackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatability - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link 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 114a1218..a14cc738 100644 --- a/android/src/main/java/io/invertase/firebase/storage/RNFirebaseStoragePackage.java +++ b/android/src/main/java/io/invertase/firebase/storage/RNFirebaseStoragePackage.java @@ -33,18 +33,6 @@ public class RNFirebaseStoragePackage implements ReactPackage { return modules; } - /** - * @return list of JS modules to register with the newly created catalyst instance. - *

- * IMPORTANT: Note that only modules that needs to be accessible from the native code should be - * listed here. Also listing a native module here doesn't imply that the JS implementation of it - * will be automatically included in the JS bundle. - */ - // TODO: Removed in 0.47.0. Here for backwards compatibility - public List> createJSModules() { - return Collections.emptyList(); - } - /** * @param reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} diff --git a/docs/README.md b/docs/README.md index 9f81e70f..745450ce 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,7 @@ [![License](https://img.shields.io/npm/l/react-native-firebase.svg?style=flat-square)](/LICENSE) [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square)](https://discord.gg/t6bdqMs) [![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](https://www.patreon.com/invertase) +[![Twitter Follow](https://img.shields.io/twitter/follow/rnfirebase.svg?style=social&label=Follow)](https://twitter.com/rnfirebase) --- @@ -46,6 +47,7 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a | **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ |**?**| | **Crash Reporting** | ✅ | ✅ | ✅ | ❌ | | **Dynamic Links** | ❌ | ❌ | ❌ | ❌ | +| **Firestore** | ❌ | ❌ | ✅ | ❌ | | **Invites** | ❌ | ❌ | ❌ | ❌ | | **Performance Monitoring** | ✅ | ✅ | ✅ | ❌ | | **Realtime Database** | ✅ | ✅ | ✅ | ✅ | @@ -57,15 +59,10 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a --- ### Supported versions - React Native / Firebase -> The table below shows the supported version of `react-native-firebase` for different React Native versions +> The table below shows the supported versions of React Native and the Firebase SDKs for different versions of `react-native-firebase` -| | v0.36 - v0.39 | v0.40 - v0.46 | v0.47 + -| ------------------------------- | :---: | :---: | :---: | -| react-native-firebase | 1.X.X | 2.X.X | 2.1.X | - -> The table below shows the minimum supported versions of the Firebase SDKs for each version of `react-native-firebase` - -| | v1 | v2 | v3 | -| ---------------------- | :---: | :---: | :---: | -| Firebase Android SDK | 10.2.0+ | 11.0.0 + | 11.2.0 + | -| Firebase iOS SDK | 3.15.0+ | 4.0.0 + | 4.0.0 + | +| | 1.X.X | 2.0.X | 2.1.X / 2.2.X | 3.0.X | +|------------------------|-------------|-------------|-----------------|----------| +| React Native | 0.36 - 0.39 | 0.40 - 0.46 | 0.47 + | 0.48 + | +| Firebase Android SDK | 10.2.0 + | 11.0.0 + | 11.0.0 + | 11.4.2 + | +| Firebase iOS SDK | 3.15.0 + | 4.0.0 + | 4.0.0 + | 4.3.0 + | diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 6caea69d..618f8daf 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -24,6 +24,7 @@ - [Cloud Messaging](/modules/cloud-messaging) - [Crash Reporting](/modules/crash) - [Database](/modules/database) + - [Firestore (Beta)](/modules/firestore) - [Remote Config](/modules/config) - [Storage](/modules/storage) - [Transactions](/modules/transactions) diff --git a/docs/installation-android.md b/docs/installation-android.md index c26d9643..2fb83fc2 100644 --- a/docs/installation-android.md +++ b/docs/installation-android.md @@ -53,6 +53,7 @@ dependencies { compile "com.google.firebase:firebase-config:11.2.0" compile "com.google.firebase:firebase-crash:11.2.0" compile "com.google.firebase:firebase-database:11.2.0" + compile "com.google.firebase:firebase-firestore:11.2.0" compile "com.google.firebase:firebase-messaging:11.2.0" compile "com.google.firebase:firebase-perf:11.2.0" compile "com.google.firebase:firebase-storage:11.2.0" @@ -88,6 +89,7 @@ import io.invertase.firebase.auth.RNFirebaseAuthPackage; // Firebase Auth import io.invertase.firebase.config.RNFirebaseRemoteConfigPackage; // Firebase Remote Config import io.invertase.firebase.crash.RNFirebaseCrashPackage; // Firebase Crash Reporting import io.invertase.firebase.database.RNFirebaseDatabasePackage; // Firebase Realtime Database +import io.invertase.firebase.firestore.RNFirebaseFirestorePackage; // Firebase Firestore import io.invertase.firebase.messaging.RNFirebaseMessagingPackage; // Firebase Cloud Messaging import io.invertase.firebase.perf.RNFirebasePerformancePackage; // Firebase Performance import io.invertase.firebase.storage.RNFirebaseStoragePackage; // Firebase Storage @@ -107,6 +109,7 @@ public class MainApplication extends Application implements ReactApplication { new RNFirebaseRemoteConfigPackage(), new RNFirebaseCrashPackage(), new RNFirebaseDatabasePackage(), + new RNFirebaseFirestorePackage(), new RNFirebaseMessagingPackage(), new RNFirebasePerformancePackage(), new RNFirebaseStoragePackage() diff --git a/docs/installation-ios.md b/docs/installation-ios.md index 4c595ccf..6ee7d179 100644 --- a/docs/installation-ios.md +++ b/docs/installation-ios.md @@ -69,6 +69,7 @@ pod 'Firebase/Auth' pod 'Firebase/Crash' pod 'Firebase/Database' pod 'Firebase/DynamicLinks' +pod 'Firebase/Firestore' pod 'Firebase/Messaging' pod 'Firebase/RemoteConfig' pod 'Firebase/Storage' diff --git a/docs/migration-guide.md b/docs/migration-guide.md index 107f5e23..5a42bc87 100644 --- a/docs/migration-guide.md +++ b/docs/migration-guide.md @@ -2,7 +2,95 @@ ## From v2 to v3 - +The below is a quick summary of steps to take when migrating from v2 to v3 of RNFirebase. Please see the [v3 change log](https://github.com/invertase/react-native-firebase/releases/tag/v3.0.0) for detailed changes. + +** Please note, we're now using `Apache License 2.0` to license this library. ** + +##### 1) Install the latest version of RNFirebase: +> `npm i react-native-firebase@latest --save` + + + + +##### 2) Upgrade react-native version (only if you're currently lower than v0.48): + +- Follow the instructions [here](https://facebook.github.io/react-native/docs/upgrading.html) + + + + +##### 3) Update your JS code to reflect deprecations/breaking changes: + +- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [app] `new RNFirebase()` is no longer supported. See below for information about app initialisation. +- ![#f03c15](https://placehold.it/15/fdfd96/000000?text=+) **[deprecated]** [app] `initializeApp()` for apps that are already initialised natively (i.e. the default app initialised via google-services plist/json) will now log a deprecation warning. + - As these apps are already initialised natively there's no need to call `initializeApp` in your JS code. For now, calling it will just return the app that's already internally initialised - in a future version this will throw an `already initialized` exception. + - Accessing apps can now be done the same way as the web sdk, simply call `firebase.app()` to get the default app, or with the name of specific app as the first arg, e.g. `const meow = firebase.app('catsApp');` to get a specific app. +- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [auth] Third party providers now user `providerId` rather than `provider` as per the Web SDK. If you are manually creating your credentials, you will need to update the field name. +- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [database] Error messages and codes internally re-written to match the web sdk +- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [database] `ref.isEqual` now checks the query modifiers as well as the ref path (was just path before). With the release of multi apps/core support this check now also includes whether the refs are for the same app. +- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [database] on/off behaviour changes. Previous `off` behaviour was incorrect. A `SyncTree/Repo` implementation was added to provide the correct behaviour you'd expect in the web sdk. Whilst this is a breaking change it shouldn't be much of an issue if you've previously setup your on/off handling correctly. See #160 for specifics of this change. +- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [storage] UploadTaskSnapshot -> `downloadUrl` renamed to `downloadURL` to match web sdk + + + + + + +##### 4) Android - Update `android/build.gradle`: + + +- Check you are using google-services 3.1.0 or greater: +- You must add `maven { url 'https://maven.google.com' }` to your `android/build.gradle` as follows: +```groovy +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.google.gms:google-services:3.1.0' // CHECK VERSION HERE + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenLocal() + jcenter() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + // ADD THIS SECTION HERE + maven { + url 'https://maven.google.com' + } + } +} +``` + + + + + +##### 5) Android - Update `app/build.gradle`: + + +- You must update all your Firebase dependencies to 11.4.2. + + + + + +##### 6) iOS - Update podfile: + +- You need to check that you're running at least version 4.3.0 of the Firebase Pods + - Run `pod outdated` + - Run `pod update` ## From v1 to v2 diff --git a/docs/modules/firestore.md b/docs/modules/firestore.md new file mode 100644 index 00000000..edd809ec --- /dev/null +++ b/docs/modules/firestore.md @@ -0,0 +1,171 @@ + +# Firestore (Beta) + +RNFirebase mimics the [Firestore Web SDK](https://firebase.google.com/docs/database/web/read-and-write), whilst +providing support for devices in low/no data connection state. + +All Firestore operations are accessed via `firestore()`. + +Please note that Persistence (offline support) is enabled by default with Firestore on iOS and Android. + +## Add and Manage Data + +### Collections + +Read information about a collection example: +```javascript +firebase.firestore() + .collection('posts') + .get() + .then(querySnapshot => { + // Access all the documents in the collection + const docs = querySnapshot.docs; + // Access the list of document changes for the collection + const changes = querySnapshot.docChanges; + // Loop through the documents + querySnapshot.forEach((doc) => { + const value = doc.data(); + }) + }) +``` + +Add to a collection example (generated ID): +```javascript +firebase.firestore() + .collection('posts') + .add({ + title: 'Amazing post', + }) + .then(() => { + // Document added to collection and ID generated + // Will have path: `posts/{generatedId}` + }) +``` + +Add to a collection example (manual ID): +```javascript +firebase.firestore() + .collection('posts') + .doc('post1') + .set({ + title: 'My awesome post', + content: 'Some awesome content', + }) + .then(() => { + // Document added to collection with path: `posts/post1` + }) +``` + +### Documents + +There are multiple ways to read a document. The following are equivalent examples: +```javascript +firebase.firestore() + .doc('posts/posts1') + .get((documentSnapshot) => { + const value = documentSnapshot.data(); + }); + +firebase.firestore() + .collection('posts') + .doc('posts1') + .get((documentSnapshot) => { + const value = documentSnapshot.data(); + }); +``` + +Create a document example: +```javascript +firebase.firestore() + .doc('posts/posts1') + .set({ + title: 'My awesome post', + content: 'Some awesome content', + }) + .then(() => { + // Document created + }); +``` + +Updating a document example: +```javascript +firebase.firestore() + .doc('posts/posts1') + .update({ + title: 'My awesome post', + }) + .then(() => { + // Document created + }); +``` + +Deleting a document example: +```javascript +firebase.firestore() + .doc('posts/posts1') + .delete() + .then(() => { + // Document deleted + }); +``` + +### Batching document updates + +Writes, updates and deletes to documents can be batched and committed atomically as follows: + +```javascript +const ayRef = firebase.firestore().doc('places/AY'); +const lRef = firebase.firestore().doc('places/LON'); +const nycRef = firebase.firestore().doc('places/NYC'); +const sfRef = firebase.firestore().doc('places/SF'); + +firebase.firestore() + .batch() + .set(ayRef, { name: 'Aylesbury' }) + .set(lRef, { name: 'London' }) + .set(nycRef, { name: 'New York City' }) + .set(sfRef, { name: 'San Francisco' }) + .update(nycRef, { population: 1000000 }) + .update(sfRef, { name: 'San Fran' }) + .set(lRef, { population: 3000000 }, { merge: true }) + .delete(ayRef) + .commit() + .then(() => { + // Would end up with three documents in the collection: London, New York City and San Francisco + }); +``` + +### Transactions + +Coming soon + +## Realtime Updates + +### Collections + +Listen to collection updates example: +```javascript +firebase.firestore() + .collection('cities') + .where('state', '==', 'CA') + .onSnapshot((querySnapshot) => { + querySnapshot.forEach((doc) => { + // DocumentSnapshot available + }) + }) +``` + +The snapshot handler will receive a new query snapshot every time the query results change (that is, when a document is added, removed, or modified). + +### Documents + +Listen to document updates example: +```javascript +firebase.firestore() + .doc('posts/post1') + .onSnapshot((documentSnapshot) => { + // DocumentSnapshot available + }) +``` + +The snapshot handler will receive the current contents of the document, and any subsequent changes to the document. diff --git a/ios/RNFirebase.xcodeproj/project.pbxproj b/ios/RNFirebase.xcodeproj/project.pbxproj index 5d91af3c..ed509361 100644 --- a/ios/RNFirebase.xcodeproj/project.pbxproj +++ b/ios/RNFirebase.xcodeproj/project.pbxproj @@ -12,6 +12,9 @@ 8323CF071F6FBD870071420B /* NativeExpressComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 8323CF011F6FBD870071420B /* NativeExpressComponent.m */; }; 8323CF081F6FBD870071420B /* RNFirebaseAdMobBannerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8323CF031F6FBD870071420B /* RNFirebaseAdMobBannerManager.m */; }; 8323CF091F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8323CF051F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m */; }; + 8376F7141F7C149100D45A85 /* RNFirebaseFirestoreDocumentReference.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F70E1F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.m */; }; + 8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F7101F7C149000D45A85 /* RNFirebaseFirestore.m */; }; + 8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */; }; 839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D914F1EF3E20A0077C7C8 /* RNFirebaseAdMob.m */; }; 839D916D1EF3E20B0077C7C8 /* RNFirebaseAdMobInterstitial.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91511EF3E20A0077C7C8 /* RNFirebaseAdMobInterstitial.m */; }; 839D916E1EF3E20B0077C7C8 /* RNFirebaseAdMobRewardedVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91531EF3E20A0077C7C8 /* RNFirebaseAdMobRewardedVideo.m */; }; @@ -50,6 +53,12 @@ 8323CF031F6FBD870071420B /* RNFirebaseAdMobBannerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAdMobBannerManager.m; sourceTree = ""; }; 8323CF041F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMobNativeExpressManager.h; sourceTree = ""; }; 8323CF051F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAdMobNativeExpressManager.m; sourceTree = ""; }; + 8376F70E1F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseFirestoreDocumentReference.m; sourceTree = ""; }; + 8376F70F1F7C149000D45A85 /* RNFirebaseFirestore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestore.h; sourceTree = ""; }; + 8376F7101F7C149000D45A85 /* RNFirebaseFirestore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseFirestore.m; sourceTree = ""; }; + 8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseFirestoreCollectionReference.m; sourceTree = ""; }; + 8376F7121F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestoreDocumentReference.h; sourceTree = ""; }; + 8376F7131F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestoreCollectionReference.h; sourceTree = ""; }; 839D914E1EF3E20A0077C7C8 /* RNFirebaseAdMob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMob.h; sourceTree = ""; }; 839D914F1EF3E20A0077C7C8 /* RNFirebaseAdMob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAdMob.m; sourceTree = ""; }; 839D91501EF3E20A0077C7C8 /* RNFirebaseAdMobInterstitial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMobInterstitial.h; sourceTree = ""; }; @@ -105,6 +114,7 @@ 839D915A1EF3E20A0077C7C8 /* config */, 839D915D1EF3E20A0077C7C8 /* crash */, 839D91601EF3E20A0077C7C8 /* database */, + 8376F70D1F7C141500D45A85 /* firestore */, 839D91631EF3E20A0077C7C8 /* messaging */, 839D91661EF3E20A0077C7C8 /* perf */, 839D91691EF3E20A0077C7C8 /* storage */, @@ -115,6 +125,20 @@ ); sourceTree = ""; }; + 8376F70D1F7C141500D45A85 /* firestore */ = { + isa = PBXGroup; + children = ( + 8376F70F1F7C149000D45A85 /* RNFirebaseFirestore.h */, + 8376F7101F7C149000D45A85 /* RNFirebaseFirestore.m */, + 8376F7131F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.h */, + 8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */, + 8376F7121F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.h */, + 8376F70E1F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.m */, + ); + name = firestore; + path = RNFirebase/firestore; + sourceTree = ""; + }; 839D914D1EF3E20A0077C7C8 /* admob */ = { isa = PBXGroup; children = ( @@ -277,9 +301,12 @@ files = ( 839D916E1EF3E20B0077C7C8 /* RNFirebaseAdMobRewardedVideo.m in Sources */, 839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */, + 8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */, 839D91761EF3E20B0077C7C8 /* RNFirebaseStorage.m in Sources */, + 8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */, 839D91701EF3E20B0077C7C8 /* RNFirebaseAuth.m in Sources */, 8323CF091F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m in Sources */, + 8376F7141F7C149100D45A85 /* RNFirebaseFirestoreDocumentReference.m in Sources */, 839D916F1EF3E20B0077C7C8 /* RNFirebaseAnalytics.m in Sources */, 839D91711EF3E20B0077C7C8 /* RNFirebaseRemoteConfig.m in Sources */, D950369E1D19C77400F7094D /* RNFirebase.m in Sources */, diff --git a/ios/RNFirebase/RNFirebaseEvents.h b/ios/RNFirebase/RNFirebaseEvents.h index 65aba648..7cccf762 100644 --- a/ios/RNFirebase/RNFirebaseEvents.h +++ b/ios/RNFirebase/RNFirebaseEvents.h @@ -17,6 +17,10 @@ static NSString *const DATABASE_CHILD_MODIFIED_EVENT = @"child_changed"; static NSString *const DATABASE_CHILD_REMOVED_EVENT = @"child_removed"; static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved"; +// Firestore +static NSString *const FIRESTORE_COLLECTION_SYNC_EVENT = @"firestore_collection_sync_event"; +static NSString *const FIRESTORE_DOCUMENT_SYNC_EVENT = @"firestore_document_sync_event"; + // Storage static NSString *const STORAGE_EVENT = @"storage_event"; static NSString *const STORAGE_ERROR = @"storage_error"; diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestore.h b/ios/RNFirebase/firestore/RNFirebaseFirestore.h new file mode 100644 index 00000000..e93595b1 --- /dev/null +++ b/ios/RNFirebase/firestore/RNFirebaseFirestore.h @@ -0,0 +1,26 @@ +#ifndef RNFirebaseFirestore_h +#define RNFirebaseFirestore_h + +#import + +#if __has_include() + +#import +#import +#import + +@interface RNFirebaseFirestore : RCTEventEmitter {} + ++ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error; + ++ (FIRFirestore *)getFirestoreForApp:(NSString *)appName; ++ (NSDictionary *)getJSError:(NSError *)nativeError; + +@end + +#else +@interface RNFirebaseFirestore : NSObject +@end +#endif + +#endif diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestore.m b/ios/RNFirebase/firestore/RNFirebaseFirestore.m new file mode 100644 index 00000000..e5261c52 --- /dev/null +++ b/ios/RNFirebase/firestore/RNFirebaseFirestore.m @@ -0,0 +1,223 @@ +#import "RNFirebaseFirestore.h" + +#if __has_include() + +#import +#import "RNFirebaseEvents.h" +#import "RNFirebaseFirestoreCollectionReference.h" +#import "RNFirebaseFirestoreDocumentReference.h" + +@implementation RNFirebaseFirestore +RCT_EXPORT_MODULE(); + +- (id)init { + self = [super init]; + if (self != nil) { + + } + return self; +} + +RCT_EXPORT_METHOD(collectionGet:(NSString *) appName + path:(NSString *) path + filters:(NSArray *) filters + orders:(NSArray *) orders + options:(NSDictionary *) options + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + [[self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options] get:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(collectionOffSnapshot:(NSString *) appName + path:(NSString *) path + filters:(NSArray *) filters + orders:(NSArray *) orders + options:(NSDictionary *) options + listenerId:(nonnull NSString *) listenerId) { + [RNFirebaseFirestoreCollectionReference offSnapshot:listenerId]; +} + +RCT_EXPORT_METHOD(collectionOnSnapshot:(NSString *) appName + path:(NSString *) path + filters:(NSArray *) filters + orders:(NSArray *) orders + options:(NSDictionary *) options + listenerId:(nonnull NSString *) listenerId) { + RNFirebaseFirestoreCollectionReference *ref = [self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options]; + [ref onSnapshot:listenerId]; +} + +RCT_EXPORT_METHOD(documentBatch:(NSString *) appName + writes:(NSArray *) writes + commitOptions:(NSDictionary *) commitOptions + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:appName]; + FIRWriteBatch *batch = [firestore batch]; + + for (NSDictionary *write in writes) { + NSString *type = write[@"type"]; + NSString *path = write[@"path"]; + NSDictionary *data = write[@"data"]; + + FIRDocumentReference *ref = [firestore documentWithPath:path]; + + if ([type isEqualToString:@"DELETE"]) { + batch = [batch deleteDocument:ref]; + } else if ([type isEqualToString:@"SET"]) { + NSDictionary *options = write[@"options"]; + if (options && options[@"merge"]) { + batch = [batch setData:data forDocument:ref options:[FIRSetOptions merge]]; + } else { + batch = [batch setData:data forDocument:ref]; + } + } else if ([type isEqualToString:@"UPDATE"]) { + batch = [batch updateData:data forDocument:ref]; + } + } + + [batch commitWithCompletion:^(NSError * _Nullable error) { + if (error) { + [RNFirebaseFirestore promiseRejectException:reject error:error]; + } else { + NSMutableArray *result = [[NSMutableArray alloc] init]; + for (NSDictionary *write in writes) { + // Missing fields from web SDK + // writeTime + [result addObject:@{}]; + } + resolve(result); + } + }]; +} + +RCT_EXPORT_METHOD(documentCollections:(NSString *) appName + path:(NSString *) path + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + [[self getDocumentForAppPath:appName path:path] get:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(documentCreate:(NSString *) appName + path:(NSString *) path + data:(NSDictionary *) data + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + [[self getDocumentForAppPath:appName path:path] create:data resolver:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(documentDelete:(NSString *) appName + path:(NSString *) path + options:(NSDictionary *) options + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + [[self getDocumentForAppPath:appName path:path] delete:options resolver:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(documentGet:(NSString *) appName + path:(NSString *) path + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + [[self getDocumentForAppPath:appName path:path] get:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(documentGetAll:(NSString *) appName + documents:(NSString *) documents + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + // Not supported on iOS out of the box +} + +RCT_EXPORT_METHOD(documentOffSnapshot:(NSString *) appName + path:(NSString *) path + listenerId:(nonnull NSString *) listenerId) { + [RNFirebaseFirestoreDocumentReference offSnapshot:listenerId]; +} + +RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *) appName + path:(NSString *) path + listenerId:(nonnull NSString *) listenerId) { + RNFirebaseFirestoreDocumentReference *ref = [self getDocumentForAppPath:appName path:path]; + [ref onSnapshot:listenerId]; +} + +RCT_EXPORT_METHOD(documentSet:(NSString *) appName + path:(NSString *) path + data:(NSDictionary *) data + options:(NSDictionary *) options + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + [[self getDocumentForAppPath:appName path:path] set:data options:options resolver:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(documentUpdate:(NSString *) appName + path:(NSString *) path + data:(NSDictionary *) data + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + [[self getDocumentForAppPath:appName path:path] update:data resolver:resolve rejecter:reject]; +} + +/* + * INTERNALS/UTILS + */ ++ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error { + NSDictionary *jsError = [RNFirebaseFirestore getJSError:error]; + reject([jsError valueForKey:@"code"], [jsError valueForKey:@"message"], error); +} + ++ (FIRFirestore *)getFirestoreForApp:(NSString *)appName { + FIRApp *app = [FIRApp appNamed:appName]; + return [FIRFirestore firestoreForApp:app]; +} + +- (RNFirebaseFirestoreCollectionReference *)getCollectionForAppPath:(NSString *)appName path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options { + return [[RNFirebaseFirestoreCollectionReference alloc] initWithPathAndModifiers:self app:appName path:path filters:filters orders:orders options:options]; +} + +- (RNFirebaseFirestoreDocumentReference *)getDocumentForAppPath:(NSString *)appName path:(NSString *)path { + return [[RNFirebaseFirestoreDocumentReference alloc] initWithPath:self app:appName path:path]; +} + +// TODO: Move to error util for use in other modules ++ (NSString *)getMessageWithService:(NSString *)message service:(NSString *)service fullCode:(NSString *)fullCode { + return [NSString stringWithFormat:@"%@: %@ (%@).", service, message, [fullCode lowercaseString]]; +} + ++ (NSString *)getCodeWithService:(NSString *)service code:(NSString *)code { + return [NSString stringWithFormat:@"%@/%@", [service lowercaseString], [code lowercaseString]]; +} + ++ (NSDictionary *)getJSError:(NSError *)nativeError { + NSMutableDictionary *errorMap = [[NSMutableDictionary alloc] init]; + [errorMap setValue:@(nativeError.code) forKey:@"nativeErrorCode"]; + [errorMap setValue:[nativeError localizedDescription] forKey:@"nativeErrorMessage"]; + + NSString *code; + NSString *message; + NSString *service = @"Firestore"; + + // TODO: Proper error codes + switch (nativeError.code) { + default: + code = [RNFirebaseFirestore getCodeWithService:service code:@"unknown"]; + message = [RNFirebaseFirestore getMessageWithService:@"An unknown error occurred." service:service fullCode:code]; + break; + } + + [errorMap setValue:code forKey:@"code"]; + [errorMap setValue:message forKey:@"message"]; + + return errorMap; +} + +- (NSArray *)supportedEvents { + return @[FIRESTORE_COLLECTION_SYNC_EVENT, FIRESTORE_DOCUMENT_SYNC_EVENT]; +} + +@end + +#else +@implementation RNFirebaseFirestore +@end +#endif diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h new file mode 100644 index 00000000..b03904b4 --- /dev/null +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h @@ -0,0 +1,35 @@ +#ifndef RNFirebaseFirestoreCollectionReference_h +#define RNFirebaseFirestoreCollectionReference_h +#import + +#if __has_include() + +#import +#import +#import "RNFirebaseEvents.h" +#import "RNFirebaseFirestore.h" +#import "RNFirebaseFirestoreDocumentReference.h" + +@interface RNFirebaseFirestoreCollectionReference : NSObject +@property RCTEventEmitter *emitter; +@property NSString *app; +@property NSString *path; +@property NSArray *filters; +@property NSArray *orders; +@property NSDictionary *options; +@property FIRQuery *query; + +- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options; +- (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; ++ (void)offSnapshot:(NSString *)listenerId; +- (void)onSnapshot:(NSString *)listenerId; ++ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot; +@end + +#else + +@interface RNFirebaseFirestoreCollectionReference : NSObject +@end +#endif + +#endif diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m new file mode 100644 index 00000000..69388409 --- /dev/null +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m @@ -0,0 +1,201 @@ +#import "RNFirebaseFirestoreCollectionReference.h" + +@implementation RNFirebaseFirestoreCollectionReference + +#if __has_include() + +static NSMutableDictionary *_listeners; + +- (id)initWithPathAndModifiers:(RCTEventEmitter *) emitter + app:(NSString *) app + path:(NSString *) path + filters:(NSArray *) filters + orders:(NSArray *) orders + options:(NSDictionary *) options { + self = [super init]; + if (self) { + _emitter = emitter; + _app = app; + _path = path; + _filters = filters; + _orders = orders; + _options = options; + _query = [self buildQuery]; + } + // Initialise the static listeners object if required + if (!_listeners) { + _listeners = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)get:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + [_query getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { + if (error) { + [RNFirebaseFirestore promiseRejectException:reject error:error]; + } else { + NSDictionary *data = [RNFirebaseFirestoreCollectionReference snapshotToDictionary:snapshot]; + resolve(data); + } + }]; +} + ++ (void)offSnapshot:(NSString *) listenerId { + id listener = _listeners[listenerId]; + if (listener) { + [_listeners removeObjectForKey:listenerId]; + [listener remove]; + } +} + +- (void)onSnapshot:(NSString *) listenerId { + if (_listeners[listenerId] == nil) { + id listenerBlock = ^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { + if (error) { + id listener = _listeners[listenerId]; + if (listener) { + [_listeners removeObjectForKey:listenerId]; + [listener remove]; + } + [self handleQuerySnapshotError:listenerId error:error]; + } else { + [self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot]; + } + }; + id listener = [_query addSnapshotListener:listenerBlock]; + _listeners[listenerId] = listener; + } +} + +- (FIRQuery *)buildQuery { + FIRQuery *query = (FIRQuery*)[[RNFirebaseFirestore getFirestoreForApp:_app] collectionWithPath:_path]; + query = [self applyFilters:query]; + query = [self applyOrders:query]; + query = [self applyOptions:query]; + + return query; +} + +- (FIRQuery *)applyFilters:(FIRQuery *) query { + for (NSDictionary *filter in _filters) { + NSString *fieldPath = filter[@"fieldPath"]; + NSString *operator = filter[@"operator"]; + // TODO: Validate this works + id value = filter[@"value"]; + + if ([operator isEqualToString:@"EQUAL"]) { + query = [query queryWhereField:fieldPath isEqualTo:value]; + } else if ([operator isEqualToString:@"GREATER_THAN"]) { + query = [query queryWhereField:fieldPath isGreaterThan:value]; + } else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) { + query = [query queryWhereField:fieldPath isGreaterThanOrEqualTo:value]; + } else if ([operator isEqualToString:@"LESS_THAN"]) { + query = [query queryWhereField:fieldPath isLessThan:value]; + } else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) { + query = [query queryWhereField:fieldPath isLessThanOrEqualTo:value]; + } + } + return query; +} + +- (FIRQuery *)applyOrders:(FIRQuery *) query { + for (NSDictionary *order in _orders) { + NSString *direction = order[@"direction"]; + NSString *fieldPath = order[@"fieldPath"]; + + query = [query queryOrderedByField:fieldPath descending:([direction isEqualToString:@"DESCENDING"])]; + } + return query; +} + +- (FIRQuery *)applyOptions:(FIRQuery *) query { + if (_options[@"endAt"]) { + query = [query queryEndingAtValues:_options[@"endAt"]]; + } + if (_options[@"endBefore"]) { + query = [query queryEndingBeforeValues:_options[@"endBefore"]]; + } + if (_options[@"offset"]) { + // iOS doesn't support offset + } + if (_options[@"selectFields"]) { + // iOS doesn't support selectFields + } + if (_options[@"startAfter"]) { + query = [query queryStartingAfterValues:_options[@"startAfter"]]; + } + if (_options[@"startAt"]) { + query = [query queryStartingAtValues:_options[@"startAt"]]; + } + return query; +} + +- (void)handleQuerySnapshotError:(NSString *)listenerId + error:(NSError *)error { + NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; + [event setValue:_app forKey:@"appName"]; + [event setValue:_path forKey:@"path"]; + [event setValue:listenerId forKey:@"listenerId"]; + [event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"]; + + [_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; +} + +- (void)handleQuerySnapshotEvent:(NSString *)listenerId + querySnapshot:(FIRQuerySnapshot *)querySnapshot { + NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; + [event setValue:_app forKey:@"appName"]; + [event setValue:_path forKey:@"path"]; + [event setValue:listenerId forKey:@"listenerId"]; + [event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"]; + + [_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; +} + ++ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot { + NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init]; + [snapshot setValue:[self documentChangesToArray:querySnapshot.documentChanges] forKey:@"changes"]; + [snapshot setValue:[self documentSnapshotsToArray:querySnapshot.documents] forKey:@"documents"]; + + return snapshot; +} + ++ (NSArray *)documentChangesToArray:(NSArray *) documentChanges { + NSMutableArray *changes = [[NSMutableArray alloc] init]; + for (FIRDocumentChange *change in documentChanges) { + [changes addObject:[self documentChangeToDictionary:change]]; + } + + return changes; +} + ++ (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange { + NSMutableDictionary *change = [[NSMutableDictionary alloc] init]; + [change setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentChange.document] forKey:@"document"]; + [change setValue:@(documentChange.newIndex) forKey:@"newIndex"]; + [change setValue:@(documentChange.oldIndex) forKey:@"oldIndex"]; + + if (documentChange.type == FIRDocumentChangeTypeAdded) { + [change setValue:@"added" forKey:@"type"]; + } else if (documentChange.type == FIRDocumentChangeTypeRemoved) { + [change setValue:@"removed" forKey:@"type"]; + } else if (documentChange.type == FIRDocumentChangeTypeModified) { + [change setValue:@"modified" forKey:@"type"]; + } + + return change; +} + ++ (NSArray *)documentSnapshotsToArray:(NSArray *) documentSnapshots { + NSMutableArray *snapshots = [[NSMutableArray alloc] init]; + for (FIRDocumentSnapshot *snapshot in documentSnapshots) { + [snapshots addObject:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot]]; + } + + return snapshots; +} + +#endif + +@end diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h new file mode 100644 index 00000000..51f6d257 --- /dev/null +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h @@ -0,0 +1,38 @@ +#ifndef RNFirebaseFirestoreDocumentReference_h +#define RNFirebaseFirestoreDocumentReference_h + +#import + +#if __has_include() + +#import +#import +#import "RNFirebaseEvents.h" +#import "RNFirebaseFirestore.h" + +@interface RNFirebaseFirestoreDocumentReference : NSObject +@property RCTEventEmitter *emitter; +@property NSString *app; +@property NSString *path; +@property FIRDocumentReference *ref; + +- (id)initWithPath:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path; +- (void)collections:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; +- (void)create:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; +- (void)delete:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; +- (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; ++ (void)offSnapshot:(NSString *)listenerId; +- (void)onSnapshot:(NSString *)listenerId; +- (void)set:(NSDictionary *)data options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; +- (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; +- (BOOL)hasListeners; ++ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot; +@end + +#else + +@interface RNFirebaseFirestoreDocumentReference : NSObject +@end +#endif + +#endif diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m new file mode 100644 index 00000000..4e51a028 --- /dev/null +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m @@ -0,0 +1,162 @@ +#import "RNFirebaseFirestoreDocumentReference.h" + +@implementation RNFirebaseFirestoreDocumentReference + +#if __has_include() + +static NSMutableDictionary *_listeners; + +- (id)initWithPath:(RCTEventEmitter *)emitter + app:(NSString *) app + path:(NSString *) path { + self = [super init]; + if (self) { + _emitter = emitter; + _app = app; + _path = path; + _ref = [[RNFirebaseFirestore getFirestoreForApp:_app] documentWithPath:_path]; + } + // Initialise the static listeners object if required + if (!_listeners) { + _listeners = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)collections:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + // Not supported on iOS +} + +- (void)create:(NSDictionary *) data + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + // Not supported on iOS out of the box +} + +- (void)delete:(NSDictionary *)options + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + [_ref deleteDocumentWithCompletion:^(NSError * _Nullable error) { + [RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject]; + }]; +} + +- (void)get:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + [_ref getDocumentWithCompletion:^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) { + if (error) { + [RNFirebaseFirestore promiseRejectException:reject error:error]; + } else { + NSDictionary *data = [RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot]; + resolve(data); + } + }]; +} + ++ (void)offSnapshot:(NSString *) listenerId { + id listener = _listeners[listenerId]; + if (listener) { + [_listeners removeObjectForKey:listenerId]; + [listener remove]; + } +} + +- (void)onSnapshot:(NSString *) listenerId { + if (_listeners[listenerId] == nil) { + id listenerBlock = ^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) { + if (error) { + id listener = _listeners[listenerId]; + if (listener) { + [_listeners removeObjectForKey:listenerId]; + [listener remove]; + } + [self handleDocumentSnapshotError:listenerId error:error]; + } else { + [self handleDocumentSnapshotEvent:listenerId documentSnapshot:snapshot]; + } + }; + + id listener = [_ref addSnapshotListener:listenerBlock]; + _listeners[listenerId] = listener; + } +} + +- (void)set:(NSDictionary *) data + options:(NSDictionary *) options + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + if (options && options[@"merge"]) { + [_ref setData:data options:[FIRSetOptions merge] completion:^(NSError * _Nullable error) { + [RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject]; + }]; + } else { + [_ref setData:data completion:^(NSError * _Nullable error) { + [RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject]; + }]; + } +} + +- (void)update:(NSDictionary *) data + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + [_ref updateData:data completion:^(NSError * _Nullable error) { + [RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject]; + }]; +} + +- (BOOL)hasListeners { + return [[_listeners allKeys] count] > 0; +} + ++ (void)handleWriteResponse:(NSError *) error + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject { + if (error) { + [RNFirebaseFirestore promiseRejectException:reject error:error]; + } else { + // Missing fields from web SDK + // writeTime + resolve(@{}); + } +} + ++ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot { + NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init]; + [snapshot setValue:documentSnapshot.reference.path forKey:@"path"]; + if (documentSnapshot.exists) { + [snapshot setValue:documentSnapshot.data forKey:@"data"]; + } + // Missing fields from web SDK + // createTime + // readTime + // updateTime + + return snapshot; +} + +- (void)handleDocumentSnapshotError:(NSString *)listenerId + error:(NSError *)error { + NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; + [event setValue:_app forKey:@"appName"]; + [event setValue:_path forKey:@"path"]; + [event setValue:listenerId forKey:@"listenerId"]; + [event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"]; + + [_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event]; +} + +- (void)handleDocumentSnapshotEvent:(NSString *)listenerId + documentSnapshot:(FIRDocumentSnapshot *)documentSnapshot { + NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; + [event setValue:_app forKey:@"appName"]; + [event setValue:_path forKey:@"path"]; + [event setValue:listenerId forKey:@"listenerId"]; + [event setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentSnapshot] forKey:@"documentSnapshot"]; + + [_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event]; +} + +#endif + +@end diff --git a/lib/firebase-app.js b/lib/firebase-app.js index 24ce0150..4f451203 100644 --- a/lib/firebase-app.js +++ b/lib/firebase-app.js @@ -12,6 +12,7 @@ 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'; const FirebaseCoreModule = NativeModules.RNFirebase; @@ -32,6 +33,7 @@ export default class FirebaseApp { this.config = this._staticsOrModuleInstance({}, RemoteConfig); this.crash = this._staticsOrModuleInstance({}, Crash); this.database = this._staticsOrModuleInstance(DatabaseStatics, Database); + this.firestore = this._staticsOrModuleInstance(FirestoreStatics, Firestore); this.messaging = this._staticsOrModuleInstance(MessagingStatics, Messaging); this.perf = this._staticsOrModuleInstance({}, Performance); this.storage = this._staticsOrModuleInstance(StorageStatics, Storage); diff --git a/lib/firebase.js b/lib/firebase.js index b31352f5..c67b4f8f 100644 --- a/lib/firebase.js +++ b/lib/firebase.js @@ -20,6 +20,7 @@ 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'; const FirebaseCoreModule = NativeModules.RNFirebase; @@ -47,6 +48,7 @@ class FirebaseCore { this.config = this._appNamespaceOrStatics({}, RemoteConfig); this.crash = this._appNamespaceOrStatics({}, Crash); this.database = this._appNamespaceOrStatics(DatabaseStatics, Database); + this.firestore = this._appNamespaceOrStatics(FirestoreStatics, Firestore); this.messaging = this._appNamespaceOrStatics(MessagingStatics, Messaging); this.perf = this._appNamespaceOrStatics(DatabaseStatics, Performance); this.storage = this._appNamespaceOrStatics(StorageStatics, Storage); diff --git a/lib/modules/firestore/CollectionReference.js b/lib/modules/firestore/CollectionReference.js new file mode 100644 index 00000000..3c53be1d --- /dev/null +++ b/lib/modules/firestore/CollectionReference.js @@ -0,0 +1,105 @@ +/** + * @flow + * CollectionReference representation wrapper + */ +import DocumentReference from './DocumentReference'; +import Path from './Path'; +import Query from './Query'; +import QuerySnapshot from './QuerySnapshot'; +import { firestoreAutoId } from '../../utils'; + +import type { Direction, Operator } from './Query'; + + /** + * @class CollectionReference + */ +export default class CollectionReference { + _collectionPath: Path; + _firestore: Object; + _query: Query; + + constructor(firestore: Object, collectionPath: Path) { + this._collectionPath = collectionPath; + this._firestore = firestore; + this._query = new Query(firestore, collectionPath); + } + + get firestore(): Object { + 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: { [string]: any }): 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(fieldValues: any): Query { + return this._query.endAt(fieldValues); + } + + endBefore(fieldValues: any): Query { + return this._query.endBefore(fieldValues); + } + + get(): Promise { + return this._query.get(); + } + + limit(n: number): Query { + return this._query.limit(n); + } + + offset(n: number): Query { + return this._query.offset(n); + } + + onSnapshot(onNext: () => any, onError?: () => any): () => void { + return this._query.onSnapshot(onNext, onError); + } + + orderBy(fieldPath: string, directionStr?: Direction): Query { + return this._query.orderBy(fieldPath, directionStr); + } + + select(varArgs: string[]): Query { + return this._query.select(varArgs); + } + + startAfter(fieldValues: any): Query { + return this._query.startAfter(fieldValues); + } + + startAt(fieldValues: any): Query { + return this._query.startAt(fieldValues); + } + + stream(): Stream { + return this._query.stream(); + } + + where(fieldPath: string, opStr: Operator, value: any): Query { + return this._query.where(fieldPath, opStr, value); + } +} diff --git a/lib/modules/firestore/DocumentChange.js b/lib/modules/firestore/DocumentChange.js new file mode 100644 index 00000000..c9c98b98 --- /dev/null +++ b/lib/modules/firestore/DocumentChange.js @@ -0,0 +1,46 @@ +/** + * @flow + * DocumentChange representation wrapper + */ +import DocumentSnapshot from './DocumentSnapshot'; + + +export type DocumentChangeNativeData = { + document: DocumentSnapshot, + newIndex: number, + oldIndex: number, + type: string, +} + + /** + * @class DocumentChange + */ +export default class DocumentChange { + _document: DocumentSnapshot; + _newIndex: number; + _oldIndex: number; + _type: string; + + constructor(nativeData: DocumentChangeNativeData) { + this._document = 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/lib/modules/firestore/DocumentReference.js b/lib/modules/firestore/DocumentReference.js new file mode 100644 index 00000000..9873c2cd --- /dev/null +++ b/lib/modules/firestore/DocumentReference.js @@ -0,0 +1,146 @@ +/** + * @flow + * DocumentReference representation wrapper + */ +import CollectionReference from './CollectionReference'; +import DocumentSnapshot from './DocumentSnapshot'; +import Path from './Path'; +import INTERNALS from './../../internals'; +import { firestoreAutoId } from '../../utils'; + +export type DeleteOptions = { + lastUpdateTime?: string, +} + +export type WriteOptions = { + merge?: boolean, +} + +export type WriteResult = { + writeTime: string, +} + + /** + * @class DocumentReference + */ +export default class DocumentReference { + _documentPath: Path; + _firestore: Object; + + constructor(firestore: Object, documentPath: Path) { + this._documentPath = documentPath; + this._firestore = firestore; + } + + get firestore(): Object { + return this._firestore; + } + + get id(): string | null { + return this._documentPath.id; + } + + get parent(): CollectionReference | null { + const parentPath = this._documentPath.parent(); + return parentPath ? new CollectionReference(this._firestore, parentPath) : null; + } + + 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); + } + + create(data: { [string]: any }): Promise { + /* return this._firestore._native + .documentCreate(this.path, data); */ + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('DocumentReference', 'create')); + } + + delete(deleteOptions?: DeleteOptions): Promise { + return this._firestore._native + .documentDelete(this.path, deleteOptions); + } + + get(): Promise { + return this._firestore._native + .documentGet(this.path) + .then(result => new DocumentSnapshot(this._firestore, result)); + } + + getCollections(): Promise { + /* return this._firestore._native + .documentCollections(this.path) + .then((collectionIds) => { + const collections = []; + + for (const collectionId of collectionIds) { + collections.push(this.collection(collectionId)); + } + + return collections; + }); */ + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('DocumentReference', 'getCollections')); + } + + onSnapshot(onNext: Function, onError?: Function): () => void { + // TODO: Validation + const listenerId = firestoreAutoId(); + + const listener = (nativeDocumentSnapshot) => { + const documentSnapshot = new DocumentSnapshot(this, nativeDocumentSnapshot); + onNext(documentSnapshot); + }; + + // Listen to snapshot events + this._firestore.on( + this._firestore._getAppEventName(`onDocumentSnapshot:${listenerId}`), + listener, + ); + + // Listen for snapshot error events + if (onError) { + this._firestore.on( + this._firestore._getAppEventName(`onDocumentSnapshotError:${listenerId}`), + onError, + ); + } + + // Add the native listener + this._firestore._native + .documentOnSnapshot(this.path, listenerId); + + // Return an unsubscribe method + return this._offDocumentSnapshot.bind(this, listenerId, listener); + } + + set(data: { [string]: any }, writeOptions?: WriteOptions): Promise { + return this._firestore._native + .documentSet(this.path, data, writeOptions); + } + + // TODO: Update to new update method signature + update(data: { [string]: any }): Promise { + return this._firestore._native + .documentUpdate(this.path, data); + } + + /** + * Remove document snapshot listener + * @param listener + */ + _offDocumentSnapshot(listenerId: number, listener: Function) { + this._firestore.log.info('Removing onDocumentSnapshot listener'); + this._firestore.removeListener(this._firestore._getAppEventName(`onDocumentSnapshot:${listenerId}`), listener); + this._firestore.removeListener(this._firestore._getAppEventName(`onDocumentSnapshotError:${listenerId}`), listener); + this._firestore._native + .documentOffSnapshot(this.path, listenerId); + } +} diff --git a/lib/modules/firestore/DocumentSnapshot.js b/lib/modules/firestore/DocumentSnapshot.js new file mode 100644 index 00000000..f3bc8823 --- /dev/null +++ b/lib/modules/firestore/DocumentSnapshot.js @@ -0,0 +1,69 @@ +/** + * @flow + * DocumentSnapshot representation wrapper + */ +import DocumentReference from './DocumentReference'; +import Path from './Path'; +import INTERNALS from './../../internals'; + +export type DocumentSnapshotNativeData = { + createTime: string, + data: Object, + path: string, + readTime: string, + updateTime: string, +} + +/** + * @class DocumentSnapshot + */ +export default class DocumentSnapshot { + _createTime: string; + _data: Object; + _readTime: string; + _ref: DocumentReference; + _updateTime: string; + + constructor(firestore: Object, nativeData: DocumentSnapshotNativeData) { + this._createTime = nativeData.createTime; + this._data = nativeData.data; + this._ref = new DocumentReference(firestore, Path.fromName(nativeData.path)); + this._readTime = nativeData.readTime; + this._updateTime = nativeData.updateTime; + } + + get createTime(): string { + // return this._createTime; + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'createTime')); + } + + get exists(): boolean { + return this._data !== undefined; + } + + get id(): string | null { + return this._ref.id; + } + + get readTime(): string { + // return this._readTime; + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'readTime')); + } + + get ref(): DocumentReference { + return this._ref; + } + + get updateTime(): string { + // return this._updateTime; + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'updateTime')); + } + + data(): Object { + return this._data; + } + + get(fieldPath: string): any { + return this._data[fieldPath]; + } +} diff --git a/lib/modules/firestore/GeoPoint.js b/lib/modules/firestore/GeoPoint.js new file mode 100644 index 00000000..d99cb19d --- /dev/null +++ b/lib/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() { + return this._latitude; + } + + get longitude() { + return this._longitude; + } +} diff --git a/lib/modules/firestore/Path.js b/lib/modules/firestore/Path.js new file mode 100644 index 00000000..0c9eb161 --- /dev/null +++ b/lib/modules/firestore/Path.js @@ -0,0 +1,59 @@ +/** + * @flow + * Path representation wrapper + */ + + /** + * @class Path + */ +export default class Path { + _parts: string[]; + + constructor(pathComponents: string[]) { + this._parts = pathComponents; + } + + get id(): string | null { + if (this._parts.length > 0) { + return this._parts[this._parts.length - 1]; + } + return 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 { + if (this._parts.length === 0) { + return null; + } + + return new Path(this._parts.slice(0, this._parts.length - 1)); + } + + /** + * + * @package + */ + static fromName(name): Path { + const parts = name.split('/'); + + if (parts.length === 0) { + return new Path([]); + } + return new Path(parts); + } +} diff --git a/lib/modules/firestore/Query.js b/lib/modules/firestore/Query.js new file mode 100644 index 00000000..c4f271f5 --- /dev/null +++ b/lib/modules/firestore/Query.js @@ -0,0 +1,272 @@ +/** + * @flow + * Query representation wrapper + */ +import DocumentSnapshot from './DocumentSnapshot'; +import Path from './Path'; +import QuerySnapshot from './QuerySnapshot'; +import INTERNALS from '../../internals'; +import { firestoreAutoId } from '../../utils'; + +const DIRECTIONS = { + ASC: 'ASCENDING', + asc: 'ASCENDING', + DESC: 'DESCENDING', + desc: 'DESCENDING', +}; + +const OPERATORS = { + '=': 'EQUAL', + '==': 'EQUAL', + '>': 'GREATER_THAN', + '>=': 'GREATER_THAN_OR_EQUAL', + '<': 'LESS_THAN', + '<=': 'LESS_THAN_OR_EQUAL', +}; + +export type Direction = 'DESC' | 'desc' | 'ASC' | 'asc'; +type FieldFilter = { + fieldPath: string, + operator: string, + value: any, +} +type FieldOrder = { + direction: string, + fieldPath: string, +} +type QueryOptions = { + endAt?: any[], + endBefore?: any[], + limit?: number, + offset?: number, + selectFields?: string[], + startAfter?: any[], + startAt?: any[], +} +export type Operator = '<' | '<=' | '=' | '==' | '>' | '>='; + + /** + * @class Query + */ +export default class Query { + _fieldFilters: FieldFilter[]; + _fieldOrders: FieldOrder[]; + _firestore: Object; + _iid: number; + _queryOptions: QueryOptions; + _referencePath: Path; + + constructor(firestore: Object, 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(): Object { + return this._firestore; + } + + endAt(fieldValues: any): Query { + fieldValues = [].slice.call(arguments); + // TODO: Validation + const options = { + ...this._queryOptions, + endAt: fieldValues, + }; + + return new Query(this.firestore, this._referencePath, this._fieldFilters, + this._fieldOrders, options); + } + + endBefore(fieldValues: any): Query { + fieldValues = [].slice.call(arguments); + // TODO: Validation + const options = { + ...this._queryOptions, + endBefore: fieldValues, + }; + + return new Query(this.firestore, this._referencePath, this._fieldFilters, + this._fieldOrders, options); + } + + get(): Promise { + return this._firestore._native + .collectionGet( + this._referencePath.relativeName, + this._fieldFilters, + this._fieldOrders, + this._queryOptions, + ) + .then(nativeData => new QuerySnapshot(this._firestore, this, nativeData)); + } + + limit(n: number): Query { + // TODO: Validation + // validate.isInteger('n', n); + + const options = { + ...this._queryOptions, + limit: n, + }; + return new Query(this.firestore, this._referencePath, this._fieldFilters, + this._fieldOrders, options); + } + + offset(n: number): Query { + // TODO: Validation + // validate.isInteger('n', n); + + /* const options = { + ...this._queryOptions, + offset: n, + }; + return new Query(this.firestore, this._referencePath, this._fieldFilters, + this._fieldOrders, options); */ + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'offset')); + } + + onSnapshot(onNext: () => any, onError?: () => any): () => void { + // TODO: Validation + const listenerId = firestoreAutoId(); + + const listener = (nativeQuerySnapshot) => { + const querySnapshot = new QuerySnapshot(this._firestore, this, nativeQuerySnapshot); + onNext(querySnapshot); + }; + + // Listen to snapshot events + this._firestore.on( + this._firestore._getAppEventName(`onQuerySnapshot:${listenerId}`), + listener, + ); + + // Listen for snapshot error events + if (onError) { + this._firestore.on( + this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`), + onError, + ); + } + + // Add the native listener + this._firestore._native + .collectionOnSnapshot( + this._referencePath.relativeName, + this._fieldFilters, + this._fieldOrders, + this._queryOptions, + listenerId + ); + + // Return an unsubscribe method + return this._offCollectionSnapshot.bind(this, listenerId, listener); + } + + orderBy(fieldPath: string, directionStr?: Direction = 'asc'): Query { + // TODO: Validation + // validate.isFieldPath('fieldPath', fieldPath); + // validate.isOptionalFieldOrder('directionStr', directionStr); + + if (this._queryOptions.startAt || this._queryOptions.endAt) { + throw new Error('Cannot specify an orderBy() constraint after calling ' + + 'startAt(), startAfter(), endBefore() or endAt().'); + } + + const newOrder = { + direction: DIRECTIONS[directionStr], + fieldPath, + }; + const combinedOrders = this._fieldOrders.concat(newOrder); + return new Query(this.firestore, this._referencePath, this._fieldFilters, + combinedOrders, this._queryOptions); + } + + select(varArgs: string[]): Query { + /* + varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments); + const fieldReferences = []; + + if (varArgs.length === 0) { + fieldReferences.push(DOCUMENT_NAME_FIELD); + } else { + for (let i = 0; i < varArgs.length; ++i) { + // TODO: Validation + // validate.isFieldPath(i, args[i]); + fieldReferences.push(varArgs[i]); + } + } + + const options = { + ...this._queryOptions, + selectFields: fieldReferences, + }; + + return new Query(this.firestore, this._referencePath, this._fieldFilters, + this._fieldOrders, options);*/ + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'select')); + } + + startAfter(fieldValues: any): Query { + fieldValues = [].slice.call(arguments); + // TODO: Validation + const options = { + ...this._queryOptions, + startAfter: fieldValues, + }; + + return new Query(this.firestore, this._referencePath, this._fieldFilters, + this._fieldOrders, options); + } + + startAt(fieldValues: any): Query { + fieldValues = [].slice.call(arguments); + // TODO: Validation + const options = { + ...this._queryOptions, + startAt: fieldValues, + }; + + return new Query(this.firestore, this._referencePath, this._fieldFilters, + this._fieldOrders, options); + } + + stream(): Stream { + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'stream')); + } + + where(fieldPath: string, opStr: Operator, value: any): Query { + // TODO: Validation + // validate.isFieldPath('fieldPath', fieldPath); + // validate.isFieldFilter('fieldFilter', opStr, value); + const newFilter = { + fieldPath, + operator: OPERATORS[opStr], + value, + }; + const combinedFilters = this._fieldFilters.concat(newFilter); + return new Query(this.firestore, this._referencePath, combinedFilters, + this._fieldOrders, this._queryOptions); + } + + /** + * Remove query snapshot listener + * @param listener + */ + _offCollectionSnapshot(listenerId: number, listener: Function) { + this._firestore.log.info('Removing onQuerySnapshot listener'); + this._firestore.removeListener(this._firestore._getAppEventName(`onQuerySnapshot:${listenerId}`), listener); + this._firestore.removeListener(this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`), listener); + this._firestore._native + .collectionOffSnapshot( + this._referencePath.relativeName, + this._fieldFilters, + this._fieldOrders, + this._queryOptions, + listenerId + ); + } +} diff --git a/lib/modules/firestore/QuerySnapshot.js b/lib/modules/firestore/QuerySnapshot.js new file mode 100644 index 00000000..4b3f4b3e --- /dev/null +++ b/lib/modules/firestore/QuerySnapshot.js @@ -0,0 +1,66 @@ +/** + * @flow + * QuerySnapshot representation wrapper + */ +import DocumentChange from './DocumentChange'; +import DocumentSnapshot from './DocumentSnapshot'; +import Query from './Query'; + +import type { DocumentChangeNativeData } from './DocumentChange'; +import type { DocumentSnapshotNativeData } from './DocumentSnapshot'; + +type QuerySnapshotNativeData = { + changes: DocumentChangeNativeData[], + documents: DocumentSnapshotNativeData[], + readTime: string, +} + + /** + * @class QuerySnapshot + */ +export default class QuerySnapshot { + _changes: DocumentChange[]; + _docs: DocumentSnapshot[]; + _query: Query; + _readTime: string; + + constructor(firestore: Object, query: Query, nativeData: QuerySnapshotNativeData) { + this._changes = nativeData.changes.map(change => new DocumentChange(change)); + this._docs = nativeData.documents.map(doc => new DocumentSnapshot(firestore, doc)); + this._query = query; + this._readTime = nativeData.readTime; + } + + get docChanges(): DocumentChange[] { + return this._changes; + } + + get docs(): DocumentSnapshot[] { + return this._docs; + } + + get empty(): boolean { + return this._docs.length === 0; + } + + get query(): Query { + return this._query; + } + + get readTime(): string { + return this._readTime; + } + + get size(): number { + return this._docs.length; + } + + forEach(callback: DocumentSnapshot => any) { + // TODO: Validation + // validate.isFunction('callback', callback); + + for (const doc of this._docs) { + callback(doc); + } + } +} diff --git a/lib/modules/firestore/WriteBatch.js b/lib/modules/firestore/WriteBatch.js new file mode 100644 index 00000000..6fd1fd85 --- /dev/null +++ b/lib/modules/firestore/WriteBatch.js @@ -0,0 +1,96 @@ +/** + * @flow + * WriteBatch representation wrapper + */ +import DocumentReference from './DocumentReference'; + +import type { DeleteOptions, WriteOptions, WriteResult } from './DocumentReference'; + +type CommitOptions = { + transactionId: string, +} + +type DocumentWrite = { + data?: Object, + options?: Object, + path: string, + type: 'DELETE' | 'SET' | 'UPDATE', +} + + /** + * @class WriteBatch + */ +export default class WriteBatch { + _firestore: Object; + _writes: DocumentWrite[]; + + constructor(firestore: Object) { + this._firestore = firestore; + this._writes = []; + } + + get firestore(): Object { + return this._firestore; + } + + get isEmpty(): boolean { + return this._writes.length === 0; + } + + create(docRef: DocumentReference, data: Object): WriteBatch { + // TODO: Validation + // validate.isDocumentReference('docRef', docRef); + // validate.isDocument('data', data); + + return this.set(docRef, data, { exists: false }); + } + + delete(docRef: DocumentReference, deleteOptions?: DeleteOptions): WriteBatch { + // TODO: Validation + // validate.isDocumentReference('docRef', docRef); + // validate.isOptionalPrecondition('deleteOptions', deleteOptions); + this._writes.push({ + options: deleteOptions, + path: docRef.path, + type: 'DELETE', + }); + + return this; + } + + set(docRef: DocumentReference, data: Object, writeOptions?: WriteOptions) { + // TODO: Validation + // validate.isDocumentReference('docRef', docRef); + // validate.isDocument('data', data); + // validate.isOptionalPrecondition('writeOptions', writeOptions); + + this._writes.push({ + data, + options: writeOptions, + path: docRef.path, + type: 'SET', + }); + + return this; + } + + // TODO: Update to new method signature + update(docRef: DocumentReference, data: { [string]: any }): WriteBatch { + // TODO: Validation + // validate.isDocumentReference('docRef', docRef); + // validate.isDocument('data', data, true); + + this._writes.push({ + data, + path: docRef.path, + type: 'UPDATE', + }); + + return this; + } + + commit(commitOptions?: CommitOptions): Promise { + return this._firestore._native + .documentBatch(this._writes, commitOptions); + } +} diff --git a/lib/modules/firestore/index.js b/lib/modules/firestore/index.js new file mode 100644 index 00000000..4f1ae343 --- /dev/null +++ b/lib/modules/firestore/index.js @@ -0,0 +1,177 @@ +/** + * @flow + * Firestore representation wrapper + */ +import { NativeModules } from 'react-native'; + +import ModuleBase from './../../utils/ModuleBase'; +import CollectionReference from './CollectionReference'; +import DocumentReference from './DocumentReference'; +import DocumentSnapshot from './DocumentSnapshot'; +import GeoPoint from './GeoPoint'; +import Path from './Path'; +import WriteBatch from './WriteBatch'; +import INTERNALS from './../../internals'; + +const unquotedIdentifier_ = '(?:[A-Za-z_][A-Za-z_0-9]*)'; +const UNQUOTED_IDENTIFIER_REGEX = new RegExp(`^${unquotedIdentifier_}$`); + +type CollectionSyncEvent = { + appName: string, + querySnapshot?: QuerySnapshot, + error?: Object, + listenerId: string, + path: string, +} + +type DocumentSyncEvent = { + appName: string, + documentSnapshot?: DocumentSnapshot, + error?: Object, + listenerId: string, + path: string, +} + +/** + * @class Firestore + */ +export default class Firestore extends ModuleBase { + static _NAMESPACE = 'firestore'; + static _NATIVE_MODULE = 'RNFirebaseFirestore'; + + _referencePath: Path; + + constructor(firebaseApp: Object, options: Object = {}) { + super(firebaseApp, options, true); + this._referencePath = new Path([]); + + this.addListener( + // sub to internal native event - this fans out to + // public event name: onCollectionSnapshot + this._getAppEventName('firestore_collection_sync_event'), + this._onCollectionSyncEvent.bind(this), + ); + + this.addListener( + // sub to internal native event - this fans out to + // public event name: onDocumentSnapshot + this._getAppEventName('firestore_document_sync_event'), + this._onDocumentSyncEvent.bind(this), + ); + } + + batch(): WriteBatch { + return new WriteBatch(this); + } + + /** + * + * @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); + } + + /** + * + * @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); + } + + getAll(varArgs: DocumentReference[]): Promise { + /*varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments); + + const documents = []; + varArgs.forEach((document) => { + // TODO: Validation + // validate.isDocumentReference(i, varArgs[i]); + documents.push(document.path); + }); + return this._native + .documentGetAll(documents) + .then(results => results.map(result => new DocumentSnapshot(this, result)));*/ + throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD('Query', 'offset')); + } + + getCollections(): Promise { + const rootDocument = new DocumentReference(this, this._referencePath); + return rootDocument.getCollections(); + } + + runTransaction(updateFunction, transactionOptions?: Object): Promise { + + } + + static geoPoint(latitude, longitude): GeoPoint { + return new GeoPoint(latitude, longitude); + } + + static fieldPath(varArgs: string[]): string { + varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments); + + let fieldPath = ''; + + for (let i = 0; i < varArgs.length; ++i) { + let component = varArgs[i]; + // TODO: Validation + // validate.isString(i, component); + if (!UNQUOTED_IDENTIFIER_REGEX.test(component)) { + component = `\`${component.replace(/[`\\]/g, '\\$&')} \``; + } + fieldPath += i !== 0 ? `.${component}` : component; + } + + return fieldPath; + } + + /** + * INTERNALS + */ + + /** + * Internal collection sync listener + * @param event + * @private + */ + _onCollectionSyncEvent(event: CollectionSyncEvent) { + if (event.error) { + this.emit(this._getAppEventName(`onQuerySnapshotError:${event.listenerId}`), event.error); + } else { + this.emit(this._getAppEventName(`onQuerySnapshot:${event.listenerId}`), event.querySnapshot); + } + } + + /** + * Internal document sync listener + * @param event + * @private + */ + _onDocumentSyncEvent(event: DocumentSyncEvent) { + if (event.error) { + this.emit(this._getAppEventName(`onDocumentSnapshotError:${event.listenerId}`), event.error); + } else { + this.emit(this._getAppEventName(`onDocumentSnapshot:${event.listenerId}`), event.documentSnapshot); + } + } +} + +export const statics = { + FieldValue: { + delete: () => NativeModules.RNFirebaseFirestore && NativeModules.RNFirebaseFirestore.deleteFieldValue || {}, + serverTimestamp: () => NativeModules.RNFirebaseFirestore && NativeModules.RNFirebaseFirestore.serverTimestampFieldValue || {} + }, +}; diff --git a/lib/utils/ModuleBase.js b/lib/utils/ModuleBase.js index c3f8df20..628ea417 100644 --- a/lib/utils/ModuleBase.js +++ b/lib/utils/ModuleBase.js @@ -15,6 +15,7 @@ const logs = {}; const MULTI_APP_MODULES = [ 'auth', 'database', + 'firestore', 'storage', ]; @@ -31,6 +32,10 @@ const NATIVE_MODULE_EVENTS = { 'database_transaction_event', // 'database_server_offset', // TODO ], + Firestore: [ + 'firestore_collection_sync_event', + 'firestore_document_sync_event', + ], }; const DEFAULTS = { diff --git a/lib/utils/index.js b/lib/utils/index.js index 4652ef73..891eb804 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -5,10 +5,19 @@ import { Platform } from 'react-native'; // modeled after base64 web-safe chars, but ordered by ASCII const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; +const AUTO_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const hasOwnProperty = Object.hasOwnProperty; // const DEFAULT_CHUNK_SIZE = 50; +// Source: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical +const REGEXP_FIELD_NAME = new RegExp( + `^(?:\\.?((?:(?:[A-Za-z_][A-Za-z_0-9]*)|(?:[A-Za-z_][A-Za-z_0-9]*))+))$` +); +const REGEXP_FIELD_PATH = new RegExp( + `^((?:(?:[A-Za-z_][A-Za-z_0-9]*)|(?:[A-Za-z_][A-Za-z_0-9]*))+)(?:\\.((?:(?:[A-Za-z_][A-Za-z_0-9]*)|(?:[A-Za-z_][A-Za-z_0-9]*))+))*$` +); + /** * Deep get a value from an object. * @website https://github.com/Salakar/deeps @@ -88,6 +97,16 @@ export function isString(value: any): boolean { return typeof value === 'string'; } +/** + * Firestore field name/path validator. + * @param field + * @param paths + * @return {boolean} + */ +export function isValidFirestoreField(field, paths) { + return (paths ? REGEXP_FIELD_PATH : REGEXP_FIELD_NAME).test(field); +} + // platform checks export const isIOS = Platform.OS === 'ios'; export const isAndroid = Platform.OS === 'android'; @@ -374,3 +393,16 @@ export function promiseOrCallback(promise: Promise<*>, optionalCallback?: Functi 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/package-lock.json b/package-lock.json index 693ef25d..3010ee8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2624,9 +2624,9 @@ } }, "flow-bin": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.46.0.tgz", - "integrity": "sha1-Bq1/4Z3dsQQiZEOAZKKjL+4SuHI=", + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.55.0.tgz", + "integrity": "sha1-kIPakye9jKtrQHbWPYXyJHp+rhs=", "dev": true }, "for-in": { diff --git a/package.json b/package.json index 2ec2a431..494db69d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "react-native-firebase", "version": "3.0.0-alpha.5", "author": "Invertase (http://invertase.io)", - "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Auth, Database, Messaging (FCM), Remote Config, Storage, Admob, Analytics, Crash Reporting, and Performance.", + "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Auth, Database, Firestore, Messaging (FCM), Remote Config, Storage, Admob, Analytics, Crash Reporting, and Performance.", "main": "index", "scripts": { "flow": "flow", @@ -51,7 +51,8 @@ "ios", "crash", "firestack", - "performance" + "performance", + "firestore" ], "peerDependencies": { "react": "*", @@ -70,7 +71,7 @@ "eslint-plugin-import": "^2.0.1", "eslint-plugin-jsx-a11y": "^2.2.3", "eslint-plugin-react": "^6.4.1", - "flow-bin": "^0.46.0", + "flow-bin": "^0.55.0", "react": "^15.3.0", "react-dom": "^15.3.0", "react-native": "^0.44.0", diff --git a/tests/android/app/build.gradle b/tests/android/app/build.gradle index 347d6988..3d4eba80 100644 --- a/tests/android/app/build.gradle +++ b/tests/android/app/build.gradle @@ -71,7 +71,7 @@ android { } } -project.ext.firebaseVersion = '11.2.0' +project.ext.firebaseVersion = '11.4.2' dependencies { // compile(project(':react-native-firebase')) { @@ -82,7 +82,6 @@ dependencies { 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" @@ -91,6 +90,7 @@ dependencies { 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.android.support:appcompat-v7:26.0.1" compile "com.facebook.react:react-native:+" // From node_modules } diff --git a/tests/android/app/src/main/assets/fonts/Feather.ttf b/tests/android/app/src/main/assets/fonts/Feather.ttf new file mode 100644 index 00000000..97e3b0fa Binary files /dev/null and b/tests/android/app/src/main/assets/fonts/Feather.ttf differ diff --git a/tests/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf b/tests/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf index fb796951..69404e3d 100644 Binary files a/tests/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf and b/tests/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf differ diff --git a/tests/android/app/src/main/assets/fonts/Octicons.ttf b/tests/android/app/src/main/assets/fonts/Octicons.ttf index 5b1f7d6e..09e2b2d7 100644 Binary files a/tests/android/app/src/main/assets/fonts/Octicons.ttf and b/tests/android/app/src/main/assets/fonts/Octicons.ttf differ diff --git a/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java b/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java index 68af9279..4047c71c 100644 --- a/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java +++ b/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java @@ -10,8 +10,8 @@ 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.firestore.RNFirebaseFirestorePackage; import io.invertase.firebase.messaging.RNFirebaseMessagingPackage; -import io.invertase.firebase.perf.RNFirebasePerformancePackage; import io.invertase.firebase.storage.RNFirebaseStoragePackage; import com.oblador.vectoricons.VectorIconsPackage; import com.facebook.react.ReactNativeHost; @@ -42,8 +42,9 @@ public class MainApplication extends Application implements ReactApplication { new RNFirebaseRemoteConfigPackage(), new RNFirebaseCrashPackage(), new RNFirebaseDatabasePackage(), + new RNFirebaseFirestorePackage(), new RNFirebaseMessagingPackage(), - new RNFirebasePerformancePackage(), + // new RNFirebasePerformancePackage(), new RNFirebaseStoragePackage() ); } diff --git a/tests/android/build.gradle b/tests/android/build.gradle index 59621ffe..32ba1492 100644 --- a/tests/android/build.gradle +++ b/tests/android/build.gradle @@ -6,8 +6,8 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' - classpath 'com.google.gms:google-services:3.1.0' - classpath 'com.google.firebase:firebase-plugins:1.1.0' + classpath 'com.google.gms:google-services:3.1.1' + classpath 'com.google.firebase:firebase-plugins:1.1.1' } } @@ -18,6 +18,7 @@ allprojects { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url "$rootDir/../node_modules/react-native/android" } + mavenLocal() google() } } diff --git a/tests/ios/GoogleService-Info.plist b/tests/ios/GoogleService-Info.plist index 30da4b8d..07e6b464 100644 --- a/tests/ios/GoogleService-Info.plist +++ b/tests/ios/GoogleService-Info.plist @@ -37,4 +37,4 @@ DATABASE_URL https://rnfirebase-b9ad4.firebaseio.com - \ No newline at end of file + diff --git a/tests/ios/Podfile b/tests/ios/Podfile index 6784c57a..d471bdd1 100644 --- a/tests/ios/Podfile +++ b/tests/ios/Podfile @@ -25,6 +25,7 @@ target 'ReactNativeFirebaseDemo' do pod 'Firebase/Crash' pod 'Firebase/Database' pod 'Firebase/DynamicLinks' + pod 'Firebase/Firestore' pod 'Firebase/Messaging' pod 'Firebase/RemoteConfig' pod 'Firebase/Storage' diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index e9f76da8..5ea77fb7 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -1,44 +1,53 @@ PODS: - - Firebase/AdMob (4.2.0): + - BoringSSL (9.0): + - BoringSSL/Implementation (= 9.0) + - BoringSSL/Interface (= 9.0) + - BoringSSL/Implementation (9.0): + - BoringSSL/Interface (= 9.0) + - BoringSSL/Interface (9.0) + - Firebase/AdMob (4.3.0): - Firebase/Core - - Google-Mobile-Ads-SDK (= 7.24.0) - - Firebase/Auth (4.2.0): + - Google-Mobile-Ads-SDK (= 7.24.1) + - Firebase/Auth (4.3.0): - Firebase/Core - - FirebaseAuth (= 4.2.0) - - Firebase/Core (4.2.0): - - FirebaseAnalytics (= 4.0.3) - - FirebaseCore (= 4.0.7) - - Firebase/Crash (4.2.0): + - FirebaseAuth (= 4.2.1) + - Firebase/Core (4.3.0): + - FirebaseAnalytics (= 4.0.4) + - FirebaseCore (= 4.0.8) + - Firebase/Crash (4.3.0): - Firebase/Core - FirebaseCrash (= 2.0.2) - - Firebase/Database (4.2.0): + - Firebase/Database (4.3.0): - Firebase/Core - - FirebaseDatabase (= 4.0.3) - - Firebase/DynamicLinks (4.2.0): + - FirebaseDatabase (= 4.1.0) + - Firebase/DynamicLinks (4.3.0): - Firebase/Core - FirebaseDynamicLinks (= 2.1.0) - - Firebase/Messaging (4.2.0): + - Firebase/Firestore (4.3.0): - Firebase/Core - - FirebaseMessaging (= 2.0.3) - - Firebase/Performance (4.2.0): + - FirebaseFirestore (= 0.8.0) + - Firebase/Messaging (4.3.0): - Firebase/Core - - FirebasePerformance (= 1.0.5) - - Firebase/RemoteConfig (4.2.0): + - FirebaseMessaging (= 2.0.4) + - Firebase/Performance (4.3.0): + - Firebase/Core + - FirebasePerformance (= 1.0.6) + - Firebase/RemoteConfig (4.3.0): - Firebase/Core - FirebaseRemoteConfig (= 2.0.3) - - Firebase/Storage (4.2.0): + - Firebase/Storage (4.3.0): - Firebase/Core - FirebaseStorage (= 2.0.2) - - FirebaseAnalytics (4.0.3): + - FirebaseAnalytics (4.0.4): - FirebaseCore (~> 4.0) - FirebaseInstanceID (~> 2.0) - GoogleToolboxForMac/NSData+zlib (~> 2.1) - nanopb (~> 0.3) - - FirebaseAuth (4.2.0): + - FirebaseAuth (4.2.1): - FirebaseAnalytics (~> 4.0) - GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1) - GTMSessionFetcher/Core (~> 1.1) - - FirebaseCore (4.0.7): + - FirebaseCore (4.0.8): - GoogleToolboxForMac/NSData+zlib (~> 2.1) - nanopb (~> 0.3) - FirebaseCrash (2.0.2): @@ -47,20 +56,27 @@ PODS: - GoogleToolboxForMac/Logger (~> 2.1) - GoogleToolboxForMac/NSData+zlib (~> 2.1) - Protobuf (~> 3.1) - - FirebaseDatabase (4.0.3): + - FirebaseDatabase (4.1.0): - FirebaseAnalytics (~> 4.0) - FirebaseCore (~> 4.0) - leveldb-library (~> 1.18) - FirebaseDynamicLinks (2.1.0): - FirebaseAnalytics (~> 4.0) - - FirebaseInstanceID (2.0.3) - - FirebaseMessaging (2.0.3): + - FirebaseFirestore (0.8.0): + - FirebaseAnalytics (~> 4.0) + - FirebaseAuth (~> 4.2) + - FirebaseCore (~> 4.0) + - gRPC-ProtoRPC (~> 1.0) + - leveldb-library (~> 1.18) + - Protobuf (~> 3.1) + - FirebaseInstanceID (2.0.4) + - FirebaseMessaging (2.0.4): - FirebaseAnalytics (~> 4.0) - FirebaseCore (~> 4.0) - FirebaseInstanceID (~> 2.0) - GoogleToolboxForMac/Logger (~> 2.1) - Protobuf (~> 3.1) - - FirebasePerformance (1.0.5): + - FirebasePerformance (1.0.6): - FirebaseAnalytics (~> 4.0) - FirebaseInstanceID (~> 2.0) - GoogleToolboxForMac/Logger (~> 2.1) @@ -76,7 +92,7 @@ PODS: - FirebaseAnalytics (~> 4.0) - FirebaseCore (~> 4.0) - GTMSessionFetcher/Core (~> 1.1) - - Google-Mobile-Ads-SDK (7.24.0) + - Google-Mobile-Ads-SDK (7.24.1) - GoogleToolboxForMac/DebugUtils (2.1.1): - GoogleToolboxForMac/Defines (= 2.1.1) - GoogleToolboxForMac/Defines (2.1.1) @@ -89,6 +105,22 @@ PODS: - GoogleToolboxForMac/Defines (= 2.1.1) - GoogleToolboxForMac/NSString+URLArguments (= 2.1.1) - GoogleToolboxForMac/NSString+URLArguments (2.1.1) + - gRPC (1.6.0): + - gRPC-Core (= 1.6.0) + - gRPC-RxLibrary (= 1.6.0) + - gRPC-Core (1.6.0): + - gRPC-Core/Implementation (= 1.6.0) + - gRPC-Core/Interface (= 1.6.0) + - gRPC-Core/Implementation (1.6.0): + - BoringSSL (~> 9.0) + - gRPC-Core/Interface (= 1.6.0) + - nanopb (~> 0.3) + - gRPC-Core/Interface (1.6.0) + - gRPC-ProtoRPC (1.6.0): + - gRPC (= 1.6.0) + - gRPC-RxLibrary (= 1.6.0) + - Protobuf (~> 3.0) + - gRPC-RxLibrary (1.6.0) - GTMSessionFetcher/Core (1.1.12) - leveldb-library (1.18.3) - nanopb (0.3.8): @@ -106,9 +138,6 @@ PODS: - yoga (= 0.49.0-rc.6.React) - React/cxxreact_legacy (0.49.0-rc.6): - React/jschelpers_legacy - - React/DevSupport (0.49.0-rc.6): - - React/Core - - React/RCTWebSocket - React/fishhook (0.49.0-rc.6) - React/jschelpers_legacy (0.49.0-rc.6) - React/RCTBlob (0.49.0-rc.6): @@ -132,13 +161,13 @@ DEPENDENCIES: - Firebase/Crash - Firebase/Database - Firebase/DynamicLinks + - Firebase/Firestore - Firebase/Messaging - Firebase/Performance - Firebase/RemoteConfig - Firebase/Storage - React/BatchedBridge (from `../node_modules/react-native`) - React/Core (from `../node_modules/react-native`) - - React/DevSupport (from `../node_modules/react-native`) - React/RCTNetwork (from `../node_modules/react-native`) - React/RCTText (from `../node_modules/react-native`) - React/RCTWebSocket (from `../node_modules/react-native`) @@ -154,28 +183,34 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Firebase: 9548cae14d69718add12d75a5b312893f7ef89c7 - FirebaseAnalytics: 76f754d37ca5b04f36856729b6af3ca0152d1069 - FirebaseAuth: 22f8a5170f31d1f111141950590f071f35df3229 - FirebaseCore: 9a6cc1e3eaf75905390f9220596ad4fd8f92faee + BoringSSL: 19083b821ef3ae0f758fae15482e183003b1e265 + Firebase: 83283761a1ef6dc9846e03d08059f51421afbd65 + FirebaseAnalytics: 722b53c7b32bfc7806b06e0093a2f5180d4f2c5a + FirebaseAuth: d7f047fbeab98062b98ea933b8d934e0fb1190e2 + FirebaseCore: 69b1a5ac5f857ba6d5fd9d5fe794f4786dd5e579 FirebaseCrash: cded0fc566c03651aea606a101bc156085f333ca - FirebaseDatabase: '03940adcac54ce30db06f1fc2136f8581734ce2c' + FirebaseDatabase: 607284a103e961d7f5863ee603cab5e85f443bd6 FirebaseDynamicLinks: ed4cb6c42705aaa5e841ed2d76e3a4bddbec10c1 - FirebaseInstanceID: a4fc702b5a026f7322964376047f1a3f1f7cc6ff - FirebaseMessaging: eaf1bfff0193170c04ea3ba3bfe983f68f893118 - FirebasePerformance: d0dc2a1d3dc1bca249d154cb40ee4eae25b455ad + FirebaseFirestore: 8e2fd99a621ae6fc6acfac3bdea824fe9d9c128d + FirebaseInstanceID: 70c2b877e9338971b2429ea5a4293df6961aa44e + FirebaseMessaging: 3dd86bfda2acb680b05c97f3f8ac566e9bb87b2a + FirebasePerformance: fa032c27e229eb8c1a8638918793fe2e47465205 FirebaseRemoteConfig: 1c982f73af48ec048c8fa8621d5178cfdffac9aa FirebaseStorage: 0cca42d9b889a0227c3a50121f45a4469fc9eb27 - Google-Mobile-Ads-SDK: f405b7acb098fe89e6fcd05fdbf400c1a5bcb935 + Google-Mobile-Ads-SDK: ed8004a7265b424568dc84f3d2bbe3ea3fff958f GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0 + gRPC: '07788969b862af21491908f82b83d17ac08c94cd' + gRPC-Core: f707ade59c559fe718e27713189607d03b15f571 + gRPC-ProtoRPC: de7505e493a9d1b6b96c8ea8f976c73100fdf53f + gRPC-RxLibrary: 17b9699beb0a838b95b57832244f9ead18e66777 GTMSessionFetcher: ebaa1f79a5366922c1735f1566901f50beba23b7 leveldb-library: 10fb39c39e243db4af1828441162405bbcec1404 nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8 React: e6ef6a41ec6dd1b7941417d60ca582bf5e9c739d - RNFirebase: 60be8c01b94551a12e7be5431189e8ee8cefcdd3 + RNFirebase: 6508ffd6cab78cc3a84305708a250d7d4b74f2dc yoga: f9485d2ebf0ca773db2d727ea71b1aa8c9f3e075 -PODFILE CHECKSUM: de6bb6c2ad60b7ad1f6c01aa01268ec04eb389a9 +PODFILE CHECKSUM: b5674be55653f5dda937c8b794d0479900643d45 COCOAPODS: 1.2.1 diff --git a/tests/ios/ReactNativeFirebaseDemo.xcodeproj/project.pbxproj b/tests/ios/ReactNativeFirebaseDemo.xcodeproj/project.pbxproj index 5db1f6f4..24ef39de 100644 --- a/tests/ios/ReactNativeFirebaseDemo.xcodeproj/project.pbxproj +++ b/tests/ios/ReactNativeFirebaseDemo.xcodeproj/project.pbxproj @@ -5,7 +5,6 @@ }; 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 */; }; @@ -37,6 +36,7 @@ E51DA6317685417F97A59475 /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C76E33ACF004369AEB318B1 /* libRNVectorIcons.a */; }; EA30CACE4CB84AC1BAE59432 /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4935BEDE99B9436581953E77 /* Entypo.ttf */; }; F57EC9E3C5414A99821B73F4 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DB5BB32AF70B41678974C6B4 /* EvilIcons.ttf */; }; + 58EAF3FA628941C98A02BAE4 /* Feather.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 528FC2DCD44F4567A3FE9F59 /* Feather.ttf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -322,6 +322,7 @@ CC24EB30F0484352BD65FFC1 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; }; DB5BB32AF70B41678974C6B4 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = ""; }; FD3DFC8253C74B6298AFD3B7 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = ""; }; + 528FC2DCD44F4567A3FE9F59 /* Feather.ttf */ = {isa = PBXFileReference; name = "Feather.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -597,6 +598,7 @@ CC24EB30F0484352BD65FFC1 /* Octicons.ttf */, FD3DFC8253C74B6298AFD3B7 /* SimpleLineIcons.ttf */, 182271FFECD74C3B92960E1D /* Zocial.ttf */, + 528FC2DCD44F4567A3FE9F59 /* Feather.ttf */, ); name = Resources; sourceTree = ""; @@ -990,6 +992,7 @@ 885057F5D1FA461AAAE0B487 /* Octicons.ttf in Resources */, 42A0E8F428A74B23B4C7D95A /* SimpleLineIcons.ttf in Resources */, D46EBD0604CE40EFB18F8A35 /* Zocial.ttf in Resources */, + 58EAF3FA628941C98A02BAE4 /* Feather.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/tests/ios/ReactNativeFirebaseDemo/Info.plist b/tests/ios/ReactNativeFirebaseDemo/Info.plist index f764dd7f..0ebc846d 100644 --- a/tests/ios/ReactNativeFirebaseDemo/Info.plist +++ b/tests/ios/ReactNativeFirebaseDemo/Info.plist @@ -34,7 +34,7 @@ NSLocationWhenInUseUsageDescription - + UIAppFonts Entypo.ttf @@ -47,6 +47,7 @@ Octicons.ttf SimpleLineIcons.ttf Zocial.ttf + Feather.ttf UILaunchStoryboardName LaunchScreen diff --git a/tests/package-lock.json b/tests/package-lock.json index e5eea2a0..4ea9ef10 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -15,6 +15,20 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "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.17", + "negotiator": "0.5.3" + } + }, "acorn": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", @@ -59,7 +73,6 @@ "version": "5.2.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", - "dev": true, "requires": { "co": "4.6.0", "fast-deep-equal": "1.0.0", @@ -77,7 +90,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, "requires": { "kind-of": "3.2.2", "longest": "1.0.1", @@ -95,6 +107,16 @@ "resolved": "https://registry.npmjs.org/andlog/-/andlog-1.0.0.tgz", "integrity": "sha1-JbOQR6ycHl9rLI5M2157+eZeU0s=" }, + "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-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -109,7 +131,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, "requires": { "micromatch": "2.3.11", "normalize-path": "2.1.1" @@ -124,6 +145,15 @@ "default-require-extensions": "1.0.0" } }, + "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.3" + } + }, "argparse": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", @@ -146,7 +176,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, "requires": { "arr-flatten": "1.1.0" } @@ -154,8 +183,12 @@ "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==", - "dev": true + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "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-equal": { "version": "1.0.0", @@ -163,6 +196,21 @@ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, + "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", @@ -175,14 +223,12 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "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=", - "dev": true + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" }, "array.prototype.find": { "version": "2.0.4", @@ -200,6 +246,11 @@ "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", @@ -208,14 +259,12 @@ "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "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=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "ast-types-flow": { "version": "0.0.7", @@ -227,7 +276,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", - "dev": true, "requires": { "lodash": "4.17.4" } @@ -242,20 +290,17 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "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=", - "dev": true + "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=", - "dev": true + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, "babel-cli": { "version": "6.26.0", @@ -381,7 +426,6 @@ "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=", - "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0", @@ -392,7 +436,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -548,6 +591,29 @@ } } }, + "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" + }, + "dependencies": { + "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.1", + "regenerator-runtime": "0.11.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", @@ -641,6 +707,25 @@ } } }, + "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" + }, + "dependencies": { + "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.1", + "regenerator-runtime": "0.11.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", @@ -685,7 +770,6 @@ "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.4" } @@ -698,38 +782,53 @@ "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=", - "dev": true + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=" }, "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=", - "dev": true + "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=", - "dev": true + "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=", - "dev": true + "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=", - "dev": true + "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" + }, + "dependencies": { + "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.1", + "regenerator-runtime": "0.11.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=", - "dev": true, "requires": { "babel-helper-function-name": "6.24.1", "babel-plugin-syntax-class-properties": "6.13.0", @@ -741,7 +840,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1180,7 +1278,6 @@ "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=", - "dev": true, "requires": { "babel-plugin-syntax-flow": "6.18.0", "babel-runtime": "6.26.0" @@ -1190,7 +1287,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1202,7 +1298,6 @@ "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=", - "dev": true, "requires": { "babel-runtime": "6.26.0" }, @@ -1211,7 +1306,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1223,7 +1317,6 @@ "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=", - "dev": true, "requires": { "babel-plugin-syntax-object-rest-spread": "6.13.0", "babel-runtime": "6.26.0" @@ -1233,7 +1326,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1245,7 +1337,6 @@ "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=", - "dev": true, "requires": { "babel-runtime": "6.26.0" }, @@ -1254,7 +1345,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1266,7 +1356,6 @@ "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=", - "dev": true, "requires": { "babel-helper-builder-react-jsx": "6.26.0", "babel-plugin-syntax-jsx": "6.18.0", @@ -1277,7 +1366,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1289,7 +1377,6 @@ "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=", - "dev": true, "requires": { "babel-plugin-syntax-jsx": "6.18.0", "babel-runtime": "6.26.0" @@ -1299,7 +1386,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1419,6 +1505,22 @@ "modify-babel-preset": "2.0.2" } }, + "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.7.7", + "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.4.1" + } + }, "babel-preset-es3": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-es3/-/babel-preset-es3-1.0.1.tgz", @@ -1428,6 +1530,63 @@ "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", + "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" + }, + "dependencies": { + "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-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.1", + "regenerator-runtime": "0.11.0" + } + } + } + }, "babel-preset-jest": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-19.0.0.tgz", @@ -1596,16 +1755,50 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + }, + "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=", - "dev": true, "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.25", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.25.tgz", + "integrity": "sha1-HeRan1dUKsIBIcaC+NZCIgo06CM=" + }, "binary-extensions": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", @@ -1618,11 +1811,47 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" }, + "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.15" + }, + "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=", - "dev": true, "requires": { "hoek": "4.2.0" } @@ -1635,6 +1864,22 @@ "andlog": "1.0.0" } }, + "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.25" + } + }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", @@ -1648,7 +1893,6 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, "requires": { "expand-range": "1.8.2", "preserve": "0.2.0", @@ -1706,6 +1950,11 @@ "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=" + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -1724,22 +1973,17 @@ "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, "requires": { "align-text": "0.1.4", "lazy-cache": "1.0.4" @@ -1792,18 +2036,23 @@ "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=" }, + "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=", - "dev": true + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, "requires": { "center-align": "0.1.3", "right-align": "0.1.3", @@ -1813,17 +2062,24 @@ "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" } } }, + "clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=" + }, + "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=", - "dev": true + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "code-point-at": { "version": "1.1.0", @@ -1834,7 +2090,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -1842,8 +2097,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.1.2", @@ -1855,7 +2109,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "dev": true, "requires": { "delayed-stream": "1.0.0" } @@ -1865,6 +2118,42 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" }, + "compressible": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.11.tgz", + "integrity": "sha1-FnGKdd4oPtjmBAQWJaIGRYZ5fYo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "compression": { + "version": "1.5.2", + "resolved": "https://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.11", + "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", @@ -1874,7 +2163,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, "requires": { "inherits": "2.0.3", "readable-stream": "2.3.3", @@ -1890,12 +2178,96 @@ "proto-list": "1.2.4" } }, + "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.15", + "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=" + } + } + }, "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==" + }, "content-type-parser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.1.tgz", @@ -1907,6 +2279,25 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=" }, + "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=" + }, "core-js": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", @@ -1915,14 +2306,48 @@ "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=", - "dev": true + "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.2", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz", + "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=", + "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.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + } + } }, "cryptiles": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, "requires": { "boom": "5.2.0" }, @@ -1931,13 +2356,22 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, "requires": { "hoek": "4.2.0" } } } }, + "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" + } + }, "cssmin": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/cssmin/-/cssmin-0.3.2.tgz", @@ -1958,6 +2392,17 @@ "cssom": "0.3.2" } }, + "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" + } + }, "cuid": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/cuid/-/cuid-1.3.8.tgz", @@ -1999,11 +2444,15 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "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", @@ -2070,8 +2519,27 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "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", @@ -2099,14 +2567,43 @@ "dom-walk": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", - "dev": true + "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" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "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" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -2124,6 +2621,11 @@ "sigmund": "1.0.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", @@ -2138,11 +2640,27 @@ "iconv-lite": "0.4.19" } }, + "envinfo": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-3.4.1.tgz", + "integrity": "sha1-jIDp8u7CzU4q2yxdASfOB6Kqoq4=", + "requires": { + "minimist": "1.2.0", + "os-name": "2.0.1", + "which": "1.3.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, "errno": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", - "dev": true, "requires": { "prr": "0.0.0" } @@ -2155,6 +2673,31 @@ "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.4", + "escape-html": "1.0.3" + }, + "dependencies": { + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "2.1.17", + "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.9.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz", @@ -2249,6 +2792,11 @@ "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", @@ -2679,6 +3227,11 @@ "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", @@ -2689,11 +3242,15 @@ "es5-ext": "0.10.30" } }, + "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=" + }, "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==", - "dev": true, "requires": { "merge": "1.2.0" } @@ -2743,7 +3300,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, "requires": { "is-posix-bracket": "0.1.1" } @@ -2752,22 +3308,68 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, "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=", - "dev": true + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "external-editor": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz", + "integrity": "sha512-Msjo64WT5W+NhOpQXh0nOHm+n0RfU1QUwDnKYvJ8dEJ8zlwLrqXNTv5mSUTJpepf41PDJGyhueTw2vNZW+Fr/w==", + "requires": { + "iconv-lite": "0.4.19", + "jschardet": "1.5.1", + "tmp": "0.0.33" + } }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, "requires": { "is-extglob": "1.0.0" } @@ -2775,19 +3377,26 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" }, + "fancy-log": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", + "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", + "requires": { + "chalk": "1.1.3", + "time-stamp": "1.1.0" + } + }, "fast-deep-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" }, "fast-levenshtein": { "version": "2.0.6", @@ -2799,7 +3408,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", - "dev": true, "requires": { "bser": "2.0.0" }, @@ -2808,7 +3416,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", - "dev": true, "requires": { "node-int64": "0.4.0" } @@ -2836,6 +3443,29 @@ } } }, + "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.1", + "cross-spawn": "5.1.0", + "gulp-util": "3.0.8", + "object-assign": "4.1.1", + "semver": "5.4.1", + "through2": "2.0.3" + } + }, + "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", @@ -2849,8 +3479,7 @@ "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" }, "fileset": { "version": "2.0.3", @@ -2866,7 +3495,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, "requires": { "is-number": "2.1.0", "isobject": "2.1.0", @@ -2875,11 +3503,41 @@ "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": "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" @@ -3056,14 +3714,12 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "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=", - "dev": true, "requires": { "for-in": "1.0.2" } @@ -3077,14 +3733,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "dev": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.5", @@ -3095,7 +3749,6 @@ "version": "2.1.17", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "dev": true, "requires": { "mime-db": "1.30.0" } @@ -3110,6 +3763,21 @@ "samsam": "1.2.1" } }, + "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.0.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz", @@ -3119,14 +3787,12 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", - "dev": true, "optional": true, "requires": { "nan": "2.7.0", @@ -3136,13 +3802,11 @@ "abbrev": { "version": "1.1.0", "bundled": true, - "dev": true, "optional": true }, "ajv": { "version": "4.11.8", "bundled": true, - "dev": true, "optional": true, "requires": { "co": "4.6.0", @@ -3151,19 +3815,16 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "dev": true + "bundled": true }, "aproba": { "version": "1.1.1", "bundled": true, - "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", "bundled": true, - "dev": true, "optional": true, "requires": { "delegates": "1.0.0", @@ -3173,42 +3834,35 @@ "asn1": { "version": "0.2.3", "bundled": true, - "dev": true, "optional": true }, "assert-plus": { "version": "0.2.0", "bundled": true, - "dev": true, "optional": true }, "asynckit": { "version": "0.4.0", "bundled": true, - "dev": true, "optional": true }, "aws-sign2": { "version": "0.6.0", "bundled": true, - "dev": true, "optional": true }, "aws4": { "version": "1.6.0", "bundled": true, - "dev": true, "optional": true }, "balanced-match": { "version": "0.4.2", - "bundled": true, - "dev": true + "bundled": true }, "bcrypt-pbkdf": { "version": "1.0.1", "bundled": true, - "dev": true, "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -3217,7 +3871,6 @@ "block-stream": { "version": "0.0.9", "bundled": true, - "dev": true, "requires": { "inherits": "2.0.3" } @@ -3225,7 +3878,6 @@ "boom": { "version": "2.10.1", "bundled": true, - "dev": true, "requires": { "hoek": "2.16.3" } @@ -3233,7 +3885,6 @@ "brace-expansion": { "version": "1.1.7", "bundled": true, - "dev": true, "requires": { "balanced-match": "0.4.2", "concat-map": "0.0.1" @@ -3241,53 +3892,44 @@ }, "buffer-shims": { "version": "1.0.0", - "bundled": true, - "dev": true + "bundled": true }, "caseless": { "version": "0.12.0", "bundled": true, - "dev": true, "optional": true }, "co": { "version": "4.6.0", "bundled": true, - "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "dev": true + "bundled": true }, "combined-stream": { "version": "1.0.5", "bundled": true, - "dev": true, "requires": { "delayed-stream": "1.0.0" } }, "concat-map": { "version": "0.0.1", - "bundled": true, - "dev": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "dev": true + "bundled": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, - "dev": true + "bundled": true }, "cryptiles": { "version": "2.0.5", "bundled": true, - "dev": true, "optional": true, "requires": { "boom": "2.10.1" @@ -3296,7 +3938,6 @@ "dashdash": { "version": "1.14.1", "bundled": true, - "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0" @@ -3305,7 +3946,6 @@ "assert-plus": { "version": "1.0.0", "bundled": true, - "dev": true, "optional": true } } @@ -3313,7 +3953,6 @@ "debug": { "version": "2.6.8", "bundled": true, - "dev": true, "optional": true, "requires": { "ms": "2.0.0" @@ -3322,24 +3961,20 @@ "deep-extend": { "version": "0.4.2", "bundled": true, - "dev": true, "optional": true }, "delayed-stream": { "version": "1.0.0", - "bundled": true, - "dev": true + "bundled": true }, "delegates": { "version": "1.0.0", "bundled": true, - "dev": true, "optional": true }, "ecc-jsbn": { "version": "0.1.1", "bundled": true, - "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -3348,24 +3983,20 @@ "extend": { "version": "3.0.1", "bundled": true, - "dev": true, "optional": true }, "extsprintf": { "version": "1.0.2", - "bundled": true, - "dev": true + "bundled": true }, "forever-agent": { "version": "0.6.1", "bundled": true, - "dev": true, "optional": true }, "form-data": { "version": "2.1.4", "bundled": true, - "dev": true, "optional": true, "requires": { "asynckit": "0.4.0", @@ -3375,13 +4006,11 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, - "dev": true + "bundled": true }, "fstream": { "version": "1.0.11", "bundled": true, - "dev": true, "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", @@ -3392,7 +4021,6 @@ "fstream-ignore": { "version": "1.0.5", "bundled": true, - "dev": true, "optional": true, "requires": { "fstream": "1.0.11", @@ -3403,7 +4031,6 @@ "gauge": { "version": "2.7.4", "bundled": true, - "dev": true, "optional": true, "requires": { "aproba": "1.1.1", @@ -3419,7 +4046,6 @@ "getpass": { "version": "0.1.7", "bundled": true, - "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0" @@ -3428,7 +4054,6 @@ "assert-plus": { "version": "1.0.0", "bundled": true, - "dev": true, "optional": true } } @@ -3436,7 +4061,6 @@ "glob": { "version": "7.1.2", "bundled": true, - "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -3448,19 +4072,16 @@ }, "graceful-fs": { "version": "4.1.11", - "bundled": true, - "dev": true + "bundled": true }, "har-schema": { "version": "1.0.5", "bundled": true, - "dev": true, "optional": true }, "har-validator": { "version": "4.2.1", "bundled": true, - "dev": true, "optional": true, "requires": { "ajv": "4.11.8", @@ -3470,13 +4091,11 @@ "has-unicode": { "version": "2.0.1", "bundled": true, - "dev": true, "optional": true }, "hawk": { "version": "3.1.3", "bundled": true, - "dev": true, "optional": true, "requires": { "boom": "2.10.1", @@ -3487,13 +4106,11 @@ }, "hoek": { "version": "2.16.3", - "bundled": true, - "dev": true + "bundled": true }, "http-signature": { "version": "1.1.1", "bundled": true, - "dev": true, "optional": true, "requires": { "assert-plus": "0.2.0", @@ -3504,7 +4121,6 @@ "inflight": { "version": "1.0.6", "bundled": true, - "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -3512,19 +4128,16 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "dev": true + "bundled": true }, "ini": { "version": "1.3.4", "bundled": true, - "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -3532,24 +4145,20 @@ "is-typedarray": { "version": "1.0.0", "bundled": true, - "dev": true, "optional": true }, "isarray": { "version": "1.0.0", - "bundled": true, - "dev": true + "bundled": true }, "isstream": { "version": "0.1.2", "bundled": true, - "dev": true, "optional": true }, "jodid25519": { "version": "1.0.2", "bundled": true, - "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -3558,19 +4167,16 @@ "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true, "optional": true }, "json-schema": { "version": "0.2.3", "bundled": true, - "dev": true, "optional": true }, "json-stable-stringify": { "version": "1.0.1", "bundled": true, - "dev": true, "optional": true, "requires": { "jsonify": "0.0.0" @@ -3579,19 +4185,16 @@ "json-stringify-safe": { "version": "5.0.1", "bundled": true, - "dev": true, "optional": true }, "jsonify": { "version": "0.0.0", "bundled": true, - "dev": true, "optional": true }, "jsprim": { "version": "1.4.0", "bundled": true, - "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0", @@ -3603,20 +4206,17 @@ "assert-plus": { "version": "1.0.0", "bundled": true, - "dev": true, "optional": true } } }, "mime-db": { "version": "1.27.0", - "bundled": true, - "dev": true + "bundled": true }, "mime-types": { "version": "2.1.15", "bundled": true, - "dev": true, "requires": { "mime-db": "1.27.0" } @@ -3624,20 +4224,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "dev": true, "requires": { "brace-expansion": "1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "dev": true + "bundled": true }, "mkdirp": { "version": "0.5.1", "bundled": true, - "dev": true, "requires": { "minimist": "0.0.8" } @@ -3645,13 +4242,11 @@ "ms": { "version": "2.0.0", "bundled": true, - "dev": true, "optional": true }, "node-pre-gyp": { "version": "0.6.36", "bundled": true, - "dev": true, "optional": true, "requires": { "mkdirp": "0.5.1", @@ -3668,7 +4263,6 @@ "nopt": { "version": "4.0.1", "bundled": true, - "dev": true, "optional": true, "requires": { "abbrev": "1.1.0", @@ -3678,7 +4272,6 @@ "npmlog": { "version": "4.1.0", "bundled": true, - "dev": true, "optional": true, "requires": { "are-we-there-yet": "1.1.4", @@ -3689,25 +4282,21 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "dev": true + "bundled": true }, "oauth-sign": { "version": "0.8.2", "bundled": true, - "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", "bundled": true, - "dev": true, "optional": true }, "once": { "version": "1.4.0", "bundled": true, - "dev": true, "requires": { "wrappy": "1.0.2" } @@ -3715,19 +4304,16 @@ "os-homedir": { "version": "1.0.2", "bundled": true, - "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", "bundled": true, - "dev": true, "optional": true }, "osenv": { "version": "0.1.4", "bundled": true, - "dev": true, "optional": true, "requires": { "os-homedir": "1.0.2", @@ -3736,36 +4322,30 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, - "dev": true + "bundled": true }, "performance-now": { "version": "0.2.0", "bundled": true, - "dev": true, "optional": true }, "process-nextick-args": { "version": "1.0.7", - "bundled": true, - "dev": true + "bundled": true }, "punycode": { "version": "1.4.1", "bundled": true, - "dev": true, "optional": true }, "qs": { "version": "6.4.0", "bundled": true, - "dev": true, "optional": true }, "rc": { "version": "1.2.1", "bundled": true, - "dev": true, "optional": true, "requires": { "deep-extend": "0.4.2", @@ -3777,7 +4357,6 @@ "minimist": { "version": "1.2.0", "bundled": true, - "dev": true, "optional": true } } @@ -3785,7 +4364,6 @@ "readable-stream": { "version": "2.2.9", "bundled": true, - "dev": true, "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", @@ -3799,7 +4377,6 @@ "request": { "version": "2.81.0", "bundled": true, - "dev": true, "optional": true, "requires": { "aws-sign2": "0.6.0", @@ -3829,38 +4406,32 @@ "rimraf": { "version": "2.6.1", "bundled": true, - "dev": true, "requires": { "glob": "7.1.2" } }, "safe-buffer": { "version": "5.0.1", - "bundled": true, - "dev": true + "bundled": true }, "semver": { "version": "5.3.0", "bundled": true, - "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", "bundled": true, - "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", "bundled": true, - "dev": true, "optional": true }, "sntp": { "version": "1.0.9", "bundled": true, - "dev": true, "optional": true, "requires": { "hoek": "2.16.3" @@ -3869,7 +4440,6 @@ "sshpk": { "version": "1.13.0", "bundled": true, - "dev": true, "optional": true, "requires": { "asn1": "0.2.3", @@ -3886,7 +4456,6 @@ "assert-plus": { "version": "1.0.0", "bundled": true, - "dev": true, "optional": true } } @@ -3894,7 +4463,6 @@ "string_decoder": { "version": "1.0.1", "bundled": true, - "dev": true, "requires": { "safe-buffer": "5.0.1" } @@ -3902,7 +4470,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -3912,13 +4479,11 @@ "stringstream": { "version": "0.0.5", "bundled": true, - "dev": true, "optional": true }, "strip-ansi": { "version": "3.0.1", "bundled": true, - "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -3926,13 +4491,11 @@ "strip-json-comments": { "version": "2.0.1", "bundled": true, - "dev": true, "optional": true }, "tar": { "version": "2.2.1", "bundled": true, - "dev": true, "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", @@ -3942,7 +4505,6 @@ "tar-pack": { "version": "3.4.0", "bundled": true, - "dev": true, "optional": true, "requires": { "debug": "2.6.8", @@ -3958,7 +4520,6 @@ "tough-cookie": { "version": "2.3.2", "bundled": true, - "dev": true, "optional": true, "requires": { "punycode": "1.4.1" @@ -3967,7 +4528,6 @@ "tunnel-agent": { "version": "0.6.0", "bundled": true, - "dev": true, "optional": true, "requires": { "safe-buffer": "5.0.1" @@ -3976,30 +4536,25 @@ "tweetnacl": { "version": "0.14.5", "bundled": true, - "dev": true, "optional": true }, "uid-number": { "version": "0.0.6", "bundled": true, - "dev": true, "optional": true }, "util-deprecate": { "version": "1.0.2", - "bundled": true, - "dev": true + "bundled": true }, "uuid": { "version": "3.0.1", "bundled": true, - "dev": true, "optional": true }, "verror": { "version": "1.3.6", "bundled": true, - "dev": true, "optional": true, "requires": { "extsprintf": "1.0.2" @@ -4008,7 +4563,6 @@ "wide-align": { "version": "1.1.2", "bundled": true, - "dev": true, "optional": true, "requires": { "string-width": "1.0.2" @@ -4016,8 +4570,7 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "dev": true + "bundled": true } } }, @@ -4027,6 +4580,18 @@ "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", @@ -4056,7 +4621,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "1.0.0" } @@ -4065,7 +4629,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -4079,7 +4642,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, "requires": { "glob-parent": "2.0.0", "is-glob": "2.0.1" @@ -4089,7 +4651,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, "requires": { "is-glob": "2.0.1" } @@ -4098,7 +4659,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", - "dev": true, "requires": { "min-document": "2.19.0", "process": "0.5.2" @@ -4123,6 +4683,14 @@ "pinkie-promise": "2.0.1" } }, + "glogg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", + "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "requires": { + "sparkles": "1.0.0" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -4134,6 +4702,51 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, + "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.0", + "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": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "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.0" + } + }, "handlebars": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", @@ -4166,14 +4779,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "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=", - "dev": true, "requires": { "ajv": "5.2.3", "har-schema": "2.0.0" @@ -4196,11 +4807,28 @@ "ansi-regex": "2.1.1" } }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, + "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-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "hawk": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", @@ -4211,8 +4839,7 @@ "hoek": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", - "dev": true + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" }, "hoist-non-react-statics": { "version": "2.3.1", @@ -4242,11 +4869,19 @@ "whatwg-encoding": "1.0.1" } }, + "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.3.1" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", @@ -4264,17 +4899,20 @@ "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==", "dev": true }, + "image-size": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.1.tgz", + "integrity": "sha512-lHMlI2MykfeHAQdtydQh4fTcBQVf4zLTA91w1euBe9rbmAfJ/iyzMh8H3KD9u1RldlHaMS3tmMV5TEe9BkmW9g==" + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -4283,14 +4921,75 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" }, + "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.1.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.0.5", + "figures": "2.0.0", + "lodash": "4.17.4", + "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.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.4.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": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "interpret": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz", @@ -4328,8 +5027,7 @@ "is-buffer": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" }, "is-builtin-module": { "version": "1.0.0", @@ -4363,14 +5061,12 @@ "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true + "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=", - "dev": true, "requires": { "is-primitive": "2.0.0" } @@ -4378,14 +5074,12 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "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=", - "dev": true + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" }, "is-finite": { "version": "1.0.2", @@ -4404,7 +5098,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, "requires": { "is-extglob": "1.0.0" } @@ -4425,7 +5118,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, "requires": { "kind-of": "3.2.2" } @@ -4457,14 +5149,17 @@ "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=", - "dev": true + "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=", - "dev": true + "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", @@ -4504,20 +5199,17 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "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 + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -4528,7 +5220,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -4857,6 +5548,11 @@ } } }, + "jest-docblock": { + "version": "20.1.0-echo.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-20.1.0-echo.1.tgz", + "integrity": "sha512-zJPqHgxSlu5AYjyFLoXzwSqTZGeRAbtW9lTrWfjfDWyQCQjPlt9j9s7t3UBoDwUocM7qVNdlrcXPPtBkQR1dJw==" + }, "jest-environment-jsdom": { "version": "19.0.2", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-19.0.2.tgz", @@ -4884,6 +5580,19 @@ "integrity": "sha1-zKLlh6EeyS4kz+qz+KlNZX8/zrg=", "dev": true }, + "jest-haste-map": { + "version": "20.1.0-echo.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-20.1.0-echo.1.tgz", + "integrity": "sha512-5hhKBYWNHIgVnGcOtzo0wsjHuMqZ+9RUmKou4py8yhjYmtbwVVVFcuvTBClwXt/NdrQ8JrbCvtHq5b4IWS7ieg==", + "requires": { + "fb-watchman": "2.0.0", + "graceful-fs": "4.1.11", + "jest-docblock": "20.1.0-echo.1", + "micromatch": "2.3.11", + "sane": "2.2.0", + "worker-farm": "1.5.0" + } + }, "jest-jasmine2": { "version": "19.0.2", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-19.0.2.tgz", @@ -5242,9 +5951,13 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true }, + "jschardet": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", + "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==" + }, "jsdom": { "version": "9.12.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.12.0.tgz", @@ -5299,20 +6012,17 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "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=", - "dev": true + "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=", - "dev": true, "requires": { "jsonify": "0.0.0" } @@ -5327,11 +6037,18 @@ "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=", - "dev": true + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" }, "jsonpointer": { "version": "4.0.1", @@ -5343,7 +6060,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -5377,17 +6093,22 @@ "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.5" } }, + "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" + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" }, "lcid": { "version": "1.0.0", @@ -5397,6 +6118,11 @@ "invert-kv": "1.0.0" } }, + "left-pad": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz", + "integrity": "sha1-YS9hwDPzqeCOk58crr7qQbbzGZo=" + }, "leven": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", @@ -5417,7 +6143,6 @@ "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", @@ -5452,12 +6177,65 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" }, + "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.cond": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", "dev": true }, + "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.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -5468,11 +6246,76 @@ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=" }, + "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.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" }, + "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" + } + }, "lolex": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.1.2.tgz", @@ -5481,8 +6324,7 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, "loose-envify": { "version": "1.3.1", @@ -5500,6 +6342,11 @@ "pseudomap": "1.0.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", @@ -5508,6 +6355,11 @@ "tmpl": "1.0.4" } }, + "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", @@ -5519,14 +6371,162 @@ "merge": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz", - "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=", - "dev": true + "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.3" + } + }, + "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-bundler": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/metro-bundler/-/metro-bundler-0.13.0.tgz", + "integrity": "sha512-fJAUuQ2HOVfXJVmTlrjj/KFrkK6ww/EJNoNzfNB+7KravsQ5otf8s1IFkSoBYTfi1kM4AuiBlqrWvmn27zcScg==", + "requires": { + "absolute-path": "0.0.0", + "async": "2.5.0", + "babel-core": "6.26.0", + "babel-generator": "6.26.0", + "babel-plugin-external-helpers": "6.22.0", + "babel-preset-es2015-node": "6.1.1", + "babel-preset-fbjs": "2.1.4", + "babel-preset-react-native": "2.1.0", + "babel-register": "6.26.0", + "babylon": "6.18.0", + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "core-js": "2.5.1", + "debug": "2.6.9", + "denodeify": "1.2.1", + "fbjs": "0.8.14", + "graceful-fs": "4.1.11", + "image-size": "0.6.1", + "jest-docblock": "20.1.0-echo.1", + "jest-haste-map": "20.1.0-echo.1", + "json-stable-stringify": "1.0.1", + "json5": "0.4.0", + "left-pad": "1.1.3", + "lodash": "4.17.4", + "merge-stream": "1.0.1", + "mime-types": "2.1.11", + "mkdirp": "0.5.1", + "request": "2.83.0", + "rimraf": "2.6.2", + "source-map": "0.5.7", + "temp": "0.8.3", + "throat": "4.1.0", + "uglify-js": "2.7.5", + "write-file-atomic": "1.3.4", + "xpipe": "1.0.5" + }, + "dependencies": { + "babel-preset-react-native": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-native/-/babel-preset-react-native-2.1.0.tgz", + "integrity": "sha1-kBPr2C2hyIECv1iIEP9Z4gnKK4o=", + "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.7.7", + "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.6.5", + "react-transform-hmr": "1.0.4" + } + }, + "fbjs": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz", + "integrity": "sha1-0dviviVMNakeCfMfnNUKQLKg7Rw=", + "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.14" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "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" + } + } + } }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, "requires": { "arr-diff": "2.0.0", "array-unique": "0.2.1", @@ -5543,11 +6543,23 @@ "regex-cache": "0.4.4" } }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, "mime-db": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", - "dev": true + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } }, "mimic-fn": { "version": "1.1.0", @@ -5558,7 +6570,6 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "dev": true, "requires": { "dom-walk": "0.1.1" } @@ -5597,16 +6608,87 @@ "resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz", "integrity": "sha1-XceVaVZaENbv7VQ5SR5p0jkuWPE=" }, + "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" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "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" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "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.7.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", - "dev": true, "optional": true }, "native-promise-only": { @@ -5620,6 +6702,11 @@ "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=" + }, "nise": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nise/-/nise-1.1.0.tgz", @@ -5656,8 +6743,7 @@ "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" }, "node-notifier": { "version": "5.1.2", @@ -5694,7 +6780,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, "requires": { "remove-trailing-separator": "1.1.0" } @@ -5707,6 +6792,16 @@ "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", @@ -5721,8 +6816,7 @@ "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, "object-assign": { "version": "4.1.1", @@ -5750,26 +6844,52 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, "requires": { "for-own": "0.1.5", "is-extendable": "0.1.1" } }, + "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=", - "dev": true, "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.1.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=", - "dev": true, "requires": { "minimist": "0.0.8", "wordwrap": "0.0.3" @@ -5778,8 +6898,7 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" } } }, @@ -5797,6 +6916,11 @@ "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", @@ -5806,11 +6930,19 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, "requires": { "lcid": "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", @@ -5849,7 +6981,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, "requires": { "glob-base": "0.3.0", "is-dotfile": "1.0.3", @@ -5871,11 +7002,15 @@ "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", "dev": true }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, "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" } @@ -5921,18 +7056,26 @@ "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" } }, + "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=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "2.3.0", @@ -5942,14 +7085,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" } @@ -5963,6 +7104,24 @@ "find-up": "1.1.2" } }, + "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", @@ -5978,8 +7137,12 @@ "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true + "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.7", @@ -5989,14 +7152,12 @@ "process": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", - "dev": true + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "progress": { "version": "1.1.8", @@ -6035,8 +7196,7 @@ "prr": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", - "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", - "dev": true + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=" }, "pseudomap": { "version": "1.0.2", @@ -6046,14 +7206,22 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "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==", - "dev": true, "requires": { "is-number": "3.0.0", "kind-of": "4.0.0" @@ -6063,7 +7231,6 @@ "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" }, @@ -6072,7 +7239,6 @@ "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.5" } @@ -6083,18 +7249,196 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { "is-buffer": "1.1.5" } } } }, + "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.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.0.0.tgz", + "integrity": "sha1-zn348ZQbA28Cssyp29DLHw6FXi0=", + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.0" + } + }, + "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=", - "dev": true + "integrity": "sha1-vNMUeAJ7ZLMznxCJIatSC0MT3Cw=" + }, + "react-devtools-core": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-2.5.2.tgz", + "integrity": "sha1-+XvsWvrl2TGNFneAZeDCFMTVcUw=", + "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=" + }, + "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.0" + } + } + } + }, + "react-native": { + "version": "0.49.0-rc.6", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.49.0-rc.6.tgz", + "integrity": "sha1-bO/g+9jFurQDDRjw8v+y2cJKBa4=", + "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-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.1", + "chalk": "1.1.3", + "commander": "2.11.0", + "connect": "2.30.2", + "create-react-class": "15.6.2", + "debug": "2.6.9", + "denodeify": "1.2.1", + "envinfo": "3.4.1", + "event-target-shim": "1.1.1", + "fbjs": "0.8.14", + "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.4", + "metro-bundler": "0.13.0", + "mime": "1.4.1", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "node-fetch": "1.7.3", + "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.0", + "react-clone-referenced-element": "1.0.1", + "react-devtools-core": "2.5.2", + "react-timer-mixin": "0.13.3", + "regenerator-runtime": "0.9.6", + "rimraf": "2.6.2", + "semver": "5.4.1", + "shell-quote": "1.6.1", + "stacktrace-parser": "0.1.4", + "whatwg-fetch": "1.1.1", + "ws": "1.1.4", + "xcode": "0.9.3", + "xmldoc": "0.4.0", + "yargs": "6.6.0" + }, + "dependencies": { + "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.1", + "regenerator-runtime": "0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==" + } + } + }, + "fbjs": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz", + "integrity": "sha1-0dviviVMNakeCfMfnNUKQLKg7Rw=", + "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.14" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "regenerator-runtime": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz", + "integrity": "sha1-0z65XQ0gAaS+OWWXB8UbDLcc4Ck=" + }, + "whatwg-fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz", + "integrity": "sha1-rDydOfMgxtzlM5lp0FTvQ90zMxk=" + } + } }, "react-native-dismiss-keyboard": { "version": "1.0.0", @@ -6284,7 +7628,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz", "integrity": "sha1-nb/Z2SdSjDqp9ETkVYw3gwq4wmo=", - "dev": true, "requires": { "lodash": "4.17.4", "react-deep-force-update": "1.1.1" @@ -6312,11 +7655,15 @@ "object-assign": "4.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=", - "dev": true, "requires": { "global": "4.3.2", "react-proxy": "1.1.8" @@ -6326,7 +7673,6 @@ "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", @@ -6337,7 +7683,6 @@ "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" @@ -6347,7 +7692,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -6466,7 +7810,6 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, "requires": { "is-equal-shallow": "0.1.3" } @@ -6504,20 +7847,17 @@ "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=", - "dev": true + "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=", - "dev": true + "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=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "repeating": { "version": "2.0.1", @@ -6527,11 +7867,15 @@ "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.83.0", "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", - "dev": true, "requires": { "aws-sign2": "0.7.0", "aws4": "1.6.0", @@ -6561,7 +7905,6 @@ "version": "2.1.17", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "dev": true, "requires": { "mime-db": "1.30.0" } @@ -6569,8 +7912,7 @@ "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" } } }, @@ -6614,12 +7956,35 @@ "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, + "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.1", + "on-headers": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + } + } + }, + "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" + } + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, "requires": { "align-text": "0.1.4" } @@ -6628,27 +7993,184 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, "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==", - "dev": true + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "samsam": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz", "integrity": "sha1-7dOQk6MYQ3DLhZJDsr3yVefY6mc=" }, + "sane": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-2.2.0.tgz", + "integrity": "sha512-OSJxhHO0CgPUw3lUm3GhfREAfza45smvEI9ozuFrxKG10GHVo0ryW9FK5VYlLvxj0SV7HVKHW0voYJIRu27GWg==", + "requires": { + "anymatch": "1.3.2", + "exec-sh": "0.2.1", + "fb-watchman": "2.0.0", + "fsevents": "1.1.2", + "minimatch": "3.0.4", + "minimist": "1.2.0", + "walker": "1.0.7", + "watch": "0.18.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "sax": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.6.tgz", + "integrity": "sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA=" + }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" }, + "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.1", + "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.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "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=" + } + } + }, + "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.17", + "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", @@ -6679,6 +8201,17 @@ "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" + } + }, "shelljs": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", @@ -6759,6 +8292,38 @@ "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=" + } + } + }, "sinon": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-3.3.0.tgz", @@ -6788,11 +8353,15 @@ "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=" + }, "sntp": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", - "dev": true, "requires": { "hoek": "4.2.0" } @@ -6810,6 +8379,11 @@ "source-map": "0.5.7" } }, + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=" + }, "spdx-correct": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", @@ -6838,7 +8412,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "dev": true, "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -6855,11 +8428,56 @@ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, + "stacktrace-parser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.4.tgz", + "integrity": "sha1-ATl5IuX2Ls8whFUiyVxP4dJefU4=" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "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" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "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" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "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, "requires": { "safe-buffer": "5.1.1" } @@ -6900,8 +8518,7 @@ "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" }, "strip-ansi": { "version": "3.0.1", @@ -6915,7 +8532,6 @@ "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" } @@ -6973,6 +8589,22 @@ } } }, + "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.1.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.1.1.tgz", @@ -6997,17 +8629,43 @@ "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=", - "dev": true + "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.3", + "xtend": "4.0.1" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" }, "timespan": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz", "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=" }, + "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", @@ -7022,7 +8680,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "dev": true, "requires": { "punycode": "1.4.1" } @@ -7049,11 +8706,15 @@ "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", "dev": true }, + "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=", - "dev": true, "requires": { "safe-buffer": "5.1.1" } @@ -7062,7 +8723,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, "type-check": { @@ -7079,11 +8739,19 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=" }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.17" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "ua-parser-js": { "version": "0.7.14", @@ -7094,8 +8762,6 @@ "version": "2.7.5", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.5.tgz", "integrity": "sha1-RhLAx7qu4rp8SH3kkErhIgefLKg=", - "dev": true, - "optional": true, "requires": { "async": "0.2.10", "source-map": "0.5.7", @@ -7106,16 +8772,12 @@ "async": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", - "dev": true, - "optional": true + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, "requires": { "camelcase": "1.2.1", "cliui": "2.1.0", @@ -7128,9 +8790,25 @@ "uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=" + }, + "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.1.0", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", + "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "user-home": { "version": "1.1.1", @@ -7141,14 +8819,17 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "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.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", - "dev": true + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" }, "v8flags": { "version": "2.1.1", @@ -7168,17 +8849,36 @@ "spdx-expression-parse": "1.0.4" } }, + "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=", - "dev": true, "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.2", + "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", @@ -7187,6 +8887,22 @@ "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" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -7244,15 +8960,20 @@ "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "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.4.1" + } }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" }, "winston": { "version": "2.4.0", @@ -7289,7 +9010,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.5.0.tgz", "integrity": "sha512-DHRiUggxtbruaTwnLDm2/BRDKZIoOYvrgYUj5Bam4fU6Gtvc0FaEyoswFPBjMXAweGW2H4BDNIpy//1yXXuaqQ==", - "dev": true, "requires": { "errno": "0.1.4", "xtend": "4.0.1" @@ -7327,8 +9047,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "wrench": { "version": "1.3.9", @@ -7344,17 +9063,92 @@ "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.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.4.tgz", + "integrity": "sha1-V/QNA2gy5fUFVmKjl8Tedu1mv2E=", + "requires": { + "options": "0.0.6", + "ultron": "1.0.2" + }, + "dependencies": { + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" + } + } + }, + "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=" + } + } + }, "xml-name-validator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", "dev": true }, + "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=", - "dev": true + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { "version": "3.2.1", @@ -7370,7 +9164,6 @@ "version": "6.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "dev": true, "requires": { "camelcase": "3.0.0", "cliui": "3.2.0", @@ -7390,14 +9183,12 @@ "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, "requires": { "string-width": "1.0.2", "strip-ansi": "3.0.1", @@ -7408,7 +9199,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" } @@ -7417,7 +9207,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", @@ -7430,7 +9219,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "dev": true, "requires": { "camelcase": "3.0.0" }, @@ -7438,8 +9226,7 @@ "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" } } } diff --git a/tests/src/containers/CoreContainer.js b/tests/src/containers/CoreContainer.js index 57a786e8..fdf30041 100644 --- a/tests/src/containers/CoreContainer.js +++ b/tests/src/containers/CoreContainer.js @@ -31,7 +31,7 @@ class CoreContainer extends React.Component { NetInfo.isConnected.fetch().then((isConnected) => { this.handleAppStateChange('active'); // Force connect (react debugger issue) this.props.dispatch(setNetworkState(isConnected)); - NetInfo.isConnected.addEventListener('change', this.handleNetworkChange); + NetInfo.isConnected.addEventListener('connectionChange', this.handleNetworkChange); }); } @@ -40,7 +40,7 @@ class CoreContainer extends React.Component { */ componentWillUnmount() { AppState.removeEventListener('change', this.handleAppStateChange); - NetInfo.isConnected.removeEventListener('change', this.handleNetworkChange); + NetInfo.isConnected.removeEventListener('connectionChange', this.handleNetworkChange); } props: Props; diff --git a/tests/src/tests/firestore/collectionReferenceTests.js b/tests/src/tests/firestore/collectionReferenceTests.js new file mode 100644 index 00000000..ab2a1f92 --- /dev/null +++ b/tests/src/tests/firestore/collectionReferenceTests.js @@ -0,0 +1,385 @@ +import sinon from 'sinon'; +import 'should-sinon'; +import should from 'should'; + +function collectionReferenceTests({ describe, it, context, firebase }) { + describe('CollectionReference', () => { + context('class', () => { + it('should return instance methods', () => { + return new Promise((resolve) => { + const collection = firebase.native.firestore().collection('collection-tests'); + collection.should.have.property('firestore'); + // TODO: Remaining checks + + resolve(); + }); + }); + }); + + context('add()', () => { + it('should create Document', () => { + return firebase.native.firestore() + .collection('collection-tests') + .add({ first: 'Ada', last: 'Lovelace', born: 1815 }) + .then(async (docRef) => { + const doc = await firebase.native.firestore().doc(docRef.path).get(); + doc.data().first.should.equal('Ada'); + }); + }); + }); + + context('doc()', () => { + it('should create DocumentReference with correct path', () => { + return new Promise((resolve) => { + const docRef = firebase.native.firestore().collection('collection-tests').doc('doc'); + should.equal(docRef.path, 'collection-tests/doc'); + resolve(); + }); + }); + }); + + context('get()', () => { + it('should retrieve a single document', () => { + return firebase.native.firestore() + .collection('collection-tests') + .get() + .then((querySnapshot) => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach((documentSnapshot) => { + should.equal(documentSnapshot.data().baz, true); + }); + }); + }); + }); + + context('onSnapshot()', () => { + it('calls callback with the initial data and then when document changes', () => { + return new Promise(async (resolve) => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); + + resolve(); + }); + }); + }); + + context('onSnapshot()', () => { + it('calls callback with the initial data and then when document is added', () => { + return new Promise(async (resolve) => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc2'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(currentDocValue); + callback.should.be.calledWith(newDocValue); + callback.should.be.calledThrice(); + + // Tear down + + unsubscribe(); + + resolve(); + }); + }); + }); + + context('onSnapshot()', () => { + it('doesn\'t call callback when the ref is updated with the same value', async () => { + return new Promise(async (resolve) => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(currentDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledOnce(); // Callback is not called again + + // Tear down + + unsubscribe(); + + resolve(); + }); + }); + }); + + context('onSnapshot()', () => { + it('allows binding multiple callbacks to the same ref', () => { + return new Promise(async (resolve) => { + // Setup + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackA(doc.data())); + resolve2(); + }); + }); + await new Promise((resolve2) => { + unsubscribeB = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackB(doc.data())); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDocValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDocValue); + callbackB.should.be.calledOnce(); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Tear down + + unsubscribeA(); + unsubscribeB(); + + resolve(); + }); + }); + }); + + context('onSnapshot()', () => { + it('listener stops listening when unsubscribed', () => { + return new Promise(async (resolve) => { + // Setup + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackA(doc.data())); + resolve2(); + }); + }); + await new Promise((resolve2) => { + unsubscribeB = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackB(doc.data())); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDocValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDocValue); + callbackB.should.be.calledOnce(); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Unsubscribe A + + unsubscribeA(); + + await docRef.set(currentDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackB.should.be.calledWith(currentDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + // Unsubscribe B + + unsubscribeB(); + + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + resolve(); + }); + }); + }); + + // Where + context('where()', () => { + it('correctly handles == boolean values', () => { + return firebase.native.firestore() + .collection('collection-tests') + .where('baz', '==', true) + .get() + .then((querySnapshot) => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach((documentSnapshot) => { + should.equal(documentSnapshot.data().baz, true); + }); + }); + }); + + it('correctly handles == string values', () => { + return firebase.native.firestore() + .collection('collection-tests') + .where('foo', '==', 'bar') + .get() + .then((querySnapshot) => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach((documentSnapshot) => { + should.equal(documentSnapshot.data().foo, 'bar'); + }); + }); + }); + + it('correctly handles == null values', () => { + return firebase.native.firestore() + .collection('collection-tests') + .where('naz', '==', null) + .get() + .then((querySnapshot) => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach((documentSnapshot) => { + should.equal(documentSnapshot.data().naz, null); + }); + }); + }); + + it('correctly handles >= number values', () => { + return firebase.native.firestore() + .collection('collection-tests') + .where('daz', '>=', 123) + .get() + .then((querySnapshot) => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach((documentSnapshot) => { + should.equal(documentSnapshot.data().daz, 123); + }); + }); + }); + + it('correctly handles <= float values', () => { + return firebase.native.firestore() + .collection('collection-tests') + .where('gaz', '<=', 12.1234666) + .get() + .then((querySnapshot) => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach((documentSnapshot) => { + should.equal(documentSnapshot.data().gaz, 12.1234567); + }); + }); + }); + }); + }); +} + +export default collectionReferenceTests; diff --git a/tests/src/tests/firestore/documentReferenceTests.js b/tests/src/tests/firestore/documentReferenceTests.js new file mode 100644 index 00000000..11166366 --- /dev/null +++ b/tests/src/tests/firestore/documentReferenceTests.js @@ -0,0 +1,299 @@ +import sinon from 'sinon'; +import 'should-sinon'; +import should from 'should'; + +function collectionReferenceTests({ describe, it, context, firebase }) { + describe('DocumentReference', () => { + context('class', () => { + it('should return instance methods', () => { + return new Promise((resolve) => { + const document = firebase.native.firestore().doc('document-tests/doc1'); + document.should.have.property('firestore'); + // TODO: Remaining checks + + resolve(); + }); + }); + }); + + context('delete()', () => { + it('should delete Document', () => { + return firebase.native.firestore() + .doc('document-tests/doc1') + .delete() + .then(async () => { + const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); + should.equal(doc.exists, false); + }); + }); + }); + + context('onSnapshot()', () => { + it('calls callback with the initial data and then when value changes', () => { + return new Promise(async (resolve) => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot((snapshot) => { + callback(snapshot.data()); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDataValue); + + // Update the document + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDataValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); + + resolve(); + }); + }); + }); + + context('onSnapshot()', () => { + it('doesn\'t call callback when the ref is updated with the same value', async () => { + return new Promise(async (resolve) => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot((snapshot) => { + callback(snapshot.data()); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDataValue); + + await docRef.set(currentDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledOnce(); // Callback is not called again + + // Tear down + + unsubscribe(); + + resolve(); + }); + }); + }); + + context('onSnapshot()', () => { + it('allows binding multiple callbacks to the same ref', () => { + return new Promise(async (resolve) => { + // Setup + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = docRef.onSnapshot((snapshot) => { + callbackA(snapshot.data()); + resolve2(); + }); + }); + + await new Promise((resolve2) => { + unsubscribeB = docRef.onSnapshot((snapshot) => { + callbackB(snapshot.data()); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDataValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDataValue); + callbackB.should.be.calledOnce(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDataValue); + callbackB.should.be.calledWith(newDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Tear down + + unsubscribeA(); + unsubscribeB(); + + resolve(); + }); + }); + }); + + context('onSnapshot()', () => { + it('listener stops listening when unsubscribed', () => { + return new Promise(async (resolve) => { + // Setup + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = docRef.onSnapshot((snapshot) => { + callbackA(snapshot.data()); + resolve2(); + }); + }); + + await new Promise((resolve2) => { + unsubscribeB = docRef.onSnapshot((snapshot) => { + callbackB(snapshot.data()); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDataValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDataValue); + callbackB.should.be.calledOnce(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDataValue); + callbackB.should.be.calledWith(newDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Unsubscribe A + + unsubscribeA(); + + await docRef.set(currentDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackB.should.be.calledWith(currentDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + // Unsubscribe B + + unsubscribeB(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + resolve(); + }); + }); + }); + + context('set()', () => { + it('should create Document', () => { + return firebase.native.firestore() + .doc('document-tests/doc2') + .set({ name: 'doc2' }) + .then(async () => { + const doc = await firebase.native.firestore().doc('document-tests/doc2').get(); + doc.data().name.should.equal('doc2'); + }); + }); + }); + + context('set()', () => { + it('should merge Document', () => { + return firebase.native.firestore() + .doc('document-tests/doc1') + .set({ merge: 'merge' }, { merge: true }) + .then(async () => { + const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); + doc.data().name.should.equal('doc1'); + doc.data().merge.should.equal('merge'); + }); + }); + }); + + context('set()', () => { + it('should overwrite Document', () => { + return firebase.native.firestore() + .doc('document-tests/doc1') + .set({ name: 'overwritten' }) + .then(async () => { + const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); + doc.data().name.should.equal('overwritten'); + }); + }); + }); + + context('update()', () => { + it('should update Document', () => { + return firebase.native.firestore() + .doc('document-tests/doc1') + .set({ name: 'updated' }) + .then(async () => { + const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); + doc.data().name.should.equal('updated'); + }); + }); + }); + }); +} + +export default collectionReferenceTests; diff --git a/tests/src/tests/firestore/firestoreTests.js b/tests/src/tests/firestore/firestoreTests.js new file mode 100644 index 00000000..26a29893 --- /dev/null +++ b/tests/src/tests/firestore/firestoreTests.js @@ -0,0 +1,63 @@ +import should from 'should'; + +function firestoreTests({ describe, it, context, firebase }) { + describe('firestore()', () => { + context('collection()', () => { + it('should create CollectionReference with the right id', () => { + return new Promise((resolve) => { + const collectionRef = firebase.native.firestore().collection('collection1/doc1/collection2'); + should.equal(collectionRef.id, 'collection2'); + resolve(); + }); + }); + }); + + context('doc()', () => { + it('should create DocumentReference with correct path', () => { + return new Promise((resolve) => { + const docRef = firebase.native.firestore().doc('collection1/doc1/collection2/doc2'); + should.equal(docRef.path, 'collection1/doc1/collection2/doc2'); + resolve(); + }); + }); + }); + + context('batch()', () => { + it('should create / update / delete as expected', () => { + const ayRef = firebase.native.firestore().collection('firestore-tests').doc('AY'); + const lRef = firebase.native.firestore().collection('firestore-tests').doc('LON'); + const nycRef = firebase.native.firestore().collection('firestore-tests').doc('NYC'); + const sfRef = firebase.native.firestore().collection('firestore-tests').doc('SF'); + + return firebase.native.firestore() + .batch() + .set(ayRef, { name: 'Aylesbury' }) + .set(lRef, { name: 'London' }) + .set(nycRef, { name: 'New York City' }) + .set(sfRef, { name: 'San Francisco' }) + .update(nycRef, { population: 1000000 }) + .update(sfRef, { name: 'San Fran' }) + .set(lRef, { population: 3000000 }, { merge: true }) + .delete(ayRef) + .commit() + .then(async () => { + const ayDoc = await ayRef.get(); + should.equal(ayDoc.exists, false); + + const lDoc = await lRef.get(); + lDoc.data().name.should.equal('London'); + lDoc.data().population.should.equal(3000000); + + const nycDoc = await nycRef.get(); + nycDoc.data().name.should.equal('New York City'); + nycDoc.data().population.should.equal(1000000); + + const sfDoc = await sfRef.get(); + sfDoc.data().name.should.equal('San Fran'); + }); + }); + }); + }); +} + +export default firestoreTests; diff --git a/tests/src/tests/firestore/index.js b/tests/src/tests/firestore/index.js new file mode 100644 index 00000000..22e4b734 --- /dev/null +++ b/tests/src/tests/firestore/index.js @@ -0,0 +1,66 @@ +import firebase from '../../firebase'; +import TestSuite from '../../../lib/TestSuite'; + +/* + Test suite files + */ +import collectionReferenceTests from './collectionReferenceTests'; +import documentReferenceTests from './documentReferenceTests'; +import firestoreTests from './firestoreTests'; + +const suite = new TestSuite('Firestore', 'firebase.firestore()', firebase); + +const testGroups = [ + collectionReferenceTests, + documentReferenceTests, + firestoreTests, +]; + +function firestoreTestSuite(testSuite) { + testSuite.beforeEach(async () => { + this.collectionTestsCollection = testSuite.firebase.native.firestore().collection('collection-tests'); + this.documentTestsCollection = testSuite.firebase.native.firestore().collection('document-tests'); + this.firestoreTestsCollection = testSuite.firebase.native.firestore().collection('firestore-tests'); + // Clean the collections in case the last run failed + await cleanCollection(this.collectionTestsCollection); + await cleanCollection(this.documentTestsCollection); + await cleanCollection(this.firestoreTestsCollection); + + await this.collectionTestsCollection.add({ + baz: true, + daz: 123, + foo: 'bar', + gaz: 12.1234567, + naz: null, + }); + + await this.documentTestsCollection.doc('doc1').set({ + name: 'doc1', + }); + }); + + testSuite.afterEach(async () => { + await cleanCollection(this.collectionTestsCollection); + await cleanCollection(this.documentTestsCollection); + await cleanCollection(this.firestoreTestsCollection); + }); + + testGroups.forEach((testGroup) => { + testGroup(testSuite); + }); +} + +/* + Register tests with test suite + */ +suite.addTests(firestoreTestSuite); + +export default suite; + +/* HELPER FUNCTIONS */ +async function cleanCollection(collection) { + const collectionTestsDocs = await collection.get(); + const tasks = []; + collectionTestsDocs.forEach(doc => tasks.push(doc.ref.delete())); + await Promise.all(tasks); +} diff --git a/tests/src/tests/index.js b/tests/src/tests/index.js index 2c37f002..c8462a9c 100644 --- a/tests/src/tests/index.js +++ b/tests/src/tests/index.js @@ -1,30 +1,32 @@ import { setSuiteStatus, setTestStatus } from '../actions/TestActions'; -import analytics from './analytics/index'; -import crash from './crash/index'; -import core from './core/index'; -import database from './database/index'; -import messaging from './messaging/index'; -import storage from './storage/index'; -import auth from './auth/index'; -import config from './config/index'; -import performance from './perf/index'; -import admob from './admob/index'; +import analytics from './analytics'; +import crash from './crash'; +import core from './core'; +import database from './database'; +import messaging from './messaging'; +import storage from './storage'; +import auth from './auth'; +import config from './config'; +import performance from './perf'; +import admob from './admob'; +import firestore from './firestore'; window.getCoverage = function getCoverage() { return (JSON.stringify(global.__coverage__)); }; const testSuiteInstances = [ - database, - auth, - analytics, - messaging, - crash, - core, - storage, - config, - performance, admob, + analytics, + auth, + config, + core, + crash, + database, + firestore, + messaging, + performance, + storage, ]; /*