From 760c458e024d10d88b6e2cc6640be6972ba209d9 Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 7 Sep 2017 01:54:33 +0100 Subject: [PATCH] RN 48 EE backwards compatible fix --- lib/modules/base.js | 2 +- lib/utils/emitter/EmitterSubscription.js | 61 +++++ lib/utils/emitter/EventEmitter.js | 220 +++++++++++++++++++ lib/utils/emitter/EventSubscription.js | 42 ++++ lib/utils/emitter/EventSubscriptionVendor.js | 100 +++++++++ package.json | 5 +- 6 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 lib/utils/emitter/EmitterSubscription.js create mode 100644 lib/utils/emitter/EventEmitter.js create mode 100644 lib/utils/emitter/EventSubscription.js create mode 100644 lib/utils/emitter/EventSubscriptionVendor.js diff --git a/lib/modules/base.js b/lib/modules/base.js index 1565645b..9bb51d48 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -2,8 +2,8 @@ * @flow */ -import EventEmitter from 'react-native/Libraries/EventEmitter/EventEmitter'; import Log from '../utils/log'; +import EventEmitter from './../utils/emitter/EventEmitter'; const logs = {}; const SharedEventEmitter = new EventEmitter(); diff --git a/lib/utils/emitter/EmitterSubscription.js b/lib/utils/emitter/EmitterSubscription.js new file mode 100644 index 00000000..5d213b3d --- /dev/null +++ b/lib/utils/emitter/EmitterSubscription.js @@ -0,0 +1,61 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @noflow + */ +'use strict'; + +const EventSubscription = require('./EventSubscription'); + +import type EventEmitter from './EventEmitter'; +import type EventSubscriptionVendor from './EventSubscriptionVendor'; + +/** + * EmitterSubscription represents a subscription with listener and context data. + */ +class EmitterSubscription extends EventSubscription { + + emitter: EventEmitter; + listener: Function; + context: ?Object; + + /** + * @param {EventEmitter} emitter - The event emitter that registered this + * subscription + * @param {EventSubscriptionVendor} subscriber - The subscriber that controls + * this subscription + * @param {function} listener - Function to invoke when the specified event is + * emitted + * @param {*} context - Optional context object to use when invoking the + * listener + */ + constructor( + emitter: EventEmitter, + subscriber: EventSubscriptionVendor, + listener: Function, + context: ?Object + ) { + super(subscriber); + this.emitter = emitter; + this.listener = listener; + this.context = context; + } + + /** + * Removes this subscription from the emitter that registered it. + * Note: we're overriding the `remove()` method of EventSubscription here + * but deliberately not calling `super.remove()` as the responsibility + * for removing the subscription lies with the EventEmitter. + */ + remove() { + this.emitter.removeSubscription(this); + } +} + +module.exports = EmitterSubscription; diff --git a/lib/utils/emitter/EventEmitter.js b/lib/utils/emitter/EventEmitter.js new file mode 100644 index 00000000..e4d5eb89 --- /dev/null +++ b/lib/utils/emitter/EventEmitter.js @@ -0,0 +1,220 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @noflow + * @typecheck + */ +'use strict'; + +const EmitterSubscription = require('./EmitterSubscription'); +const EventSubscriptionVendor = require('./EventSubscriptionVendor'); + +const emptyFunction = require('fbjs/lib/emptyFunction'); +const invariant = require('fbjs/lib/invariant'); + +/** + * @class EventEmitter + * @description + * An EventEmitter is responsible for managing a set of listeners and publishing + * events to them when it is told that such events happened. In addition to the + * data for the given event it also sends a event control object which allows + * the listeners/handlers to prevent the default behavior of the given event. + * + * The emitter is designed to be generic enough to support all the different + * contexts in which one might want to emit events. It is a simple multicast + * mechanism on top of which extra functionality can be composed. For example, a + * more advanced emitter may use an EventHolder and EventFactory. + */ +class EventEmitter { + + _subscriber: EventSubscriptionVendor; + _currentSubscription: ?EmitterSubscription; + + /** + * @constructor + * + * @param {EventSubscriptionVendor} subscriber - Optional subscriber instance + * to use. If omitted, a new subscriber will be created for the emitter. + */ + constructor(subscriber: ?EventSubscriptionVendor) { + this._subscriber = subscriber || new EventSubscriptionVendor(); + } + + /** + * Adds a listener to be invoked when events of the specified type are + * emitted. An optional calling context may be provided. The data arguments + * emitted will be passed to the listener function. + * + * TODO: Annotate the listener arg's type. This is tricky because listeners + * can be invoked with varargs. + * + * @param {string} eventType - Name of the event to listen to + * @param {function} listener - Function to invoke when the specified event is + * emitted + * @param {*} context - Optional context object to use when invoking the + * listener + */ + addListener( + eventType: string, listener: Function, context: ?Object): EmitterSubscription { + + return (this._subscriber.addSubscription( + eventType, + new EmitterSubscription(this, this._subscriber, listener, context) + ) : any); + } + + /** + * Similar to addListener, except that the listener is removed after it is + * invoked once. + * + * @param {string} eventType - Name of the event to listen to + * @param {function} listener - Function to invoke only once when the + * specified event is emitted + * @param {*} context - Optional context object to use when invoking the + * listener + */ + once(eventType: string, listener: Function, context: ?Object): EmitterSubscription { + return this.addListener(eventType, (...args) => { + this.removeCurrentListener(); + listener.apply(context, args); + }); + } + + /** + * Removes all of the registered listeners, including those registered as + * listener maps. + * + * @param {?string} eventType - Optional name of the event whose registered + * listeners to remove + */ + removeAllListeners(eventType: ?string) { + this._subscriber.removeAllSubscriptions(eventType); + } + + /** + * Provides an API that can be called during an eventing cycle to remove the + * last listener that was invoked. This allows a developer to provide an event + * object that can remove the listener (or listener map) during the + * invocation. + * + * If it is called when not inside of an emitting cycle it will throw. + * + * @throws {Error} When called not during an eventing cycle + * + * @example + * var subscription = emitter.addListenerMap({ + * someEvent: function(data, event) { + * console.log(data); + * emitter.removeCurrentListener(); + * } + * }); + * + * emitter.emit('someEvent', 'abc'); // logs 'abc' + * emitter.emit('someEvent', 'def'); // does not log anything + */ + removeCurrentListener() { + invariant( + !!this._currentSubscription, + 'Not in an emitting cycle; there is no current subscription' + ); + this.removeSubscription(this._currentSubscription); + } + + /** + * Removes a specific subscription. Called by the `remove()` method of the + * subscription itself to ensure any necessary cleanup is performed. + */ + removeSubscription(subscription: EmitterSubscription) { + invariant( + subscription.emitter === this, + 'Subscription does not belong to this emitter.' + ); + this._subscriber.removeSubscription(subscription); + } + + /** + * Returns an array of listeners that are currently registered for the given + * event. + * + * @param {string} eventType - Name of the event to query + * @returns {array} + */ + listeners(eventType: string): [EmitterSubscription] { + const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + return subscriptions + ? subscriptions.filter(emptyFunction.thatReturnsTrue).map( + function(subscription) { + return subscription.listener; + }) + : []; + } + + /** + * Emits an event of the given type with the given data. All handlers of that + * particular type will be notified. + * + * @param {string} eventType - Name of the event to emit + * @param {...*} Arbitrary arguments to be passed to each registered listener + * + * @example + * emitter.addListener('someEvent', function(message) { + * console.log(message); + * }); + * + * emitter.emit('someEvent', 'abc'); // logs 'abc' + */ + emit(eventType: string) { + const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + if (subscriptions) { + for (let i = 0, l = subscriptions.length; i < l; i++) { + const subscription = subscriptions[i]; + + // The subscription may have been removed during this event loop. + if (subscription) { + this._currentSubscription = subscription; + subscription.listener.apply( + subscription.context, + Array.prototype.slice.call(arguments, 1) + ); + } + } + this._currentSubscription = null; + } + } + + /** + * Removes the given listener for event of specific type. + * + * @param {string} eventType - Name of the event to emit + * @param {function} listener - Function to invoke when the specified event is + * emitted + * + * @example + * emitter.removeListener('someEvent', function(message) { + * console.log(message); + * }); // removes the listener if already registered + * + */ + removeListener(eventType: String, listener) { + const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + if (subscriptions) { + for (let i = 0, l = subscriptions.length; i < l; i++) { + const subscription = subscriptions[i]; + + // The subscription may have been removed during this event loop. + // its listener matches the listener in method parameters + if (subscription && subscription.listener === listener) { + subscription.remove(); + } + } + } + } +} + +module.exports = EventEmitter; diff --git a/lib/utils/emitter/EventSubscription.js b/lib/utils/emitter/EventSubscription.js new file mode 100644 index 00000000..670d6231 --- /dev/null +++ b/lib/utils/emitter/EventSubscription.js @@ -0,0 +1,42 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +'use strict'; + +import type EventSubscriptionVendor from './EventSubscriptionVendor'; + +/** + * EventSubscription represents a subscription to a particular event. It can + * remove its own subscription. + */ +class EventSubscription { + + eventType: string; + key: number; + subscriber: EventSubscriptionVendor; + + /** + * @param {EventSubscriptionVendor} subscriber the subscriber that controls + * this subscription. + */ + constructor(subscriber: EventSubscriptionVendor) { + this.subscriber = subscriber; + } + + /** + * Removes this subscription from the subscriber that controls it. + */ + remove() { + this.subscriber.removeSubscription(this); + } +} + +module.exports = EventSubscription; diff --git a/lib/utils/emitter/EventSubscriptionVendor.js b/lib/utils/emitter/EventSubscriptionVendor.js new file mode 100644 index 00000000..7e44009c --- /dev/null +++ b/lib/utils/emitter/EventSubscriptionVendor.js @@ -0,0 +1,100 @@ +/* eslint-disable */ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +'use strict'; + +const invariant = require('fbjs/lib/invariant'); + +import type EventSubscription from './EventSubscription'; + +/** + * EventSubscriptionVendor stores a set of EventSubscriptions that are + * subscribed to a particular event type. + */ +class EventSubscriptionVendor { + + _subscriptionsForType: Object; + _currentSubscription: ?EventSubscription; + + constructor() { + this._subscriptionsForType = {}; + this._currentSubscription = null; + } + + /** + * Adds a subscription keyed by an event type. + * + * @param {string} eventType + * @param {EventSubscription} subscription + */ + addSubscription( + eventType: string, subscription: EventSubscription): EventSubscription { + invariant( + subscription.subscriber === this, + 'The subscriber of the subscription is incorrectly set.'); + if (!this._subscriptionsForType[eventType]) { + this._subscriptionsForType[eventType] = []; + } + const key = this._subscriptionsForType[eventType].length; + this._subscriptionsForType[eventType].push(subscription); + subscription.eventType = eventType; + subscription.key = key; + return subscription; + } + + /** + * Removes a bulk set of the subscriptions. + * + * @param {?string} eventType - Optional name of the event type whose + * registered supscriptions to remove, if null remove all subscriptions. + */ + removeAllSubscriptions(eventType: ?string) { + if (eventType === undefined) { + this._subscriptionsForType = {}; + } else { + delete this._subscriptionsForType[eventType]; + } + } + + /** + * Removes a specific subscription. Instead of calling this function, call + * `subscription.remove()` directly. + * + * @param {object} subscription + */ + removeSubscription(subscription: Object) { + const eventType = subscription.eventType; + const key = subscription.key; + + const subscriptionsForType = this._subscriptionsForType[eventType]; + if (subscriptionsForType) { + delete subscriptionsForType[key]; + } + } + + /** + * Returns the array of subscriptions that are currently registered for the + * given event type. + * + * Note: This array can be potentially sparse as subscriptions are deleted + * from it when they are removed. + * + * TODO: This returns a nullable array. wat? + * + * @param {string} eventType + * @returns {?array} + */ + getSubscriptionsForType(eventType: string): ?[EventSubscription] { + return this._subscriptionsForType[eventType]; + } +} + +module.exports = EventSubscriptionVendor; diff --git a/package.json b/package.json index bb59b922..68350877 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "2.1.4", + "version": "2.2.0", "author": "Invertase (http://invertase.io)", "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Auth, Database, Messaging (FCM), Remote Config, Storage, Admob, Analytics, Crash Reporting, and Performance.", "main": "index", @@ -54,7 +54,8 @@ ], "peerDependencies": { "react": "*", - "react-native": ">= 0.40.0" + "react-native": ">= 0.40.0", + "fbjs": "*" }, "devDependencies": { "babel-eslint": "^7.0.0",