diff --git a/.babelrc b/.babelrc index 6c1e0ce..54489eb 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,4 @@ { - "presets": ["react-native"] -} \ No newline at end of file + "presets": ["react-native"], + "plugins": ["transform-flow-strip-types"] +} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index a155612..e03e71f 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -639,6 +639,3 @@ private WritableMap getUserMap() { return userMap; } } -n userMap; - } -} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 37aeca3..49de29c 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -1,6 +1,7 @@ package io.fullstack.firestack; import android.content.Context; +import android.text.TextUtils; import android.util.Log; import java.util.HashMap; import java.util.List; @@ -13,6 +14,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; @@ -49,29 +51,29 @@ public void setModifiers(final ReadableArray modifiers) { mModifiers = modifiers; } - public void addChildEventListener(final String name, final ReadableArray modifiers) { + public void addChildEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { final FirestackDBReference self = this; if (mEventListener == null) { mEventListener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_added", mPath, dataSnapshot); + self.handleDatabaseEvent("child_added", mPath, modifiersString, dataSnapshot); } @Override public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_changed", mPath, dataSnapshot); + self.handleDatabaseEvent("child_changed", mPath, modifiersString, dataSnapshot); } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("child_removed", mPath, dataSnapshot); + self.handleDatabaseEvent("child_removed", mPath, modifiersString, dataSnapshot); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_moved", mPath, dataSnapshot); + self.handleDatabaseEvent("child_moved", mPath, modifiersString, dataSnapshot); } @Override @@ -81,18 +83,18 @@ public void onCancelled(DatabaseError error) { }; } - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); ref.addChildEventListener(mEventListener); - this.setListeningTo(mPath, name); + //this.setListeningTo(mPath, modifiersString, name); } - public void addValueEventListener(final String name, final ReadableArray modifiers) { + public void addValueEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { final FirestackDBReference self = this; mValueListener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("value", mPath, dataSnapshot); + self.handleDatabaseEvent("value", mPath, modifiersString, dataSnapshot); } @Override @@ -101,19 +103,20 @@ public void onCancelled(DatabaseError error) { } }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); ref.addValueEventListener(mValueListener); - this.setListeningTo(mPath, "value"); + //this.setListeningTo(mPath, modifiersString, "value"); } - public void addOnceValueEventListener(final ReadableArray modifiers, + public void addOnceValueEventListener(final ReadableArray modifiersArray, + final String modifiersString, final Callback callback) { final FirestackDBReference self = this; mOnceValueListener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { - WritableMap data = FirestackUtils.dataSnapshotToMap("value", mPath, dataSnapshot); + WritableMap data = FirestackUtils.dataSnapshotToMap("value", mPath, modifiersString, dataSnapshot); callback.invoke(null, data); } @@ -127,31 +130,31 @@ public void onCancelled(DatabaseError error) { } }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); ref.addListenerForSingleValueEvent(mOnceValueListener); } - public Boolean isListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, evtName); - return mListeners.containsKey(key); - } + //public Boolean isListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // return mListeners.containsKey(key); + //} /** * Note: these path/eventType listeners only get removed when javascript calls .off() and cleanup is run on the entire path */ - public void setListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, evtName); - mListeners.put(key, true); - } + //public void setListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // mListeners.put(key, true); + //} - public void notListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, evtName); - mListeners.remove(key); - } + //public void notListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // mListeners.remove(key); + //} - private String pathListeningKey(final String path, final String eventName) { - return "listener/" + path + "/" + eventName; - } + //private String pathListeningKey(final String path, String modifiersString, final String eventName) { + //return "listener/" + path + "/" + modifiersString + "/" + eventName; + //} public void cleanup() { Log.d(TAG, "cleaning up database reference " + this); @@ -163,10 +166,10 @@ public void removeChildEventListener() { if (mEventListener != null) { DatabaseReference ref = this.getDatabaseRef(); ref.removeEventListener(mEventListener); - this.notListeningTo(mPath, "child_added"); - this.notListeningTo(mPath, "child_changed"); - this.notListeningTo(mPath, "child_removed"); - this.notListeningTo(mPath, "child_moved"); + //this.notListeningTo(mPath, "child_added"); + //this.notListeningTo(mPath, "child_changed"); + //this.notListeningTo(mPath, "child_removed"); + //this.notListeningTo(mPath, "child_moved"); mEventListener = null; } } @@ -175,7 +178,7 @@ public void removeValueEventListener() { DatabaseReference ref = this.getDatabaseRef(); if (mValueListener != null) { ref.removeEventListener(mValueListener); - this.notListeningTo(mPath, "value"); + //this.notListeningTo(mPath, "value"); mValueListener = null; } if (mOnceValueListener != null) { @@ -184,14 +187,15 @@ public void removeValueEventListener() { } } - private void handleDatabaseEvent(final String name, final String path, final DataSnapshot dataSnapshot) { - if (!FirestackDBReference.this.isListeningTo(path, name)) { - return; - } - WritableMap data = FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); + private void handleDatabaseEvent(final String name, final String path, final String modifiersString, final DataSnapshot dataSnapshot) { + //if (!FirestackDBReference.this.isListeningTo(path, modifiersString, name)) { + //return; + //} + WritableMap data = FirestackUtils.dataSnapshotToMap(name, path, modifiersString, dataSnapshot); WritableMap evt = Arguments.createMap(); evt.putString("eventName", name); evt.putString("path", path); + evt.putString("modifiersString", modifiersString); evt.putMap("body", data); FirestackUtils.sendEvent(mReactContext, "database_event", evt); @@ -433,22 +437,23 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { @ReactMethod public void on(final String path, - final ReadableArray modifiers, + final String modifiersString, + final ReadableArray modifiersArray, final String name, final Callback callback) { - FirestackDBReference ref = this.getDBHandle(path); + FirestackDBReference ref = this.getDBHandle(path, modifiersString); WritableMap resp = Arguments.createMap(); if (name.equals("value")) { - ref.addValueEventListener(name, modifiers); + ref.addValueEventListener(name, modifiersArray, modifiersString); } else { - ref.addChildEventListener(name, modifiers); + ref.addChildEventListener(name, modifiersArray, modifiersString); } - this.saveDBHandle(path, ref); + this.saveDBHandle(path, modifiersString, ref); resp.putString("result", "success"); - Log.d(TAG, "Added listener " + name + " for " + ref); + Log.d(TAG, "Added listener " + name + " for " + ref + "with modifiers: "+ modifiersString); resp.putString("handle", path); callback.invoke(null, resp); @@ -456,12 +461,13 @@ public void on(final String path, @ReactMethod public void onOnce(final String path, - final ReadableArray modifiers, + final String modifiersString, + final ReadableArray modifiersArray, final String name, final Callback callback) { Log.d(TAG, "Setting one-time listener on event: " + name + " for path " + path); - FirestackDBReference ref = this.getDBHandle(path); - ref.addOnceValueEventListener(modifiers, callback); + FirestackDBReference ref = this.getDBHandle(path, modifiersString); + ref.addOnceValueEventListener(modifiersArray, modifiersString, callback); } /** @@ -471,9 +477,13 @@ public void onOnce(final String path, * off() should therefore clean *everything* up */ @ReactMethod - public void off(final String path, @Deprecated final String name, final Callback callback) { - this.removeDBHandle(path); - Log.d(TAG, "Removed listener " + path); + public void off( + final String path, + final String modifiersString, + @Deprecated final String name, + final Callback callback) { + this.removeDBHandle(path, modifiersString); + Log.d(TAG, "Removed listener " + path + "/" + modifiersString); WritableMap resp = Arguments.createMap(); resp.putString("handle", path); resp.putString("result", "success"); @@ -573,24 +583,31 @@ private void handleCallback( } } - private FirestackDBReference getDBHandle(final String path) { - if (!mDBListeners.containsKey(path)) { + private FirestackDBReference getDBHandle(final String path, final String modifiersString) { + String key = this.getDBListenerKey(path, modifiersString); + if (!mDBListeners.containsKey(key)) { ReactContext ctx = getReactApplicationContext(); - mDBListeners.put(path, new FirestackDBReference(ctx, path)); + mDBListeners.put(key, new FirestackDBReference(ctx, path)); } - return mDBListeners.get(path); + return mDBListeners.get(key); + } + + private void saveDBHandle(final String path, String modifiersString, final FirestackDBReference dbRef) { + String key = this.getDBListenerKey(path, modifiersString); + mDBListeners.put(key, dbRef); } - private void saveDBHandle(final String path, final FirestackDBReference dbRef) { - mDBListeners.put(path, dbRef); + private String getDBListenerKey(String path, String modifiersString) { + return path + " | " + modifiersString; } - private void removeDBHandle(final String path) { - if (mDBListeners.containsKey(path)) { - FirestackDBReference r = mDBListeners.get(path); + private void removeDBHandle(final String path, String modifiersString) { + String key = this.getDBListenerKey(path, modifiersString); + if (mDBListeners.containsKey(key)) { + FirestackDBReference r = mDBListeners.get(key); r.cleanup(); - mDBListeners.remove(path); + mDBListeners.remove(key); } } @@ -604,73 +621,10 @@ private DatabaseReference getDatabaseReferenceAtPath(final String path) { return mDatabase; } - private Query getDatabaseQueryAtPathAndModifiers( - final String path, - final ReadableArray modifiers) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - List strModifiers = FirestackUtils.recursivelyDeconstructReadableArray(modifiers); - ListIterator it = strModifiers.listIterator(); - Query query = ref.orderByKey(); - - while(it.hasNext()) { - String str = (String) it.next(); - String[] strArr = str.split(":"); - String methStr = strArr[0]; - if (methStr.equalsIgnoreCase("orderByKey")) { - query = ref.orderByKey(); - } else if (methStr.equalsIgnoreCase("orderByValue")) { - query = ref.orderByValue(); - } else if (methStr.equalsIgnoreCase("orderByPriority")) { - query = ref.orderByPriority(); - } else if (methStr.contains("orderByChild")) { - String key = strArr[1]; - Log.d(TAG, "orderByChild: " + key); - query = ref.orderByChild(key); - } else if (methStr.contains("limitToLast")) { - String key = strArr[1]; - int limit = Integer.parseInt(key); - Log.d(TAG, "limitToLast: " + limit); - query = query.limitToLast(limit); - } else if (methStr.contains("limitToFirst")) { - String key = strArr[1]; - int limit = Integer.parseInt(key); - Log.d(TAG, "limitToFirst: " + limit); - query = query.limitToFirst(limit); - } else if (methStr.contains("equalTo")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.equalTo(value); - } else { - query = query.equalTo(value, key); - } - } else if (methStr.contains("endAt")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.endAt(value); - } else { - query = query.endAt(value, key); - } - } else if (methStr.contains("startAt")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.startAt(value); - } else { - query = query.startAt(value, key); - } - } - } - - return query; - } - - private WritableMap dataSnapshotToMap(String name, String path, DataSnapshot dataSnapshot) { - return FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); - } + //private WritableMap dataSnapshotToMap(String name, String path, DataSnapshot dataSnapshot) { + // return FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); + //} private Any castSnapshotValue(DataSnapshot snapshot) { if (snapshot.hasChildren()) { diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 25ca218..210a1bd 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -96,8 +96,9 @@ public void onSuccess(Uri uri) { res.putString("bucket", storageRef.getBucket()); res.putString("fullPath", uri.toString()); res.putString("path", uri.getPath()); + res.putString("url", uri.toString()); - storageRef.getMetadata() + fileRef.getMetadata() .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(final StorageMetadata storageMetadata) { diff --git a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java index 32c871a..8ec7a82 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java @@ -50,7 +50,8 @@ public static void sendEvent(final ReactContext context, // snapshot public static WritableMap dataSnapshotToMap(String name, - String path, + String path, + String modifiersString, DataSnapshot dataSnapshot) { WritableMap data = Arguments.createMap(); @@ -98,6 +99,7 @@ public static WritableMap dataSnapshotToMap(String name, WritableMap eventMap = Arguments.createMap(); eventMap.putString("eventName", name); eventMap.putMap("snapshot", data); + eventMap.putString("modifiersString", modifiersString); eventMap.putString("path", path); return eventMap; } diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index fb684fc..baf52da 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -321,7 +321,8 @@ @implementation FirestackDatabase props:(NSDictionary *) props callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[self getRefAtPath:path] childByAutoId]; + FIRDatabaseReference *pathRef = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [pathRef childByAutoId]; NSURL *url = [NSURL URLWithString:ref.URL]; NSString *newPath = [url path]; @@ -350,11 +351,12 @@ @implementation FirestackDatabase RCT_EXPORT_METHOD(on:(NSString *) path + modifiersString:(NSString *) modifiersString modifiers:(NSArray *) modifiers name:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path]; + FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; FIRDatabaseQuery *query = [r getQueryWithModifiers:modifiers]; if (![r isListeningTo:eventName]) { @@ -367,6 +369,7 @@ @implementation FirestackDatabase props: @{ @"eventName": eventName, @"path": path, + @"modifiersString": modifiersString, @"snapshot": props }]; }; @@ -398,11 +401,12 @@ @implementation FirestackDatabase } RCT_EXPORT_METHOD(onOnce:(NSString *) path - modifiers:(NSArray *) modifiers - name:(NSString *) name - callback:(RCTResponseSenderBlock) callback) + modifiersString:(NSString *) modifiersString + modifiers:(NSArray *) modifiers + name:(NSString *) name + callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path]; + FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; int eventType = [r eventTypeFromName:name]; FIRDatabaseQuery *ref = [r getQueryWithModifiers:modifiers]; @@ -412,6 +416,7 @@ @implementation FirestackDatabase callback(@[[NSNull null], @{ @"eventName": name, @"path": path, + @"modifiersString": modifiersString, @"snapshot": props }]); } @@ -425,17 +430,18 @@ @implementation FirestackDatabase } RCT_EXPORT_METHOD(off:(NSString *)path + modifiersString:(NSString *) modifiersString eventName:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path]; + FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; if (eventName == nil || [eventName isEqualToString:@""]) { [r cleanup]; - [self removeDBHandle:path]; + [self removeDBHandle:path withModifiersString:modifiersString]; } else { [r removeEventHandler:eventName]; if (![r hasListeners]) { - [self removeDBHandle:path]; + [self removeDBHandle:path withModifiersString:modifiersString]; } } @@ -444,6 +450,7 @@ @implementation FirestackDatabase callback(@[[NSNull null], @{ @"result": @"success", @"path": path, + @"modifiersString": modifiersString, @"remainingListeners": [r listenerKeys], }]); } @@ -537,10 +544,9 @@ - (FIRDatabaseReference *) getRef return self.ref; } -- (FIRDatabaseReference *) getRefAtPath:(NSString *) str +- (FIRDatabaseReference *) getRefAtPath:(NSString *) path { - FirestackDBReference *r = [self getDBHandle:str]; - return [r getRef]; + return [[FIRDatabase database] referenceWithPath:path]; } // Handles @@ -552,36 +558,48 @@ - (NSDictionary *) storedDBHandles return __DBHandles; } +- (NSString *) getDBListenerKey:(NSString *) path + withModifiers:(NSString *) modifiersString +{ + return [NSString stringWithFormat:@"%@ | %@", path, modifiersString, nil]; +} + - (FirestackDBReference *) getDBHandle:(NSString *) path + withModifiers:modifiersString { NSDictionary *stored = [self storedDBHandles]; - FirestackDBReference *r = [stored objectForKey:path]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; + FirestackDBReference *r = [stored objectForKey:key]; if (r == nil) { r = [[FirestackDBReference alloc] initWithPath:path]; - [self saveDBHandle:path dbRef:r]; + [self saveDBHandle:path withModifiersString:modifiersString dbRef:r]; } return r; } - (void) saveDBHandle:(NSString *) path + withModifiersString:(NSString*)modifiersString dbRef:(FirestackDBReference *) dbRef { NSMutableDictionary *stored = [[self storedDBHandles] mutableCopy]; - if ([stored objectForKey:path]) { - FirestackDBReference *r = [stored objectForKey:path]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; + if ([stored objectForKey:key]) { + FirestackDBReference *r = [stored objectForKey:key]; [r cleanup]; } - [stored setObject:dbRef forKey:path]; + [stored setObject:dbRef forKey:key]; self._DBHandles = stored; } - (void) removeDBHandle:(NSString *) path + withModifiersString:(NSString*)modifiersString { NSMutableDictionary *stored = [[self storedDBHandles] mutableCopy]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; - FirestackDBReference *r = [stored objectForKey:path]; + FirestackDBReference *r = [stored objectForKey:key]; if (r != nil) { [r cleanup]; } @@ -656,4 +674,4 @@ - (void) sendJSEvent:(NSString *)type } } -@end \ No newline at end of file +@end diff --git a/lib/.flowconfig b/lib/.flowconfig new file mode 100644 index 0000000..020e032 --- /dev/null +++ b/lib/.flowconfig @@ -0,0 +1,105 @@ +[ignore] + + +# Some modules have their own node_modules with overlap +.*/node_modules/node-haste/.* + + +# React Native problems +.*/node_modules/react-native/Libraries/Animated/src/AnimatedInterpolation.js +.*/node_modules/react-native/Libraries/Animated/src/Interpolation.js +.*/node_modules/react-native/Libraries/BugReporting/dumpReactTree.js +.*/node_modules/react-native/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js +.*/node_modules/react-native/Libraries/CustomComponents/NavigationExperimental/NavigationPagerStyleInterpolater.js +.*/node_modules/react-native/Libraries/Experimental/WindowedListView.js +.*/node_modules/react-native/Libraries/Image/Image.io.js +.*/node_modules/react-native/Libraries/NavigationExperimental/NavigationExperimental.js +.*/node_modules/react-native/Libraries/NavigationExperimental/NavigationHeaderStyleInterpolator.js +.*/node_modules/react-native/Libraries/Network/FormData.js +.*/node_modules/react-native/Libraries/ReactIOS/YellowBox.js + + + +# Ignore react and fbjs where there are overlaps, but don't ignore +# anything that react-native relies on +.*/node_modules/fbjs/lib/Map.js +.*/node_modules/fbjs/lib/ErrorUtils.js + +# Flow has a built-in definition for the 'react' module which we prefer to use +# over the currently-untyped source +.*/node_modules/react/react.js +.*/node_modules/react/lib/React.js +.*/node_modules/react/lib/ReactDOM.js + +.*/__mocks__/.* +.*/__tests__/.* + +.*/commoner/test/source/widget/share.js + +# Ignore commoner tests +.*/node_modules/commoner/test/.* + +# See https://github.com/facebook/flow/issues/442 +.*/react-tools/node_modules/commoner/lib/reader.js + +# Ignore jest +.*/node_modules/jest-cli/.* + +# Ignore Website +.*/website/.* + +# Ignore generators +.*/local-cli/generator.* + +# Ignore BUCK generated folders +.*\.buckd/ + +.*/node_modules/is-my-json-valid/test/.*\.json +.*/node_modules/iconv-lite/encodings/tables/.*\.json +.*/node_modules/y18n/test/.*\.json +.*/node_modules/spdx-license-ids/spdx-license-ids.json +.*/node_modules/spdx-exceptions/index.json +.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json +.*/node_modules/resolve/lib/core.json +.*/node_modules/jsonparse/samplejson/.*\.json +.*/node_modules/json5/test/.*\.json +.*/node_modules/ua-parser-js/test/.*\.json +.*/node_modules/builtin-modules/builtin-modules.json +.*/node_modules/binary-extensions/binary-extensions.json +.*/node_modules/url-regex/tlds.json +.*/node_modules/joi/.*\.json +.*/node_modules/isemail/.*\.json +.*/node_modules/tr46/.*\.json +.*/node_modules/protobufjs/src/bower.json +.*/node_modules/grpc/node_modules/protobufjs/src/bower.json + +[include] +node_modules/fbjs/lib + +[libs] +js/flow-lib.js +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow +node_modules/fbjs/flow/lib + +[options] +module.system=haste + +experimental.strict_type_args=true +unsafe.enable_getters_and_setters=true + +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + +munge_underscores=true + +module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy diff --git a/lib/firestack.js b/lib/firestack.js index 8bcbbfa..a36b004 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -1,6 +1,5 @@ /** * @providesModule Firestack - * @flow */ import Log from './utils/log' @@ -21,7 +20,7 @@ import Singleton from './utils/singleton' import RemoteConfig from './modules/remoteConfig' import {Authentication} from './modules/authentication' -import {Database} from './modules/database' +import {Database} from './modules/database.js' import {Analytics} from './modules/analytics' import {Storage} from './modules/storage' import {Presence} from './modules/presence' @@ -38,7 +37,6 @@ export class Firestack extends Singleton { Log.enable(instance._debug); log = instance._log = new Log('firestack'); - log.info('Creating new firestack instance'); instance._remoteConfig = instance.options.remoteConfig || {}; diff --git a/lib/modules/base.js b/lib/modules/base.js index 56bf538..b30f92d 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -1,5 +1,4 @@ /** - * @flow */ import Log from '../utils/log' @@ -110,4 +109,4 @@ export class ReferenceBase extends Base { } return pathStr; } -} \ No newline at end of file +} diff --git a/lib/modules/database.js b/lib/modules/database.js index 5c4b27e..10e5c32 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -1,370 +1,29 @@ /** + * @flow * Database representation wrapper */ +'use strict'; import {NativeModules, NativeEventEmitter} from 'react-native'; const FirestackDatabase = NativeModules.FirestackDatabase; const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); -import promisify from '../utils/promisify' -import { Base, ReferenceBase } from './base' - -let dbSubscriptions = {}; - -class DataSnapshot { - static key:String; - static value:Object; - static exists:boolean; - static hasChildren:boolean; - static childrenCount:Number; - static childKeys:String[]; - - constructor(ref, snapshot) { - this.ref = ref; - this.key = snapshot.key; - this.value = snapshot.value; - this.exists = snapshot.exists || true; - this.priority = snapshot.priority; - this.hasChildren = snapshot.hasChildren || false; - this.childrenCount = snapshot.childrenCount || 0; - this.childKeys = snapshot.childKeys || []; - } - - val() { - return this.value; - } - - forEach(fn) { - (this.childKeys || []) - .forEach(key => fn(this.value[key])) - } - - map(fn) { - let arr = []; - this.forEach(item => arr.push(fn(item))) - return arr; - } - - reverseMap(fn) { - return this.map(fn).reverse(); - } -} - -class DatabaseOnDisconnect { - constructor(ref) { - this.ref = ref; - } - - setValue(val) { - const path = this.ref.dbPath(); - if (typeof val == 'string') { - return promisify('onDisconnectSetString', FirestackDatabase)(path, val); - } else if (typeof val == 'object') { - return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); - } - } - - remove() { - const path = this.ref.dbPath(); - return promisify('onDisconnectRemove', FirestackDatabase)(path); - } - - cancel() { - const path = this.ref.dbPath(); - return promisify('onDisconnectCancel', FirestackDatabase)(path); - } -} - -class DatabaseQuery { - static ref: DatabaseRef; - static orderBy: String[]; - static limit: String[]; - static filters: Object; - - constructor(ref) { - this.ref = ref; - this.reset(); - } - - setOrderBy(name, ...args) { - this.orderBy = [name].concat(args); - return this.ref; - } - - setLimit(name, ...args) { - this.limit = [name].concat(args); - return this.ref; - } - - setFilter(name, ...args) { - this.filters[name] = args; - return this.ref; - } - - build() { - const argsSeparator = ':' - let modifiers = []; - if (this.orderBy) { - modifiers.push(this.orderBy.join(argsSeparator)); - } - if (this.limit) { - modifiers.push(this.limit.join(argsSeparator)); - } - Object.keys(this.filters) - .forEach(key => { - const filter = this.filters[key]; - if (filter) { - const filterArgs = ([key].concat(filter)).join(argsSeparator) - modifiers.push(filterArgs); - } - }) - return modifiers; - } - - reset() { - this.orderBy = null; - this.limit = null; - this.filters = {}; - ['startAt', 'endAt', 'equalTo'] - .forEach(key => this.filters[key] = null); - return this.ref; - } -} - -// https://firebase.google.com/docs/reference/js/firebase.database.Reference -const separator = '/'; -class DatabaseRef extends ReferenceBase { - constructor(db, path) { - super(db.firestack, path); - - this.db = db; - this.query = new DatabaseQuery(this); - this.listeners = {}; - - // Aliases - this.get = this.getAt; - this.set = this.setAt; - this.update = this.updateAt; - this.remove = this.removeAt; - - this.log.debug('Created new DatabaseRef', this.dbPath()); - } - - // Parent roots - parent() { - const parentPaths = this.path.slice(0, -1); - return new DatabaseRef(this.db, parentPaths); - } - - root() { - return new DatabaseRef(this.db, []); - } - - child(...paths) { - return new DatabaseRef(this.db, this.path.concat(paths)); - } - - keepSynced(bool) { - const path = this.dbPath(); - return promisify('keepSynced', FirestackDatabase)(path, bool); - } - - // Get the value of a ref either with a key - getAt() { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, 'value'); - } - - setAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('set', FirestackDatabase)(path, value) - } - - updateAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('update', FirestackDatabase)(path, value) - } - - removeAt(key) { - const path = this.dbPath(); - return promisify('remove', FirestackDatabase)(path) - } - - push(val={}) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('push', FirestackDatabase)(path, value) - .then(({ref}) => { - return new DatabaseRef(this.db, ref.split(separator)) - }) - } - - on(evt, cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return this.db.on(path, evt, cb) - .then(({callback, subscriptions}) => { - return promisify('on', FirestackDatabase)(path, modifiers, evt) - .then(() => { - this.listeners[evt] = subscriptions; - callback(this); - return subscriptions; - }) - }); - } - - once(evt='once', cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, evt) - .then(({snapshot}) => new DataSnapshot(this, snapshot)) - .then(snapshot => { - if (cb && typeof cb === 'function') { - cb(snapshot); - } - return snapshot; - }) - } - - off(evt='', origCB) { - const path = this.dbPath(); - return this.db.off(path, evt, origCB) - .then(({callback, subscriptions}) => { - if (dbSubscriptions[path] && dbSubscriptions[path][evt].length > 0) { - return subscriptions; - } - - return promisify('off', FirestackDatabase)(path, evt) - .then(() => { - // subscriptions.forEach(sub => sub.remove()); - delete this.listeners[evt]; - callback(this); - return subscriptions; - }) - }) - .catch(err => { - console.error('Never get here', err); - }) - } - - cleanup() { - let promises = Object.keys(this.listeners) - .map(key => this.off(key)) - return Promise.all(promises); - } - - // Sanitize value - // As Firebase cannot store date objects. - _serializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.toISOString(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - _deserializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.getTime(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - // Modifiers - orderByKey() { - return this.query.setOrderBy('orderByKey'); - } - - orderByPriority() { - return this.query.setOrderBy('orderByPriority'); - } - - orderByValue() { - return this.query.setOrderBy('orderByValue'); - } - - orderByChild(key) { - return this.query.setOrderBy('orderByChild', key); - } - - // Limits - limitToLast(limit) { - return this.query.setLimit('limitToLast', limit); - } - - limitToFirst(limit) { - return this.query.setLimit('limitToFirst', limit); - } - - // Filters - equalTo(value, key) { - return this.query.setFilter('equalTo', value, key); - } - - endAt(value, key) { - return this.query.setFilter('endAt', value, key); - } - - startAt(value, key) { - return this.query.setFilter('startAt', value, key); - } - - presence(path) { - const presence = this.firestack.presence; - const ref = path ? this.child(path) : this; - return presence.ref(ref, this.dbPath()); - } - - // onDisconnect - onDisconnect() { - return new DatabaseOnDisconnect(this); - } - - // attributes - get fullPath() { - return this.dbPath(); - } - - get name() { - return this.path.splice(-1); - } +import promisify from '../utils/promisify'; +import { Base, ReferenceBase } from './base'; +import DatabaseSnapshot from './databaseSnapshot.js'; +import DatabaseRef from './databaseReference.js'; +import DataSnapshot from './databaseSnapshot.js'; - dbPath() { - let path = this.path; - let pathStr = (path.length > 0 ? path.join('/') : '/'); - if (pathStr[0] != '/') { - pathStr = `/${pathStr}` - } - return pathStr; - } - dbModifiers() { - const modifiers = this.query.build(); - this.query.reset(); // should we reset this - return modifiers; - } - - get namespace() { - return `firestack:dbRef` +function getModifiersString(modifiers) { + if (!modifiers || !Array.isArray(modifiers)) { + return ''; } + return modifiers.join('|'); } export class Database extends Base { - constructor(firestack, options={}) { + constructor(firestack: Object, options: Object={}) { super(firestack, options); this.log.debug('Created new Database instance', this.options); @@ -372,146 +31,193 @@ export class Database extends Base { this.successListener = null; this.errorListener = null; this.refs = {}; + this.dbSubscriptions = {}; // { path: { modifier: { eventType: [Subscriptions] } } } } - ref(...path) { - const key = this._pathKey(path); + ref(...path: Array) { + return new DatabaseRef(this, path); + } + + storeRef(key: string, instance: DatabaseRef): Promise { if (!this.refs[key]) { - const ref = new DatabaseRef(this, path); - this.refs[key] = ref; + this.refs[key] = instance; + } + return Promise.resolve(this.refs[key]); + } + + unstoreRef(key: string): Promise { + if (this.refs[key]) { + delete this.refs[key]; } - return this.refs[key]; + return Promise.resolve(); } - setPersistence(enable=true) { + setPersistence(enable: boolean=true) { let promise; if (this.persistenceEnabled !== enable) { this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`); promise = this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); this.persistenceEnabled = enable; } else { - promise = this.whenReady(Promise.resolve({status: "Already enabled"})) + promise = this.whenReady(Promise.resolve({status: 'Already enabled'})) } return promise; } - handleDatabaseEvent(evt) { - const body = evt.body; + handleDatabaseEvent(evt: Object) { + const body = evt.body || {}; const path = body.path; - const evtName = body.eventName; - - const subscriptions = dbSubscriptions[path]; - - if (subscriptions) { - const cbs = subscriptions[evtName]; - cbs.forEach(cb => { - if (cb && typeof(cb) === 'function') { - const snap = new DataSnapshot(this, body.snapshot); - this.log.debug('database_event received', path, evtName); - cb(snap, body); - } - }); + const modifiersString = body.modifiersString || ''; + const modifier = modifiersString; + const eventName = body.eventName; + this.log.debug('handleDatabaseEvent: ', path, modifiersString, eventName, body.snapshot && body.snapshot.key); + + // subscriptionsMap === { path: { modifier: { eventType: [Subscriptions] } } } + const modifierMap = this.dbSubscriptions[path]; + if (modifierMap) { + const eventTypeMap = modifierMap[modifier]; + if (eventTypeMap) { + const callbacks = eventTypeMap[eventName] || []; + this.log.debug(' -- about to fire its '+callbacks.length+' callbacks'); + callbacks.forEach(cb => { + if (cb && typeof(cb) === 'function') { + const snap = new DataSnapshot(this, body.snapshot); + cb(snap, body); + } + }); + } } } - handleDatabaseError(evt) { + handleDatabaseError(evt: Object) { this.log.debug('handleDatabaseError ->', evt); } - on(path, evt, cb) { + on(referenceKey: string, path: string, modifiers: Array, evt: string, cb: () => void) { + this.log.debug('adding on listener', referenceKey, path, modifiers, evt); const key = this._pathKey(path); + const modifiersString = getModifiersString(modifiers); + const modifier = modifiersString; + + if (!this.dbSubscriptions[key]) { + this.dbSubscriptions[key] = {}; + } - if (!dbSubscriptions[key]) { - dbSubscriptions[key] = {}; + if (!this.dbSubscriptions[key][modifier]) { + this.dbSubscriptions[key][modifier] = {}; } - if (!dbSubscriptions[key][evt]) { - dbSubscriptions[key][evt] = []; + if (!this.dbSubscriptions[key][modifier][evt]) { + this.dbSubscriptions[key][modifier][evt] = []; } - dbSubscriptions[key][evt].push(cb); + + this.dbSubscriptions[key][modifier][evt].push(cb); if (!this.successListener) { this.successListener = FirestackDatabaseEvt .addListener( - 'database_event', + 'database_event', this.handleDatabaseEvent.bind(this)); } if (!this.errorListener) { this.errorListener = FirestackDatabaseEvt .addListener( - 'database_error', + 'database_error', this.handleDatabaseError.bind(this)); } - const callback = (ref) => { - const key = this._pathKey(ref.path); - this.refs[key] = ref; - } - const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); + return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, evt).then(() => { + const subscriptions = [this.successListener, this.errorListener]; + return subscriptions; + }); } - off(path, evt, origCB) { - const key = this._pathKey(path); + off(referenceKey: string, path: string, modifiers: Array, eventName: string, origCB?: () => void) { + const pathKey = this._pathKey(path); + const modifiersString = getModifiersString(modifiers); + const modifier = modifiersString; + this.log.debug('off() : ', referenceKey, pathKey, modifiersString, eventName); // Remove subscription - if (dbSubscriptions[key]) { - if (!evt || evt === "") { - dbSubscriptions[key] = {}; - } else if (dbSubscriptions[key][evt]) { - if (origCB) { - dbSubscriptions[key][evt].splice(dbSubscriptions[key][evt].indexOf(origCB), 1); - } else { - delete dbSubscriptions[key][evt]; + if (this.dbSubscriptions[pathKey]) { + + if (!eventName || eventName === '') { + // remove all listeners for this pathKey + this.dbSubscriptions[pathKey] = {}; + } + + if (this.dbSubscriptions[pathKey][modifier]) { + if (this.dbSubscriptions[pathKey][modifier][eventName]) { + if (origCB) { + // remove only the given callback + this.dbSubscriptions[pathKey][modifier][eventName].splice(this.dbSubscriptions[pathKey][modifier][eventName].indexOf(origCB), 1); + } + else { + // remove all callbacks for this path:modifier:eventType + delete this.dbSubscriptions[pathKey][modifier][eventName]; + } + } + else { + this.log.warn('off() called, but not currently listening at that location (bad eventName)', pathKey, modifiersString, eventName); } } + else { + this.log.warn('off() called, but not currently listening at that location (bad modifier)', pathKey, modifiersString, eventName); + } - if (Object.keys(dbSubscriptions[key]).length <= 0) { - // there are no more subscriptions - // so we can unwatch - delete dbSubscriptions[key] + if (Object.keys(this.dbSubscriptions[pathKey]).length <= 0) { + // there are no more subscriptions so we can unwatch + delete this.dbSubscriptions[pathKey]; } - if (Object.keys(dbSubscriptions).length == 0) { + if (Object.keys(this.dbSubscriptions).length === 0) { if (this.successListener) { this.successListener.remove(); this.successListener = null; } if (this.errorListener) { this.errorListener.remove(); - this.errorListener = null; + this.errorListener = null; } } } - const callback = (ref) => { - const key = this._pathKey(ref.path); - delete this.refs[key]; + else { + this.log.warn('off() called, but not currently listening at that location (bad path)', pathKey, modifiersString, eventName); } + const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); + + let modifierMap = this.dbSubscriptions[path]; + if (modifierMap && modifierMap[modifier] && modifierMap[modifier][eventName] && modifierMap[modifier][eventName].length > 0) { + return Promise.resolve(subscriptions); + } + + return promisify('off', FirestackDatabase)(path, modifiersString, eventName).then(() => { + // subscriptions.forEach(sub => sub.remove()); + // delete this.listeners[eventName]; + return subscriptions; + }); } cleanup() { let promises = Object.keys(this.refs) .map(key => this.refs[key]) - .map(ref => ref.cleanup()) + .map(ref => ref.cleanup()); return Promise.all(promises); } - release(...path) { - const key = this._pathKey(path); + release(...path: Array) { + const key = this._pathKey(...path); if (this.refs[key]) { delete this.refs[key]; } } - _pathKey(...path) { + _pathKey(...path: Array): string { return path.join('-'); } - get namespace() { - return 'firestack:database' + get namespace(): string { + return 'firestack:database'; } } - -export default Database diff --git a/lib/modules/databaseQuery.js b/lib/modules/databaseQuery.js new file mode 100644 index 0000000..e610ead --- /dev/null +++ b/lib/modules/databaseQuery.js @@ -0,0 +1,98 @@ +/** + * @flow + */ +'use strict'; + +import { ReferenceBase } from './base'; +import DatabaseRef from './databaseReference.js'; + +let uid = 1000000; +// class DatabaseQuery extends DatabaseRef { +class DatabaseQuery extends ReferenceBase { + static ref: DatabaseRef; + + static orderBy: Array; + static limit: Array; + static filters: Object;// { [key: string]: Array }; + + ref: DatabaseRef; + + constructor(ref: DatabaseRef, path: Array, existingModifiers?: { [key: string]: string }) { + super(ref.db, path); + this.log.debug('creating Query ', path, existingModifiers); + this.uid = uid++; // uuid.v4(); + this.ref = ref; + this.orderBy = undefined; + this.limit = undefined; + this.filters = {}; + + // parse exsitingModifiers + if (existingModifiers) { + this.import(existingModifiers); + } + } + + export(): { [key: string]: string } { + const argsSeparator = ':'; + const ret = {}; + if (this.orderBy) { + ret.orderBy = this.orderBy.join(argsSeparator); + } + if (this.limit) { + ret.limit = this.limit.join(argsSeparator); + } + if (this.filters && Object.keys(this.filters).length > 0) { + let filters = Object.keys(this.filters).map(key => { + const filter = this.filters[key]; + if (filter) { + return [key, filter].join(argsSeparator); + } + return; + }).filter(Boolean); + if (filters.length > 0) { + ret.filters = filters.join('|'); + } + } + return ret; + } + + import(modifiers: { [key: string]: string }) { + const argsSeparator = ':'; + if (modifiers.orderBy) { + this.setOrderBy(...modifiers.orderBy.split(argsSeparator)); + } + + if (modifiers.limit) { + const [name, value] = modifiers.limit.split(argsSeparator); + this.setLimit(name, parseInt(value, 10)); + } + + if (modifiers.filters) { + modifiers.filters.split('|').forEach(filter => { + this.setFilter(...filter.split(argsSeparator)); + }); + } + } + + setOrderBy(name: string, ...args: Array) { + this.orderBy = [name].concat(args); + } + + setLimit(name: string, limit: number) { + this.limit = [name, limit]; + } + + setFilter(name: string, ...args: Array) { + let vals = args.filter(str => !!str); + if (vals.length > 0) { + this.filters[name] = vals; + } + } + + build() { + let exportObj = this.export(); + return Object.keys(exportObj).map(exportKey => exportObj[exportKey]); + } +} + +export default DatabaseQuery; diff --git a/lib/modules/databaseReference.js b/lib/modules/databaseReference.js new file mode 100644 index 0000000..6381c3c --- /dev/null +++ b/lib/modules/databaseReference.js @@ -0,0 +1,305 @@ +/** + * @flow + */ +'use strict'; +import {NativeModules} from 'react-native'; +const FirestackDatabase = NativeModules.FirestackDatabase; + +import promisify from '../utils/promisify'; +import { Base, ReferenceBase } from './base'; +import DataSnapshot from './databaseSnapshot.js'; +import DatabaseQuery from './databaseQuery.js'; + +function getModifiersString(modifiers) { + if (!modifiers || !Array.isArray(modifiers)) { + return ''; + } + return modifiers.join('|'); +} + +// https://firebase.google.com/docs/reference/js/firebase.database.Reference +let uid = 0; +class DatabaseRef extends ReferenceBase { + + db: FirestackDatabase; + query: DatabaseQuery; + uid: number; + + constructor(db: FirestackDatabase, path: Array, existingModifiers?: { [key: string]: string }) { + super(db.firestack, path); + + this.db = db; + this.query = new DatabaseQuery(this, path, existingModifiers); + this.uid = uid++; // uuid.v4(); + this.listeners = {}; + + // Aliases + this.get = this.getAt; + this.set = this.setAt; + this.update = this.updateAt; + this.remove = this.removeAt; + + this.log.debug('Created new DatabaseRef', this.dbPath(), this.uid); + } + + // Parent roots + parent() { + const parentPaths = this.path.slice(0, -1); + return new DatabaseRef(this.db, parentPaths); + } + + root() { + return new DatabaseRef(this.db, []); + } + + child(...paths: Array) { + return new DatabaseRef(this.db, this.path.concat(paths)); + } + + keepSynced(bool: boolean) { + const path = this.dbPath(); + return promisify('keepSynced', FirestackDatabase)(path, bool); + } + + // Get the value of a ref either with a key + getAt() { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + const modifiersString = getModifiersString(modifiers); + return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, 'value'); + } + + setAt(val: any) { + const path = this.dbPath(); + const value = this._serializeValue(val); + return promisify('set', FirestackDatabase)(path, value); + } + + updateAt(val: any) { + const path = this.dbPath(); + const value = this._serializeValue(val); + return promisify('update', FirestackDatabase)(path, value); + } + + removeAt(key: string) { + const path = this.dbPath(); + return promisify('remove', FirestackDatabase)(path); + } + + push(val: any={}) { + const path = this.dbPath(); + const value = this._serializeValue(val); + return promisify('push', FirestackDatabase)(path, value) + .then(({ref}) => { + const separator = '/'; + return new DatabaseRef(this.db, ref.split(separator)); + }); + } + + on(evt?: string, cb: () => any) { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + const modifiersString = getModifiersString(modifiers); + this.log.debug('adding reference.on', path, modifiersString, evt); + return this.db.storeRef(this.uid, this).then(() => { + return this.db.on(this.uid, path, modifiers, evt, cb).then(subscriptions => { + this.listeners[evt] = subscriptions; + }); + }); + } + + once(evt?: string='once', cb: (snapshot: Object) => void) { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + const modifiersString = getModifiersString(modifiers); + return this.db.storeRef(this.uid, this).then(() => { + return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, evt) + .then(({snapshot}) => new DataSnapshot(this, snapshot)) + .then(snapshot => { + if (cb && typeof cb === 'function') { + cb(snapshot); + } + return snapshot; + }); + }); + } + + off(evt: string='', origCB?: () => any) { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + this.log.debug('ref.off(): ', path, modifiers, evt); + return this.db.unstoreRef(this.uid).then(() => { + return this.db.off(this.uid, path, modifiers, evt, origCB).then(subscriptions => { + // delete this.listeners[eventName]; + // this.listeners[evt] = subscriptions; + }); + }); + } + + cleanup() { + let promises = Object.keys(this.listeners) + .map(key => this.off(key)); + return Promise.all(promises); + } + + // Sanitize value + // As Firebase cannot store date objects. + _serializeValue(obj: Object={}) { + return Object.keys(obj).reduce((sum, key) => { + let val = obj[key]; + if (val instanceof Date) { + val = val.toISOString(); + } + return { + ...sum, + [key]: val, + }; + }, {}); + } + + _deserializeValue(obj: Object={}) { + return Object.keys(obj).reduce((sum, key) => { + let val = obj[key]; + if (val instanceof Date) { + val = val.getTime(); + } + return { + ...sum, + [key]: val, + }; + }, {}); + } + + // class Query extends DatabaseRef {} + + // let ref = firestack.database().ref('/timeline'); + // ref.limitToLast(1).on('child_added', () => {}); + // ref.limitToFirst(1).on('child_added', () => {}); + // ref.on('child_added', () => {}) + + // Modifiers + orderByKey(): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByKey'); + return newRef; + } + + orderByPriority(): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByPriority'); + return newRef; + } + + orderByValue(): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByValue'); + return newRef; + } + + orderByChild(key: string): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByChild', key); + return newRef; + } + + // Limits + limitToLast(limit: number): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setLimit('limitToLast', limit); + return newRef; + } + + limitToFirst(limit: number): DatabaseRef { + // return this.query.setLimit('limitToFirst', limit); + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setLimit('limitToFirst', limit); + return newRef; + } + + // Filters + equalTo(value: any, key?: string): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setFilter('equalTo', value, key); + return newRef; + } + + endAt(value: any, key?: string): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setFilter('endAt', value, key); + return newRef; + } + + startAt(value: any, key?: string): DatabaseRef { + let newRef = new DatabaseRef(this.db, this.path, this.query.export()); + newRef.query.setFilter('startAt', value, key); + return newRef; + } + + presence(path: string) { + const presence = this.firestack.presence; + const ref = path ? this.child(path) : this; + return presence.ref(ref, this.dbPath()); + } + + // onDisconnect + onDisconnect() { + return new DatabaseOnDisconnect(this); + } + + // attributes + get fullPath(): string { + return this.dbPath(); + } + + get name(): string { + return this.path.splice(-1); + } + + dbPath(): string { + let path = this.path; + let pathStr = (path.length > 0 ? path.join('/') : '/'); + if (pathStr[0] !== '/') { + pathStr = `/${pathStr}`; + } + return pathStr; + } + + dbModifiers(): Array { + const modifiers = this.query.build(); + return modifiers; + } + + get namespace(): string { + return 'firestack:dbRef'; + } +} + +export default DatabaseRef; + +class DatabaseOnDisconnect { + ref: DatabaseRef; + + constructor(ref: DatabaseRef) { + this.ref = ref; + } + + setValue(val: string | Object) { + const path = this.ref.dbPath(); + if (typeof val === 'string') { + return promisify('onDisconnectSetString', FirestackDatabase)(path, val); + } else if (typeof val === 'object') { + return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); + } + } + + remove() { + const path = this.ref.dbPath(); + return promisify('onDisconnectRemove', FirestackDatabase)(path); + } + + cancel() { + const path = this.ref.dbPath(); + return promisify('onDisconnectCancel', FirestackDatabase)(path); + } +} + diff --git a/lib/modules/databaseSnapshot.js b/lib/modules/databaseSnapshot.js new file mode 100644 index 0000000..996c798 --- /dev/null +++ b/lib/modules/databaseSnapshot.js @@ -0,0 +1,57 @@ +/** + * @flow + */ +'use strict'; + +import promisify from '../utils/promisify'; +import { Base, ReferenceBase } from './base'; +import DatabaseRef from './databaseReference.js'; + +class DataSnapshot { + static key:String; + static value:Object; + static exists:boolean; + static hasChildren:boolean; + static childrenCount:Number; + static childKeys:String[]; + + ref: Object; + key: string; + value: any; + exists: boolean; + priority: any; + hasChildren: boolean; + childrenCount: number + childKeys: Array; + + constructor(ref: DatabaseRef, snapshot: Object) { + this.ref = ref; + this.key = snapshot.key; + this.value = snapshot.value; + this.exists = snapshot.exists || true; + this.priority = snapshot.priority; + this.hasChildren = snapshot.hasChildren || false; + this.childrenCount = snapshot.childrenCount || 0; + this.childKeys = snapshot.childKeys || []; + } + + val() { + return this.value; + } + + forEach(fn: (key: any) => any) { + (this.childKeys || []) + .forEach(key => fn(this.value[key])); + } + + map(fn: (key: string) => mixed) { + let arr = []; + this.forEach(item => arr.push(fn(item))); + return arr; + } + + reverseMap(fn: (key: string) => mixed) { + return this.map(fn).reverse(); + } +} +export default DataSnapshot; diff --git a/lib/utils/window-or-global.js b/lib/utils/window-or-global.js index 7b64020..c19a003 100644 --- a/lib/utils/window-or-global.js +++ b/lib/utils/window-or-global.js @@ -2,4 +2,4 @@ // https://github.com/purposeindustries/window-or-global module.exports = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || - {} \ No newline at end of file + this diff --git a/package.json b/package.json index e611094..8386807 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start", "build": "./node_modules/.bin/babel --source-maps=true --out-dir=dist .", + "build-lib": "./node_modules/.bin/babel --source-maps=true --out-dir=dist lib", "dev": "npm run compile -- --watch", "lint": "eslint ./src", "publish_pages": "gh-pages -d public/", @@ -51,7 +52,9 @@ } }, "devDependencies": { + "babel-cli": "^6.18.0", "babel-jest": "^14.1.0", + "babel-plugin-transform-flow-strip-types": "^6.18.0", "babel-preset-react-native": "^1.9.0", "debug": "^2.2.0", "enzyme": "^2.4.1",