diff --git a/Libraries/Components/Keyboard/Keyboard.js b/Libraries/Components/Keyboard/Keyboard.js index 7c1cc7547..603a96c6e 100644 --- a/Libraries/Components/Keyboard/Keyboard.js +++ b/Libraries/Components/Keyboard/Keyboard.js @@ -12,5 +12,6 @@ 'use strict'; const NativeEventEmitter = require('NativeEventEmitter'); +const KeyboardObserver = require('NativeModules').KeyboardObserver; -module.exports = new NativeEventEmitter('KeyboardObserver'); +module.exports = new NativeEventEmitter(KeyboardObserver); diff --git a/Libraries/Components/StatusBar/StatusBarIOS.ios.js b/Libraries/Components/StatusBar/StatusBarIOS.ios.js index 9c2176c44..92e75b1d1 100644 --- a/Libraries/Components/StatusBar/StatusBarIOS.ios.js +++ b/Libraries/Components/StatusBar/StatusBarIOS.ios.js @@ -13,6 +13,7 @@ const NativeEventEmitter = require('NativeEventEmitter'); const StatusBar = require('StatusBar'); +const StatusBarManager = require('NativeModules').StatusBarManager; import type {StatusBarStyle, StatusBarAnimation} from 'StatusBar'; @@ -38,6 +39,6 @@ class StatusBarIOS extends NativeEventEmitter { ); StatusBar.setNetworkActivityIndicatorVisible(visible); } -}; +} -module.exports = new StatusBarIOS('StatusBarManager'); +module.exports = new StatusBarIOS(StatusBarManager); diff --git a/Libraries/Device/NativeEventEmitter.js b/Libraries/Device/NativeEventEmitter.js index 78ee98c8e..5b7224f3b 100644 --- a/Libraries/Device/NativeEventEmitter.js +++ b/Libraries/Device/NativeEventEmitter.js @@ -11,7 +11,6 @@ */ 'use strict'; -const NativeModules = require('NativeModules'); const Platform = require('Platform'); const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); const invariant = require('fbjs/lib/invariant'); @@ -24,21 +23,17 @@ import type EmitterSubscription from 'EmitterSubscription'; */ class NativeEventEmitter { - _listenerCount: number; _nativeModule: Object; - constructor(nativeModuleName: string) { + constructor(nativeModule: Object) { if (Platform.OS === 'ios') { - this._listenerCount = 0; - this._nativeModule = NativeModules[nativeModuleName]; - invariant(this._nativeModule, - 'Native module `' + nativeModuleName + '` not found.'); + invariant(nativeModule, 'Native module cannot be null.'); + this._nativeModule = nativeModule; } } addListener(eventType: string, listener: any, context: ?Object): EmitterSubscription { if (Platform.OS === 'ios') { - this._listenerCount++; this._nativeModule.addListener(eventType); } return RCTDeviceEventEmitter.nativeAddListener(eventType, listener, context); @@ -54,7 +49,7 @@ class NativeEventEmitter { removeAllListeners(eventType: string) { invariant(eventType, 'eventType argument is required.'); if (Platform.OS === 'ios') { - var count = RCTDeviceEventEmitter.listeners(eventType).length; + const count = RCTDeviceEventEmitter.listeners(eventType).length; this._nativeModule.removeListeners(count); } RCTDeviceEventEmitter.removeAllListeners(eventType); @@ -62,7 +57,6 @@ class NativeEventEmitter { removeCurrentListener() { if (Platform.OS === 'ios') { - this._listenerCount--; this._nativeModule.removeListeners(1); } RCTDeviceEventEmitter.removeCurrentListener(); diff --git a/Libraries/Device/RCTDeviceEventEmitter.js b/Libraries/Device/RCTDeviceEventEmitter.js index 4aeb55afc..96d9183b2 100644 --- a/Libraries/Device/RCTDeviceEventEmitter.js +++ b/Libraries/Device/RCTDeviceEventEmitter.js @@ -13,23 +13,22 @@ const EventEmitter = require('EventEmitter'); const BatchedBridge = require('BatchedBridge'); -const NativeModules = require('NativeModules'); import type EmitterSubscription from 'EmitterSubscription'; /** * Deprecated - subclass NativeEventEmitter to create granular event modules instead of - * routing all event observation through RCTDeviceEventEmitter. + * adding all event listeners directly to RCTDeviceEventEmitter. */ class RCTDeviceEventEmitter extends EventEmitter { addListener(eventType: string, listener: any, context: ?Object): EmitterSubscription { if (eventType.lastIndexOf('statusBar', 0) === 0) { - console.warn('statusBar events should be registered via the StatusBarIOS module'); + console.warn('`%s` event should be registered via the StatusBarIOS module', eventType); return require('StatusBarIOS').addListener(eventType, listener, context); } if (eventType.lastIndexOf('keyboard', 0) === 0) { - console.warn('keyboard events should be registered via the Keyboard module'); + console.warn('`%s` event should be registered via the Keyboard module', eventType); return require('Keyboard').addListener(eventType, listener, context); } return super.addListener(eventType, listener, context); diff --git a/Libraries/WebSocket/RCTWebSocketModule.h b/Libraries/WebSocket/RCTWebSocketModule.h index 082122e00..ceead5ad9 100644 --- a/Libraries/WebSocket/RCTWebSocketModule.h +++ b/Libraries/WebSocket/RCTWebSocketModule.h @@ -7,9 +7,9 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTBridgeModule.h" +#import "RCTEventEmitter.h" #import "RCTSRWebSocket.h" -@interface RCTWebSocketModule : NSObject +@interface RCTWebSocketModule : RCTEventEmitter @end diff --git a/Libraries/WebSocket/RCTWebSocketModule.m b/Libraries/WebSocket/RCTWebSocketModule.m index ad0c33964..d83e5942b 100644 --- a/Libraries/WebSocket/RCTWebSocketModule.m +++ b/Libraries/WebSocket/RCTWebSocketModule.m @@ -9,8 +9,6 @@ #import "RCTWebSocketModule.h" -#import "RCTBridge.h" -#import "RCTEventDispatcher.h" #import "RCTConvert.h" #import "RCTUtils.h" @@ -35,7 +33,13 @@ RCT_EXPORT_MODULE() -@synthesize bridge = _bridge; +- (NSArray *)supportedEvents +{ + return @[@"websocketMessage", + @"websocketOpen", + @"websocketFailed", + @"websocketClosed"]; +} - (void)dealloc { @@ -84,7 +88,7 @@ RCT_EXPORT_METHOD(close:(nonnull NSNumber *)socketID) - (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message { BOOL binary = [message isKindOfClass:[NSData class]]; - [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketMessage" body:@{ + [self sendEventWithName:@"websocketMessage" body:@{ @"data": binary ? [message base64EncodedStringWithOptions:0] : message, @"type": binary ? @"binary" : @"text", @"id": webSocket.reactTag @@ -93,14 +97,14 @@ RCT_EXPORT_METHOD(close:(nonnull NSNumber *)socketID) - (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket { - [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketOpen" body:@{ + [self sendEventWithName:@"websocketOpen" body:@{ @"id": webSocket.reactTag }]; } - (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error { - [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketFailed" body:@{ + [self sendEventWithName:@"websocketFailed" body:@{ @"message":error.localizedDescription, @"id": webSocket.reactTag }]; @@ -109,7 +113,7 @@ RCT_EXPORT_METHOD(close:(nonnull NSNumber *)socketID) - (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { - [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketClosed" body:@{ + [self sendEventWithName:@"websocketClosed" body:@{ @"code": @(code), @"reason": RCTNullIfNil(reason), @"clean": @(wasClean), diff --git a/Libraries/WebSocket/WebSocket.js b/Libraries/WebSocket/WebSocket.js index caed9a276..f5063fdb2 100644 --- a/Libraries/WebSocket/WebSocket.js +++ b/Libraries/WebSocket/WebSocket.js @@ -11,9 +11,9 @@ */ 'use strict'; -const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -const RCTWebSocketModule = require('NativeModules').WebSocketModule; +const NativeEventEmitter = require('NativeEventEmitter'); const Platform = require('Platform'); +const RCTWebSocketModule = require('NativeModules').WebSocketModule; const WebSocketEvent = require('WebSocketEvent'); const EventTarget = require('event-target-shim'); @@ -67,6 +67,7 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) { CLOSED: number = CLOSED; _socketId: number; + _eventEmitter: NativeEventEmitter; _subscriptions: Array; onclose: ?Function; @@ -91,6 +92,7 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) { protocols = null; } + this._eventEmitter = new NativeEventEmitter(RCTWebSocketModule); this._socketId = nextWebSocketId++; RCTWebSocketModule.connect(url, protocols, options, this._socketId); this._registerEvents(); @@ -136,8 +138,8 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) { _close(code?: number, reason?: string): void { if (Platform.OS === 'android') { // See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent - var statusCode = typeof code === 'number' ? code : CLOSE_NORMAL; - var closeReason = typeof reason === 'string' ? reason : ''; + const statusCode = typeof code === 'number' ? code : CLOSE_NORMAL; + const closeReason = typeof reason === 'string' ? reason : ''; RCTWebSocketModule.close(statusCode, closeReason, this._socketId); } else { RCTWebSocketModule.close(this._socketId); @@ -151,47 +153,43 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) { _registerEvents(): void { this._subscriptions = [ - RCTDeviceEventEmitter.addListener('websocketMessage', ev => { + this._eventEmitter.addListener('websocketMessage', ev => { if (ev.id !== this._socketId) { return; } - var event = new WebSocketEvent('message', { + this.dispatchEvent(new WebSocketEvent('message', { data: (ev.type === 'binary') ? base64.toByteArray(ev.data).buffer : ev.data - }); - this.dispatchEvent(event); + })); }), - RCTDeviceEventEmitter.addListener('websocketOpen', ev => { + this._eventEmitter.addListener('websocketOpen', ev => { if (ev.id !== this._socketId) { return; } this.readyState = this.OPEN; - var event = new WebSocketEvent('open'); - this.dispatchEvent(event); + this.dispatchEvent(new WebSocketEvent('open')); }), - RCTDeviceEventEmitter.addListener('websocketClosed', ev => { + this._eventEmitter.addListener('websocketClosed', ev => { if (ev.id !== this._socketId) { return; } this.readyState = this.CLOSED; - var event = new WebSocketEvent('close'); - event.code = ev.code; - event.reason = ev.reason; - this.dispatchEvent(event); + this.dispatchEvent(new WebSocketEvent('close', { + code: ev.code, + reason: ev.reason, + })); this._unregisterEvents(); this.close(); }), - RCTDeviceEventEmitter.addListener('websocketFailed', ev => { + this._eventEmitter.addListener('websocketFailed', ev => { if (ev.id !== this._socketId) { return; } - var event = new WebSocketEvent('error'); - event.message = ev.message; - this.dispatchEvent(event); - - event = new WebSocketEvent('close'); - event.message = ev.message; - this.dispatchEvent(event); - + this.dispatchEvent(new WebSocketEvent('error', { + message: ev.message, + })); + this.dispatchEvent(new WebSocketEvent('close', { + message: ev.message, + })); this._unregisterEvents(); this.close(); }) diff --git a/React/Modules/RCTEventEmitter.m b/React/Modules/RCTEventEmitter.m index d0a2cf720..d53e1f8c2 100644 --- a/React/Modules/RCTEventEmitter.m +++ b/React/Modules/RCTEventEmitter.m @@ -23,18 +23,23 @@ - (NSArray *)supportedEvents { + RCTAssert(NO, @"You must override the `supportedEvents` method of %@", [self class]); return nil; } - (void)sendEventWithName:(NSString *)eventName body:(id)body { - RCTAssert(self.bridge != nil, @"bridge is not set."); + RCTAssert(_bridge != nil, @"bridge is not set."); if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) { RCTLogError(@"`%@` is not a supported event type for %@", eventName, [self class]); } - [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" - args:body ? @[eventName, body] : @[eventName]]; + if (_listenerCount > 0) { + [_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" + args:body ? @[eventName, body] : @[eventName]]; + } else { + RCTLogWarn(@"Sending `%@` with no listeners registered.", eventName); + } } - (void)startObserving