From 6fda002018b70be16bf54f3aa6110466ec389310 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Wed, 9 Nov 2016 11:26:11 -0500 Subject: [PATCH 1/3] Fix listeners with modifiers reusing handles with the same path but no modifiers Previously: db.ref('/messages').on('child_added') db.ref('/messages').limitToFirst(1).on('child_added') db.ref('/messages').limitToLast(1).on('child_added') would add native listeners for the appropiarate queries, but when ANY of the three fired a child_added, than ALL three would receive it e.g. the limitToFirst child_added would get called when the *last* element changed We now keep track of the listeners by path as well as modifierString, and to a lesser extent, by eventType --- .../firestack/FirestackDatabase.java | 208 +++++++----------- .../fullstack/firestack/FirestackUtils.java | 4 +- ios/Firestack/FirestackDatabase.m | 36 ++- lib/modules/database.js | 157 +++++++------ 4 files changed, 202 insertions(+), 203 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 61cda5d..9acdcc6 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); @@ -429,22 +433,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); @@ -452,12 +457,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); } /** @@ -467,9 +473,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"); @@ -569,24 +579,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); } } @@ -600,73 +617,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/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..be9ca80 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]; @@ -425,10 +429,11 @@ @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]; @@ -537,10 +542,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,10 +556,18 @@ - (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]; @@ -656,4 +668,4 @@ - (void) sendJSEvent:(NSString *)type } } -@end \ No newline at end of file +@end diff --git a/lib/modules/database.js b/lib/modules/database.js index f7f91fe..3092f8d 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -8,6 +8,7 @@ const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); import promisify from '../utils/promisify' import { Base, ReferenceBase } from './base' +// subscriptionsMap === { path: { modifier: { eventType: [Subscriptions] } } } let dbSubscriptions = {}; class DataSnapshot { @@ -172,7 +173,8 @@ class DatabaseRef extends ReferenceBase { getAt() { const path = this.dbPath(); const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, 'value'); + const modifiersString = getModifiersString(modifiers); + return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, 'value'); } setAt(val) { @@ -204,9 +206,10 @@ class DatabaseRef extends ReferenceBase { on(evt, cb) { const path = this.dbPath(); const modifiers = this.dbModifiers(); - return this.db.on(path, evt, cb) + const modifiersString = getModifiersString(modifiers); + return this.db.on(path, modifiers, evt, cb) .then(({callback, subscriptions}) => { - return promisify('on', FirestackDatabase)(path, modifiers, evt) + return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, evt) .then(() => { this.listeners[evt] = subscriptions; callback(this); @@ -218,7 +221,8 @@ class DatabaseRef extends ReferenceBase { once(evt='once', cb) { const path = this.dbPath(); const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, evt) + const modifiersString = getModifiersString(modifiers); + return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, evt) .then(({snapshot}) => new DataSnapshot(this, snapshot)) .then(snapshot => { if (cb && typeof cb === 'function') { @@ -230,23 +234,8 @@ class DatabaseRef extends ReferenceBase { 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); - }) + const modifiers = this.dbModifiers(); + return this.db.off(path, modifiers, evt, origCB) } cleanup() { @@ -256,7 +245,7 @@ class DatabaseRef extends ReferenceBase { } // Sanitize value - // As Firebase cannot store date objects. + // As Firebase cannot store date objects. _serializeValue(obj={}) { return Object.keys(obj).reduce((sum, key) => { let val = obj[key]; @@ -362,6 +351,13 @@ class DatabaseRef extends ReferenceBase { } } +function getModifiersString(modifiers) { + if (!modifiers || !Array.isArray(modifiers)) { + return ''; + } + return modifiers.join('|'); +} + export class Database extends Base { constructor(firestack, options={}) { @@ -397,21 +393,26 @@ export class Database extends Base { } handleDatabaseEvent(evt) { - const body = evt.body; + 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; + + // subscriptionsMap === { path: { modifier: { eventType: [Subscriptions] } } } + const modifierMap = dbSubscriptions[path]; + if (modifierMap) { + const eventTypeMap = modifierMap[modifier]; + if (eventTypeMap) { + const callbacks = eventTypeMap[eventName] || []; + callbacks.forEach(cb => { + if (cb && typeof(cb) === 'function') { + const snap = new DataSnapshot(this, body.snapshot); + this.log.debug('database_event received', path, eventName); + cb(snap, body); + } + }); + } } } @@ -419,29 +420,36 @@ export class Database extends Base { this.log.debug('handleDatabaseError ->', evt); } - on(path, evt, cb) { + on(path, modifiers, evt, cb) { const key = this._pathKey(path); + const modifiersString = getModifiersString(modifiers); + const modifier = modifiersString; if (!dbSubscriptions[key]) { dbSubscriptions[key] = {}; } - if (!dbSubscriptions[key][evt]) { - dbSubscriptions[key][evt] = []; + if (!dbSubscriptions[key][modifier]) { + dbSubscriptions[key][modifier] = {}; + } + + if (!dbSubscriptions[key][modifier][evt]) { + dbSubscriptions[key][modifier][evt] = []; } - dbSubscriptions[key][evt].push(cb); + + 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)); } @@ -453,48 +461,71 @@ export class Database extends Base { return Promise.resolve({callback, subscriptions}); } - off(path, evt, origCB) { - const key = this._pathKey(path); + off(path, modifiers, eventName, origCB) { + const pathKey = this._pathKey(path); + const modifiersString = getModifiersString(modifiers); + const modifier = modifiersString; // 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 (dbSubscriptions[pathKey]) { + + if (!eventName || eventName === "") { + // remove all listeners for this pathKey + dbSubscriptions[pathKey] = {}; + } + + if (dbSubscriptions[pathKey][modifier]) { + if (dbSubscriptions[pathKey][modifier][eventName]) { + if (origCB) { + // remove only the given callback + dbSubscriptions[pathKey][modifier][eventName].splice(dbSubscriptions[pathKey][modifier][eventName].indexOf(origCB), 1); + } + else { + // remove all callbacks for this path:modifier:eventType + delete dbSubscriptions[pathKey][modifier][eventName]; + } } } - if (Object.keys(dbSubscriptions[key]).length <= 0) { - // there are no more subscriptions - // so we can unwatch - delete dbSubscriptions[key] + if (Object.keys(dbSubscriptions[pathKey]).length <= 0) { + // there are no more subscriptions so we can unwatch + delete dbSubscriptions[pathKey]; } - if (Object.keys(dbSubscriptions).length == 0) { + if (Object.keys(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]; - } + }; + const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); + + let modifierMap = 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]; + callback(this); + 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); } @@ -510,8 +541,8 @@ export class Database extends Base { } get namespace() { - return 'firestack:database' + return 'firestack:database'; } } -export default Database \ No newline at end of file +export default Database From c051fdb33d47429f23a20b5ac720bdb187b6a9eb Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Wed, 9 Nov 2016 11:22:49 -0500 Subject: [PATCH 2/3] Lint database.js --- lib/modules/database.js | 63 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index 3092f8d..40eb366 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -5,11 +5,10 @@ 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' +import promisify from '../utils/promisify'; +import { Base, ReferenceBase } from './base'; -// subscriptionsMap === { path: { modifier: { eventType: [Subscriptions] } } } -let dbSubscriptions = {}; +let dbSubscriptions = {}; // { path: { modifier: { eventType: [Subscriptions] } } } class DataSnapshot { static key:String; @@ -36,12 +35,12 @@ class DataSnapshot { forEach(fn) { (this.childKeys || []) - .forEach(key => fn(this.value[key])) + .forEach(key => fn(this.value[key])); } map(fn) { let arr = []; - this.forEach(item => arr.push(fn(item))) + this.forEach(item => arr.push(fn(item))); return arr; } @@ -57,9 +56,9 @@ class DatabaseOnDisconnect { setValue(val) { const path = this.ref.dbPath(); - if (typeof val == 'string') { + if (typeof val === 'string') { return promisify('onDisconnectSetString', FirestackDatabase)(path, val); - } else if (typeof val == 'object') { + } else if (typeof val === 'object') { return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); } } @@ -102,7 +101,7 @@ class DatabaseQuery { } build() { - const argsSeparator = ':' + const argsSeparator = ':'; let modifiers = []; if (this.orderBy) { modifiers.push(this.orderBy.join(argsSeparator)); @@ -114,10 +113,10 @@ class DatabaseQuery { .forEach(key => { const filter = this.filters[key]; if (filter) { - const filterArgs = [key, filter].join(argsSeparator) + const filterArgs = [key, filter].join(argsSeparator); modifiers.push(filterArgs); } - }) + }); return modifiers; } @@ -180,18 +179,18 @@ class DatabaseRef extends ReferenceBase { setAt(val) { const path = this.dbPath(); const value = this._serializeValue(val); - return promisify('set', FirestackDatabase)(path, value) + return promisify('set', FirestackDatabase)(path, value); } updateAt(val) { const path = this.dbPath(); const value = this._serializeValue(val); - return promisify('update', FirestackDatabase)(path, value) + return promisify('update', FirestackDatabase)(path, value); } removeAt(key) { const path = this.dbPath(); - return promisify('remove', FirestackDatabase)(path) + return promisify('remove', FirestackDatabase)(path); } push(val={}) { @@ -199,8 +198,8 @@ class DatabaseRef extends ReferenceBase { const value = this._serializeValue(val); return promisify('push', FirestackDatabase)(path, value) .then(({ref}) => { - return new DatabaseRef(this.db, ref.split(separator)) - }) + return new DatabaseRef(this.db, ref.split(separator)); + }); } on(evt, cb) { @@ -214,8 +213,8 @@ class DatabaseRef extends ReferenceBase { this.listeners[evt] = subscriptions; callback(this); return subscriptions; - }) - }); + }); + }); } once(evt='once', cb) { @@ -229,18 +228,18 @@ class DatabaseRef extends ReferenceBase { cb(snapshot); } return snapshot; - }) + }); } off(evt='', origCB) { const path = this.dbPath(); const modifiers = this.dbModifiers(); - return this.db.off(path, modifiers, evt, origCB) + return this.db.off(path, modifiers, evt, origCB); } cleanup() { let promises = Object.keys(this.listeners) - .map(key => this.off(key)) + .map(key => this.off(key)); return Promise.all(promises); } @@ -254,8 +253,8 @@ class DatabaseRef extends ReferenceBase { } return { ...sum, - [key]: val - } + [key]: val, + }; }, {}); } @@ -267,8 +266,8 @@ class DatabaseRef extends ReferenceBase { } return { ...sum, - [key]: val - } + [key]: val, + }; }, {}); } @@ -334,8 +333,8 @@ class DatabaseRef extends ReferenceBase { dbPath() { let path = this.path; let pathStr = (path.length > 0 ? path.join('/') : '/'); - if (pathStr[0] != '/') { - pathStr = `/${pathStr}` + if (pathStr[0] !== '/') { + pathStr = `/${pathStr}`; } return pathStr; } @@ -347,7 +346,7 @@ class DatabaseRef extends ReferenceBase { } get namespace() { - return `firestack:dbRef` + return 'firestack:dbRef'; } } @@ -386,7 +385,7 @@ export class Database extends Base { 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; @@ -456,7 +455,7 @@ export class Database extends Base { const callback = (ref) => { const key = this._pathKey(ref.path); this.refs[key] = ref; - } + }; const subscriptions = [this.successListener, this.errorListener]; return Promise.resolve({callback, subscriptions}); } @@ -468,7 +467,7 @@ export class Database extends Base { // Remove subscription if (dbSubscriptions[pathKey]) { - if (!eventName || eventName === "") { + if (!eventName || eventName === '') { // remove all listeners for this pathKey dbSubscriptions[pathKey] = {}; } @@ -545,4 +544,4 @@ export class Database extends Base { } } -export default Database +export default Database; From b34097b61be9b929f8435959f31a83a564885376 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Wed, 9 Nov 2016 14:17:34 -0500 Subject: [PATCH 3/3] Fix Listener w/ modifiers for ios --- ios/Firestack/FirestackDatabase.m | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index be9ca80..baf52da 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -416,6 +416,7 @@ @implementation FirestackDatabase callback(@[[NSNull null], @{ @"eventName": name, @"path": path, + @"modifiersString": modifiersString, @"snapshot": props }]); } @@ -436,11 +437,11 @@ @implementation FirestackDatabase 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]; } } @@ -449,6 +450,7 @@ @implementation FirestackDatabase callback(@[[NSNull null], @{ @"result": @"success", @"path": path, + @"modifiersString": modifiersString, @"remainingListeners": [r listenerKeys], }]); } @@ -559,7 +561,7 @@ - (NSDictionary *) storedDBHandles - (NSString *) getDBListenerKey:(NSString *) path withModifiers:(NSString *) modifiersString { - return [NSString stringWithFormat:@"@% | %@", path, modifiersString, nil]; + return [NSString stringWithFormat:@"%@ | %@", path, modifiersString, nil]; } - (FirestackDBReference *) getDBHandle:(NSString *) path @@ -571,29 +573,33 @@ - (FirestackDBReference *) getDBHandle:(NSString *) path 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]; }