diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index 51536cabd..365b6a58b 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -11,7 +11,7 @@ */ 'use strict'; -const EventEmitter = require('EventEmitter'); +const MissingNativeEventEmitterShim = require('MissingNativeEventEmitterShim'); const NativeEventEmitter = require('NativeEventEmitter'); const NativeModules = require('NativeModules'); const RCTAppState = NativeModules.AppState; @@ -32,8 +32,8 @@ const invariant = require('fbjs/lib/invariant'); * - `background` - The app is running in the background. The user is either * in another app or on the home screen * - `inactive` - This is a state that occurs when transitioning between - * foreground & background, and during periods of inactivity such as - * entering the Multitasking view or in the event of an incoming call + * foreground & background, and during periods of inactivity such as + * entering the Multitasking view or in the event of an incoming call * * For more information, see * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html) @@ -87,7 +87,7 @@ class AppState extends NativeEventEmitter { _eventHandlers: Object; currentState: ?string; - isAvailable: boolean; + isAvailable: boolean = true; constructor() { super(RCTAppState); @@ -176,48 +176,31 @@ class AppState extends NativeEventEmitter { } } -function throwMissingNativeModule() { - invariant( - false, - 'Cannot use AppState module when native RCTAppState is not included in the build.\n' + - 'Either include it, or check AppState.isAvailable before calling any methods.' - ); -} +if (__DEV__ && !RCTAppState) { + class MissingNativeAppStateShim extends MissingNativeEventEmitterShim { + constructor() { + super('RCTAppState', 'AppState'); + } -class MissingNativeAppStateShim extends EventEmitter { - // AppState - isAvailable: boolean = false; - currentState: ?string = null; + get currentState(): ?string { + this.throwMissingNativeModule(); + } - addEventListener() { - throwMissingNativeModule(); + addEventListener(...args: Array) { + this.throwMissingNativeModule(); + } + + removeEventListener(...args: Array) { + this.throwMissingNativeModule(); + } } - removeEventListener() { - throwMissingNativeModule(); - } - - // EventEmitter - addListener() { - throwMissingNativeModule(); - } - - removeAllListeners() { - throwMissingNativeModule(); - } - - removeSubscription() { - throwMissingNativeModule(); - } -} - -// This module depends on the native `RCTAppState` module. If you don't include it, -// `AppState.isAvailable` will return `false`, and any method calls will throw. -// We reassign the class variable to keep the autodoc generator happy. -if (RCTAppState) { - AppState = new AppState(); -} else { + // This module depends on the native `RCTAppState` module. If you don't include it, + // `AppState.isAvailable` will return `false`, and any method calls will throw. + // We reassign the class variable to keep the autodoc generator happy. AppState = new MissingNativeAppStateShim(); +} else { + AppState = new AppState(); } module.exports = AppState; diff --git a/Libraries/Core/Devtools/setupDevtools.js b/Libraries/Core/Devtools/setupDevtools.js index 56d5564b6..de1c9ec67 100644 --- a/Libraries/Core/Devtools/setupDevtools.js +++ b/Libraries/Core/Devtools/setupDevtools.js @@ -13,24 +13,28 @@ if (__DEV__) { const AppState = require('AppState'); + const WebSocket = require('WebSocket'); const {PlatformConstants} = require('NativeModules'); const {connectToDevTools} = require('react-devtools-core'); - connectToDevTools({ - isAppActive() { - // Don't steal the DevTools from currently active app. - // Note: if you add any AppState subscriptions to this file, - // you will also need to guard against `AppState.isAvailable`, - // or the code will throw for bundles that don't have it. - return AppState.currentState !== 'background'; - }, - // Special case: Genymotion is running on a different host. - host: PlatformConstants && PlatformConstants.ServerHost ? - PlatformConstants.ServerHost.split(':')[0] : - 'localhost', - // Read the optional global variable for backward compatibility. - // It was added in https://github.com/facebook/react-native/commit/bf2b435322e89d0aeee8792b1c6e04656c2719a0. - port: window.__REACT_DEVTOOLS_PORT__, - resolveRNStyle: require('flattenStyle'), - }); + // Initialize dev tools only if the native module for WebSocket is available + if (WebSocket.isAvailable) { + connectToDevTools({ + isAppActive() { + // Don't steal the DevTools from currently active app. + // Note: if you add any AppState subscriptions to this file, + // you will also need to guard against `AppState.isAvailable`, + // or the code will throw for bundles that don't have it. + return AppState.currentState !== 'background'; + }, + // Special case: Genymotion is running on a different host. + host: PlatformConstants && PlatformConstants.ServerHost ? + PlatformConstants.ServerHost.split(':')[0] : + 'localhost', + // Read the optional global variable for backward compatibility. + // It was added in https://github.com/facebook/react-native/commit/bf2b435322e89d0aeee8792b1c6e04656c2719a0. + port: window.__REACT_DEVTOOLS_PORT__, + resolveRNStyle: require('flattenStyle'), + }); + } } diff --git a/Libraries/EventEmitter/MissingNativeEventEmitterShim.js b/Libraries/EventEmitter/MissingNativeEventEmitterShim.js new file mode 100644 index 000000000..244b03361 --- /dev/null +++ b/Libraries/EventEmitter/MissingNativeEventEmitterShim.js @@ -0,0 +1,54 @@ +/** + * 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. + * + * @providesModule MissingNativeEventEmitterShim + * @flow + */ +'use strict'; + +const EmitterSubscription = require('EmitterSubscription'); +const EventEmitter = require('EventEmitter'); + +const invariant = require('fbjs/lib/invariant'); + +class MissingNativeEventEmitterShim extends EventEmitter { + isAvailable: boolean = false; + _nativeModuleName: string; + _nativeEventEmitterName: string; + + constructor(nativeModuleName: string, nativeEventEmitterName: string) { + super(null); + this._nativeModuleName = nativeModuleName; + this._nativeEventEmitterName = nativeEventEmitterName; + } + + throwMissingNativeModule() { + invariant( + false, + `Cannot use '${this._nativeEventEmitterName}' module when ` + + `native '${this._nativeModuleName}' is not included in the build. ` + + `Either include it, or check '${this._nativeEventEmitterName}'.isAvailable ` + + 'before calling any methods.' + ); + } + + // EventEmitter + addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription { + this.throwMissingNativeModule(); + } + + removeAllListeners(eventType: string) { + this.throwMissingNativeModule(); + } + + removeSubscription(subscription: EmitterSubscription) { + this.throwMissingNativeModule(); + } +} + +module.exports = MissingNativeEventEmitterShim; diff --git a/Libraries/EventEmitter/NativeEventEmitter.js b/Libraries/EventEmitter/NativeEventEmitter.js index 67774a165..be0de96f7 100644 --- a/Libraries/EventEmitter/NativeEventEmitter.js +++ b/Libraries/EventEmitter/NativeEventEmitter.js @@ -23,7 +23,6 @@ import type EmitterSubscription from 'EmitterSubscription'; * a subset of the standard EventEmitter node module API. */ class NativeEventEmitter extends EventEmitter { - _nativeModule: Object; constructor(nativeModule: Object) { diff --git a/Libraries/Network/RCTNetworking.android.js b/Libraries/Network/RCTNetworking.android.js index e8bee18a3..db7ccdf08 100644 --- a/Libraries/Network/RCTNetworking.android.js +++ b/Libraries/Network/RCTNetworking.android.js @@ -13,7 +13,7 @@ // Do not require the native RCTNetworking module directly! Use this wrapper module instead. // It will add the necessary requestId, so that you don't have to generate it yourself. -const FormData = require('FormData'); +const MissingNativeEventEmitterShim = require('MissingNativeEventEmitterShim'); const NativeEventEmitter = require('NativeEventEmitter'); const RCTNetworkingNative = require('NativeModules').Networking; const convertRequestBody = require('convertRequestBody'); @@ -43,6 +43,8 @@ function generateRequestId(): number { */ class RCTNetworking extends NativeEventEmitter { + isAvailable: boolean = true; + constructor() { super(RCTNetworkingNative); } @@ -90,4 +92,31 @@ class RCTNetworking extends NativeEventEmitter { } } -module.exports = new RCTNetworking(); +if (__DEV__ && !RCTNetworkingNative) { + class MissingNativeRCTNetworkingShim extends MissingNativeEventEmitterShim { + constructor() { + super('RCTAppState', 'AppState'); + } + + sendRequest(...args: Array) { + this.throwMissingNativeModule(); + } + + abortRequest(...args: Array) { + this.throwMissingNativeModule(); + } + + clearCookies(...args: Array) { + this.throwMissingNativeModule(); + } + } + + // This module depends on the native `RCTNetworkingNative` module. If you don't include it, + // `RCTNetworking.isAvailable` will return `false`, and any method calls will throw. + // We reassign the class variable to keep the autodoc generator happy. + RCTNetworking = new MissingNativeRCTNetworkingShim(); +} else { + RCTNetworking = new RCTNetworking(); +} + +module.exports = RCTNetworking; diff --git a/Libraries/Network/RCTNetworking.ios.js b/Libraries/Network/RCTNetworking.ios.js index dd569a07d..15f3d6da7 100644 --- a/Libraries/Network/RCTNetworking.ios.js +++ b/Libraries/Network/RCTNetworking.ios.js @@ -11,7 +11,7 @@ */ 'use strict'; -const FormData = require('FormData'); +const MissingNativeEventEmitterShim = require('MissingNativeEventEmitterShim'); const NativeEventEmitter = require('NativeEventEmitter'); const RCTNetworkingNative = require('NativeModules').Networking; const convertRequestBody = require('convertRequestBody'); @@ -20,6 +20,8 @@ import type {RequestBody} from 'convertRequestBody'; class RCTNetworking extends NativeEventEmitter { + isAvailable: boolean = true; + constructor() { super(RCTNetworkingNative); } @@ -58,4 +60,31 @@ class RCTNetworking extends NativeEventEmitter { } } -module.exports = new RCTNetworking(); +if (__DEV__ && !RCTNetworkingNative) { + class MissingNativeRCTNetworkingShim extends MissingNativeEventEmitterShim { + constructor() { + super('RCTAppState', 'AppState'); + } + + sendRequest(...args: Array) { + this.throwMissingNativeModule(); + } + + abortRequest(...args: Array) { + this.throwMissingNativeModule(); + } + + clearCookies(...args: Array) { + this.throwMissingNativeModule(); + } + } + + // This module depends on the native `RCTNetworkingNative` module. If you don't include it, + // `RCTNetworking.isAvailable` will return `false`, and any method calls will throw. + // We reassign the class variable to keep the autodoc generator happy. + RCTNetworking = new MissingNativeRCTNetworkingShim(); +} else { + RCTNetworking = new RCTNetworking(); +} + +module.exports = RCTNetworking; diff --git a/Libraries/WebSocket/WebSocket.js b/Libraries/WebSocket/WebSocket.js index 12c6e4ad6..40b086de2 100644 --- a/Libraries/WebSocket/WebSocket.js +++ b/Libraries/WebSocket/WebSocket.js @@ -71,6 +71,10 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) { readyState: number = CONNECTING; url: ?string; + // This module depends on the native `RCTWebSocketModule` module. If you don't include it, + // `WebSocket.isAvailable` will return `false`, and WebSocket constructor will throw an error + static isAvailable: boolean = !!RCTWebSocketModule; + constructor(url: string, protocols: ?string | ?Array, options: ?{origin?: string}) { super(); if (typeof protocols === 'string') { @@ -81,6 +85,11 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) { protocols = null; } + if (!WebSocket.isAvailable) { + throw new Error('Cannot initialize WebSocket module. ' + + 'Native module RCTWebSocketModule is missing.'); + } + this._eventEmitter = new NativeEventEmitter(RCTWebSocketModule); this._socketId = nextWebSocketId++; this._registerEvents();