diff --git a/packages/database/lib/DatabaseSyncTree.js b/packages/database/lib/DatabaseSyncTree.js index 59d4653aec..f53065e6f1 100644 --- a/packages/database/lib/DatabaseSyncTree.js +++ b/packages/database/lib/DatabaseSyncTree.js @@ -26,6 +26,15 @@ class DatabaseSyncTree { this._tree = {}; this._reverseLookup = {}; + // we need to be able to register multiple listeners for a single event, + // *then delete a specific single listener for that event*, so we have to + // be able to identify specific listeners for removal which means we need + // to mirror the private registration accounting from upstream EventEmitter + // so we have access here and can do our single emitter removal + // This is a map of emitter-key <-> set of listener registrations + // The listener registrations have { context, listener, remove() } + this._registry = {}; + SharedEventEmitter.addListener('database_sync_event', this._handleSyncEvent.bind(this)); } @@ -33,6 +42,16 @@ class DatabaseSyncTree { return NativeModules.RNFBDatabaseQueryModule; } + // from upstream EventEmitter: initialize registrations for an emitter key + _allocate(registry, eventType) { + let registrations = registry[eventType]; + if (registrations == null) { + registrations = new Set(); + registry[eventType] = registrations; + } + return registrations; + } + /** * Handles an incoming event from native * @param event @@ -132,6 +151,13 @@ class DatabaseSyncTree { if (isString(registrations)) { this.removeRegistration(registrations); SharedEventEmitter.removeAllListeners(registrations); + + // mirror upstream accounting - clear out all registrations for this key + if (registrations == null) { + this._registry = {}; + } else { + delete this._registry[registrations]; + } return 1; } @@ -141,6 +167,12 @@ class DatabaseSyncTree { for (let i = 0, len = registrations.length; i < len; i++) { this.removeRegistration(registrations[i]); SharedEventEmitter.removeAllListeners(registrations[i]); + // mirror upstream accounting - clear out all registrations for this key + if (registrations[i] == null) { + this._registry = {}; + } else { + delete this._registry[registrations[i]]; + } } return registrations.length; @@ -163,15 +195,10 @@ class DatabaseSyncTree { const registration = registrations[i]; let subscriptions; - // EventEmitter in react-native < 0.70 had a `_subscriber` property with a method for subscriptions by type... - if (SharedEventEmitter._subscriber) { - subscriptions = SharedEventEmitter._subscriber.getSubscriptionsForType(registration); - } else { - // ...react-native 0.70 now stores subscribers as a map of Sets by type in `_registry` - const registrySubscriptionsSet = SharedEventEmitter._registry[registration]; - if (registrySubscriptionsSet) { - subscriptions = Array.from(registrySubscriptionsSet); - } + // get all registrations for this key so we can find our specific listener + const registrySubscriptionsSet = this._registry[registration]; + if (registrySubscriptionsSet) { + subscriptions = Array.from(registrySubscriptionsSet); } if (subscriptions) { @@ -180,6 +207,7 @@ class DatabaseSyncTree { // The subscription may have been removed during this event loop. // its listener matches the listener in method parameters if (subscription && subscription.listener === listener) { + this._registry[registration].delete(subscription); subscription.remove(); removed.push(registration); this.removeRegistration(registration); @@ -278,7 +306,12 @@ class DatabaseSyncTree { subscription.remove(); }); } else { - SharedEventEmitter.addListener(eventRegistrationKey, listener); + const registration = SharedEventEmitter.addListener(eventRegistrationKey, listener); + + // add this listener registration info to our emitter-key map + // in case we need to identify and remove a specific listener later + const registrations = this._allocate(this._registry, eventRegistrationKey); + registrations.add(registration); } return eventRegistrationKey;