From 4b5385b2f04d2a67a4954f3661ea7963881489f2 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 13 May 2015 12:19:32 -0700 Subject: [PATCH 01/21] [ReactNative] Fix Navigator resetTo race condition Summary: SetState can be somewhat racy. By the time the route state finishes, another resetTo has already happened, so the origional route is no longer in the stack. Hence the redbox invariant "Calling pop to route for a route that doesn't exist!" This could also be fixed in product code by not calling resetTo rapidly, but the navigator should be resilient to such shenanigans @public Test Plan: Cannot get AdsManager crash t7031976 --- Libraries/CustomComponents/Navigator/Navigator.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index bc044f1b9..b8c019e32 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -1173,7 +1173,11 @@ var Navigator = React.createClass({ resetTo: function(route) { invariant(!!route, 'Must supply route to push'); this.replaceAtIndex(route, 0, () => { - this.popToRoute(route); + // Do not use popToRoute here, because race conditions could prevent the + // route from existing at this time. Instead, just go to index 0 + if (this.state.presentedIndex > 0) { + this._popN(this.state.presentedIndex); + } }); }, From b47e89a397f39ec0afb7f2c2887d2a37f9430217 Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Wed, 13 May 2015 12:36:45 -0700 Subject: [PATCH 02/21] Back out D2063283: [react-packager] Update worker farm --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0421c96a8..61735be64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native", - "version": "0.4.2", + "version": "0.4.1", "description": "A framework for building native apps using React", "repository": { "type": "git", @@ -63,12 +63,12 @@ "stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638", "uglify-js": "~2.4.16", "underscore": "1.7.0", - "worker-farm": "^1.3.0", + "worker-farm": "1.1.0", "ws": "0.4.31", "yargs": "1.3.2" }, "devDependencies": { - "jest-cli": "0.4.3", + "jest-cli": "0.2.1", "eslint": "0.9.2" } } From 5429b5f9cc84cd471909c3a9353b93d5d2efd8e1 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 13 May 2015 14:44:16 -0700 Subject: [PATCH 03/21] [react-packager] Use transformer name in cache name Summary: @public Shouldn't confuse the cache from files transformed by different transformers. This takes into account the transformer in the cache hash name. Test Plan: * start server with --babel * generate bundle * start server with --jstransform * generate bundle * compare them and they're different --- .../react-packager/src/JSTransformer/Cache.js | 6 +++ .../src/JSTransformer/__tests__/Cache-test.js | 37 ++++++++++++++++--- .../react-packager/src/JSTransformer/index.js | 1 + 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/packager/react-packager/src/JSTransformer/Cache.js b/packager/react-packager/src/JSTransformer/Cache.js index 4761d15ed..584077b6c 100644 --- a/packager/react-packager/src/JSTransformer/Cache.js +++ b/packager/react-packager/src/JSTransformer/Cache.js @@ -31,6 +31,10 @@ var validateOpts = declareOpts({ type: 'array', required: true, }, + transformModulePath: { + type:'string', + required: true, + }, }); module.exports = Cache; @@ -162,6 +166,8 @@ function cacheFilePath(options) { var cacheVersion = options.cacheVersion || '0'; hash.update(cacheVersion); + hash.update(options.transformModulePath); + var name = 'react-packager-cache-' + hash.digest('hex'); return path.join(tmpdir, name); } diff --git a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js index 7ad658183..f91490ba0 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -32,10 +32,14 @@ describe('JSTransformer Cache', function() { describe('getting/setting', function() { it('calls loader callback for uncached file', function() { - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve(); }); + cache.get('/rootDir/someFile', loaderCb); expect(loaderCb).toBeCalledWith('/rootDir/someFile'); }); @@ -48,10 +52,15 @@ describe('JSTransformer Cache', function() { } }); }); - var cache = new Cache({projectRoots: ['/rootDir']}); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve('lol'); }); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { expect(value).toBe('lol'); }); @@ -65,10 +74,15 @@ describe('JSTransformer Cache', function() { } }); }); - var cache = new Cache({projectRoots: ['/rootDir']}); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve('lol'); }); + return cache.get('/rootDir/someFile', loaderCb).then(function() { var shouldNotBeCalled = jest.genMockFn(); return cache.get('/rootDir/someFile', shouldNotBeCalled) @@ -126,8 +140,12 @@ describe('JSTransformer Cache', function() { }); pit('should load cache from disk', function() { - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn(); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { expect(loaderCb).not.toBeCalled(); expect(value).toBe('oh hai'); @@ -152,7 +170,10 @@ describe('JSTransformer Cache', function() { return 123; }; - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve('new value'); }); @@ -193,7 +214,11 @@ describe('JSTransformer Cache', function() { }); }); - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + cache.get('/rootDir/bar', function() { return Promise.resolve('bar value'); }); diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index 33e017037..2dc5e20bd 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -60,6 +60,7 @@ function Transformer(options) { resetCache: options.resetCache, cacheVersion: options.cacheVersion, projectRoots: options.projectRoots, + transformModulePath: options.transformModulePath, }); if (options.transformModulePath != null) { From 9fde7d2828956372aadd64c760cdfb66a3acde0a Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 13 May 2015 17:45:36 -0700 Subject: [PATCH 04/21] [react-native] Make document.js into a polyfill. Fixes #1149 Summary: @public document shimming must run before anything else. However, we don't currently guarantee that. This moves the document shimming into `document.js` which is used as a polyfill. Test Plan: * start server * go to playground app * require `NativeModules` as the first thing * open chrome debugger * no error --- .../InitializeJavaScriptAppEngine.js | 34 ------------------- .../JavaScriptAppEngine/polyfills/document.js | 34 +++++++++++++++++++ packager/packager.js | 7 +++- 3 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 Libraries/JavaScriptAppEngine/polyfills/document.js diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 217fd93e7..b810c5bc3 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -33,39 +33,6 @@ if (typeof window === 'undefined') { window = GLOBAL; } -/** - * The document must be shimmed before anything else that might define the - * `ExecutionEnvironment` module (which checks for `document.createElement`). - */ -function setupDocumentShim() { - // The browser defines Text and Image globals by default. If you forget to - // require them, then the error message is very confusing. - function getInvalidGlobalUseError(name) { - return new Error( - 'You are trying to render the global ' + name + ' variable as a ' + - 'React element. You probably forgot to require ' + name + '.' - ); - } - GLOBAL.Text = { - get defaultProps() { - throw getInvalidGlobalUseError('Text'); - } - }; - GLOBAL.Image = { - get defaultProps() { - throw getInvalidGlobalUseError('Image'); - } - }; - // Force `ExecutionEnvironment.canUseDOM` to be false. - if (GLOBAL.document) { - GLOBAL.document.createElement = null; - } - - // There is no DOM so MutationObserver doesn't make sense. It is used - // as feature detection in Bluebird Promise implementation - GLOBAL.MutationObserver = undefined; -} - function handleErrorWithRedBox(e, isFatal) { try { require('ExceptionsManager').handleException(e, isFatal); @@ -148,7 +115,6 @@ function setupGeolocation() { GLOBAL.navigator.geolocation = require('Geolocation'); } -setupDocumentShim(); setupRedBoxErrorHandler(); setupTimers(); setupAlert(); diff --git a/Libraries/JavaScriptAppEngine/polyfills/document.js b/Libraries/JavaScriptAppEngine/polyfills/document.js new file mode 100644 index 000000000..4e76db957 --- /dev/null +++ b/Libraries/JavaScriptAppEngine/polyfills/document.js @@ -0,0 +1,34 @@ +/* eslint global-strict: 0 */ +(function(GLOBAL) { + /** + * The document must be shimmed before anything else that might define the + * `ExecutionEnvironment` module (which checks for `document.createElement`). + */ + + // The browser defines Text and Image globals by default. If you forget to + // require them, then the error message is very confusing. + function getInvalidGlobalUseError(name) { + return new Error( + 'You are trying to render the global ' + name + ' variable as a ' + + 'React element. You probably forgot to require ' + name + '.' + ); + } + GLOBAL.Text = { + get defaultProps() { + throw getInvalidGlobalUseError('Text'); + } + }; + GLOBAL.Image = { + get defaultProps() { + throw getInvalidGlobalUseError('Image'); + } + }; + // Force `ExecutionEnvironment.canUseDOM` to be false. + if (GLOBAL.document) { + GLOBAL.document.createElement = null; + } + + // There is no DOM so MutationObserver doesn't make sense. It is used + // as feature detection in Bluebird Promise implementation + GLOBAL.MutationObserver = undefined; +})(this); diff --git a/packager/packager.js b/packager/packager.js index 48552d93e..eb90b04e0 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -200,7 +200,12 @@ function getAppMiddleware(options) { cacheVersion: '2', transformModulePath: require.resolve('./transformer.js'), assetRoots: options.assetRoots, - assetExts: ['png', 'jpeg', 'jpg'] + assetExts: ['png', 'jpeg', 'jpg'], + polyfillModuleNames: [ + require.resolve( + '../Libraries/JavaScriptAppEngine/polyfills/document.js' + ), + ], }); } From a6b29a0b1a74b666e2c10a1c72563b02e83b814c Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 13 May 2015 17:39:02 -0700 Subject: [PATCH 05/21] [react-packager] Update worker farm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61735be64..2cc5ec6f8 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638", "uglify-js": "~2.4.16", "underscore": "1.7.0", - "worker-farm": "1.1.0", + "worker-farm": "^1.3.0", "ws": "0.4.31", "yargs": "1.3.2" }, From 6e179fb7cd68c92f8abd94d381f85c41298c828c Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 13 May 2015 18:33:43 -0700 Subject: [PATCH 06/21] [ReactNative] introduce mountSafeCallback Summary: `mountSafeCallback` simply wraps a callback in an `isMounted()` check to prevent crashes when old callbacks are called on unmounted components. @public Test Plan: Added logging and made sure callbacks were getting called through `mountSafeCallback` and that things worked (e.g. photo viewer rotation etc). --- Libraries/ReactIOS/NativeMethodsMixin.js | 16 ++++++++++++---- Libraries/Utilities/mountSafeCallback.js | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 Libraries/Utilities/mountSafeCallback.js diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index 1bbf30b06..4bc99f56b 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -20,6 +20,7 @@ var findNodeHandle = require('findNodeHandle'); var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); var mergeFast = require('mergeFast'); +var mountSafeCallback = require('mountSafeCallback'); var precomputeStyle = require('precomputeStyle'); type MeasureOnSuccessCallback = ( @@ -52,7 +53,11 @@ var animationIDInvariant = function( var NativeMethodsMixin = { addAnimation: function(anim: number, callback?: (finished: bool) => void) { animationIDInvariant('addAnimation', anim); - RCTPOPAnimationManager.addAnimation(findNodeHandle(this), anim, callback); + RCTPOPAnimationManager.addAnimation( + findNodeHandle(this), + anim, + mountSafeCallback(this, callback) + ); }, removeAnimation: function(anim: number) { @@ -61,7 +66,10 @@ var NativeMethodsMixin = { }, measure: function(callback: MeasureOnSuccessCallback) { - RCTUIManager.measure(findNodeHandle(this), callback); + RCTUIManager.measure( + findNodeHandle(this), + mountSafeCallback(this, callback) + ); }, measureLayout: function( @@ -72,8 +80,8 @@ var NativeMethodsMixin = { RCTUIManager.measureLayout( findNodeHandle(this), relativeToNativeNode, - onFail, - onSuccess + mountSafeCallback(this, onFail), + mountSafeCallback(this, onSuccess) ); }, diff --git a/Libraries/Utilities/mountSafeCallback.js b/Libraries/Utilities/mountSafeCallback.js new file mode 100644 index 000000000..c065718d1 --- /dev/null +++ b/Libraries/Utilities/mountSafeCallback.js @@ -0,0 +1,23 @@ +/** + * 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 mountSafeCallback + * @flow + */ +'use strict'; + +var mountSafeCallback = function(context: ReactComponent, callback: ?Function): any { + return function() { + if (!callback || !context.isMounted()) { + return; + } + return callback.apply(context, arguments); + }; +}; + +module.exports = mountSafeCallback; From e84e5710e4ef7f2fa8d527abad60b2dc26ddfe2f Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Thu, 14 May 2015 04:07:07 -0700 Subject: [PATCH 07/21] [React Native] Update podspec for 0.4.2 --- React.podspec | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/React.podspec b/React.podspec index 159072b39..eec148520 100644 --- a/React.podspec +++ b/React.podspec @@ -80,10 +80,10 @@ Pod::Spec.new do |s| ss.preserve_paths = "Libraries/PushNotificationIOS/*.js" end - s.subspec 'RCTWebSocketDebugger' do |ss| + s.subspec 'RCTSettings' do |ss| ss.dependency 'React/Core' - ss.libraries = 'icucore' - ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}" + ss.source_files = "Libraries/Settings/*.{h,m}" + ss.preserve_paths = "Libraries/Settings/*.js" end s.subspec 'RCTText' do |ss| @@ -97,4 +97,9 @@ Pod::Spec.new do |s| ss.source_files = "Libraries/Vibration/*.{h,m}" ss.preserve_paths = "Libraries/Vibration/*.js" end + + s.subspec 'RCTWebSocketDebugger' do |ss| + ss.dependency 'React/Core' + ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}" + end end From 661321fda79bba48cf6e5d6e8ccce34e5d7b67c9 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 14 May 2015 08:13:37 -0700 Subject: [PATCH 08/21] [ReactNative] Remove ReactNativeComponentMixin --- .../ReactNative/ReactNativeBaseComponent.js | 4 +-- .../ReactNativeBaseComponentMixin.js | 32 ------------------- .../ReactNativeDefaultInjection.js | 3 -- 3 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 Libraries/ReactNative/ReactNativeBaseComponentMixin.js diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 1db02652e..dcc31a2b3 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -12,7 +12,6 @@ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); -var ReactNativeComponentMixin = require('ReactNativeComponentMixin'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); @@ -275,8 +274,7 @@ Object.assign( ReactNativeBaseComponent.prototype, ReactMultiChild.Mixin, ReactNativeBaseComponent.Mixin, - NativeMethodsMixin, - ReactNativeComponentMixin + NativeMethodsMixin ); module.exports = ReactNativeBaseComponent; diff --git a/Libraries/ReactNative/ReactNativeBaseComponentMixin.js b/Libraries/ReactNative/ReactNativeBaseComponentMixin.js deleted file mode 100644 index 7cbf97077..000000000 --- a/Libraries/ReactNative/ReactNativeBaseComponentMixin.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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 ReactNativeComponentMixin - * @flow - */ -'use strict'; - -var findNodeHandle = require('findNodeHandle'); - -var ReactNativeComponentMixin = { - /** - * This method is deprecated; use `React.findNodeHandle` instead. - */ - getNativeNode: function() { - return findNodeHandle(this); - }, - - /** - * This method is deprecated; use `React.findNodeHandle` instead. - */ - getNodeHandle: function() { - return findNodeHandle(this); - } -}; - -module.exports = ReactNativeComponentMixin; diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js index 93c261280..3b15a8cc9 100644 --- a/Libraries/ReactNative/ReactNativeDefaultInjection.js +++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js @@ -27,7 +27,6 @@ var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactEmptyComponent = require('ReactEmptyComponent'); var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment'); -var ReactNativeComponentMixin = require('ReactNativeComponentMixin'); var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler'); var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler'); var ReactNativeMount = require('ReactNativeMount'); @@ -90,8 +89,6 @@ function inject() { EventPluginUtils.injection.injectMount(ReactNativeMount); - ReactClass.injection.injectMixin(ReactNativeComponentMixin); - ReactNativeComponent.injection.injectTextComponentClass( ReactNativeTextComponent ); From b1c93bb9fc233443e9a12a2e9625429e80e3459e Mon Sep 17 00:00:00 2001 From: James Ide Date: Thu, 14 May 2015 08:36:35 -0700 Subject: [PATCH 09/21] [Haste] @provides -> @providesModule StaticRenderer Summary: Pretty sure this shouldn't be @provides Closes https://github.com/facebook/react-native/pull/837 Github Author: James Ide Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Components/StaticRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/StaticRenderer.js b/Libraries/Components/StaticRenderer.js index 8800198a5..8f6cabde5 100644 --- a/Libraries/Components/StaticRenderer.js +++ b/Libraries/Components/StaticRenderer.js @@ -6,7 +6,7 @@ * 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. * - * @provides StaticRenderer + * @providesModule StaticRenderer * @flow */ 'use strict'; From f865da26baa796c0982f9b4a906f95647cc213aa Mon Sep 17 00:00:00 2001 From: Dave Sibiski Date: Thu, 14 May 2015 08:54:26 -0700 Subject: [PATCH 10/21] [NavigatorIOS] Fixes #1268 - Bug causing the leftButtonIcon to not appear Summary: Closes https://github.com/facebook/react-native/pull/1269 Github Author: Dave Sibiski Test Plan: Imported from GitHub, without a `Test Plan:` line. --- React/Views/RCTNavItem.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Views/RCTNavItem.m b/React/Views/RCTNavItem.m index 56346a363..f875e6aa5 100644 --- a/React/Views/RCTNavItem.m +++ b/React/Views/RCTNavItem.m @@ -56,7 +56,7 @@ - (void)setLeftButtonIcon:(UIImage *)leftButtonIcon { _leftButtonIcon = leftButtonIcon; - _leftButtonIcon = nil; + _leftButtonItem = nil; } - (UIBarButtonItem *)leftButtonItem From 55e280d200d54b3cf63557d79ad430e92f8ab266 Mon Sep 17 00:00:00 2001 From: James Ide Date: Thu, 14 May 2015 09:07:14 -0700 Subject: [PATCH 11/21] [UIExplorer] Fix app icon so it actually shows up on the home screen Summary: Generated icons of the proper sizes so they show up on the home screen. It's not beautiful since the images have a transparent background but this also makes build warnings go away. Closes https://github.com/facebook/react-native/pull/1267 Github Author: James Ide Test Plan: Run app, see icon on simulator home screen --- .../AppIcon.appiconset/Contents.json | 14 +++++++------- .../AppIcon.appiconset/Icon-40@2x.png | Bin 0 -> 10684 bytes .../AppIcon.appiconset/Icon-40@3x.png | Bin 0 -> 19981 bytes .../AppIcon.appiconset/Icon-60@2x.png | Bin 0 -> 19981 bytes .../{uie_icon@2x-1.png => Icon-60@3x.png} | Bin .../AppIcon.appiconset/Icon-Small@2x.png | Bin 0 -> 6520 bytes .../AppIcon.appiconset/Icon-Small@3x.png | Bin 0 -> 12464 bytes .../AppIcon.appiconset/uie_icon@2x-2.png | Bin 31759 -> 0 bytes .../AppIcon.appiconset/uie_icon@2x-3.png | Bin 31759 -> 0 bytes .../AppIcon.appiconset/uie_icon@2x-4.png | Bin 31759 -> 0 bytes .../AppIcon.appiconset/uie_icon@2x-5.png | Bin 31759 -> 0 bytes .../AppIcon.appiconset/uie_icon@2x.png | Bin 31759 -> 0 bytes 12 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png create mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png create mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png rename Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/{uie_icon@2x-1.png => Icon-60@3x.png} (100%) create mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png create mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png delete mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-2.png delete mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png delete mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png delete mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png delete mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Contents.json index 413d60e76..6654cff6e 100644 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Contents.json @@ -3,37 +3,37 @@ { "size" : "29x29", "idiom" : "iphone", - "filename" : "uie_icon@2x.png", + "filename" : "Icon-Small@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "uie_icon@2x-1.png", + "filename" : "Icon-Small@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "uie_icon@2x-2.png", + "filename" : "Icon-40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "uie_icon@2x-3.png", + "filename" : "Icon-40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "uie_icon@2x-5.png", + "filename" : "Icon-60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "uie_icon@2x-4.png", + "filename" : "Icon-60@3x.png", "scale" : "3x" } ], @@ -41,4 +41,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d4aa4cd71ad902313f8698e4d3e97bb07ebdd5c8 GIT binary patch literal 10684 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 literal 0 HcmV?d00001 diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8f9ee3c9c0c8c99164ab8e34805e456148511ff0 GIT binary patch literal 19981 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 literal 0 HcmV?d00001 diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png similarity index 100% rename from Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png rename to Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..15fb96f883d993ad4f2657a0eaae69732ac5f33a GIT binary patch literal 6520 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 diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png deleted file mode 100644 index 08a42699daa9ca2149e641e7236b63bf74bdb5f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31759 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 diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png deleted file mode 100644 index 08a42699daa9ca2149e641e7236b63bf74bdb5f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31759 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 diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png deleted file mode 100644 index 08a42699daa9ca2149e641e7236b63bf74bdb5f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31759 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 diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png deleted file mode 100644 index 08a42699daa9ca2149e641e7236b63bf74bdb5f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31759 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 From babdc2161445f839e76c35365dfa35b199d60dbe Mon Sep 17 00:00:00 2001 From: Harrison Harnisch Date: Thu, 14 May 2015 09:28:09 -0700 Subject: [PATCH 12/21] WebSocket polyfill Summary: - Added as a library in /Libraries/WebSocket - Drag and drop to add to project (similar to adding Geolocation polyfill) - Exposed as `window.WebSocket` which conforms with https://developer.mozilla.org/en-US/docs/Web/API/WebSocket specs Closes https://github.com/facebook/react-native/pull/890 Github Author: Harrison Harnisch Test Plan: Imported from GitHub, without a `Test Plan:` line. --- .../Movies/Movies.xcodeproj/project.pbxproj | 30 + .../SampleApp.xcodeproj/project.pbxproj | 60 +- .../UIExplorer.xcodeproj/project.pbxproj | 42 +- .../InitializeJavaScriptAppEngine.js | 33 +- .../project.pbxproj | 275 --- Libraries/RCTWebSocketDebugger/SRWebSocket.h | 132 -- Libraries/RCTWebSocketDebugger/SRWebSocket.m | 1779 ----------------- Libraries/WebSocket/RCTSRWebSocket.h | 132 ++ Libraries/WebSocket/RCTSRWebSocket.m | 1627 +++++++++++++++ .../RCTWebSocketExecutor.h | 2 +- .../RCTWebSocketExecutor.m | 23 +- Libraries/WebSocket/RCTWebSocketManager.h | 14 + Libraries/WebSocket/RCTWebSocketManager.m | 116 ++ Libraries/WebSocket/WebSocket.android.js | 39 + Libraries/WebSocket/WebSocket.ios.js | 104 + Libraries/WebSocket/WebSocketBase.js | 97 + React.podspec | 5 +- React/Base/RCTDevMenu.m | 25 +- 18 files changed, 2265 insertions(+), 2270 deletions(-) delete mode 100644 Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj delete mode 100644 Libraries/RCTWebSocketDebugger/SRWebSocket.h delete mode 100644 Libraries/RCTWebSocketDebugger/SRWebSocket.m create mode 100644 Libraries/WebSocket/RCTSRWebSocket.h create mode 100644 Libraries/WebSocket/RCTSRWebSocket.m rename Libraries/{RCTWebSocketDebugger => WebSocket}/RCTWebSocketExecutor.h (88%) rename Libraries/{RCTWebSocketDebugger => WebSocket}/RCTWebSocketExecutor.m (89%) create mode 100644 Libraries/WebSocket/RCTWebSocketManager.h create mode 100644 Libraries/WebSocket/RCTWebSocketManager.m create mode 100644 Libraries/WebSocket/WebSocket.android.js create mode 100644 Libraries/WebSocket/WebSocket.ios.js create mode 100644 Libraries/WebSocket/WebSocketBase.js diff --git a/Examples/Movies/Movies.xcodeproj/project.pbxproj b/Examples/Movies/Movies.xcodeproj/project.pbxproj index e86674f68..aaabcbead 100644 --- a/Examples/Movies/Movies.xcodeproj/project.pbxproj +++ b/Examples/Movies/Movies.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 1338BC281B04C72D0064A9C9 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1338BC261B04C7080064A9C9 /* libRCTWebSocket.a */; }; 1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341801B1AA91740003F314A /* libRCTNetwork.a */; }; 13442C061AA90EA00037E5B0 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13442C051AA90E7D0037E5B0 /* libRCTImage.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; @@ -19,6 +20,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 1338BC251B04C7080064A9C9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; 1341801A1AA91740003F314A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 134180151AA91740003F314A /* RCTNetwork.xcodeproj */; @@ -57,6 +65,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/Text/../WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 134180151AA91740003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; 13442C001AA90E7D0037E5B0 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Movies.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Movies.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -81,12 +90,21 @@ 1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */, 13442C061AA90EA00037E5B0 /* libRCTImage.a in Frameworks */, 58C5726B1AA6239E00CDF9C8 /* libRCTText.a in Frameworks */, + 1338BC281B04C72D0064A9C9 /* libRCTWebSocket.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1338BC1E1B04C7070064A9C9 /* Products */ = { + isa = PBXGroup; + children = ( + 1338BC261B04C7080064A9C9 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; 134180161AA91740003F314A /* Products */ = { isa = PBXGroup; children = ( @@ -140,6 +158,7 @@ 134180151AA91740003F314A /* RCTNetwork.xcodeproj */, 13442C001AA90E7D0037E5B0 /* RCTImage.xcodeproj */, 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */, + 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -228,6 +247,10 @@ ProductGroup = 58C572571AA6236500CDF9C8 /* Products */; ProjectRef = 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */; }, + { + ProductGroup = 1338BC1E1B04C7070064A9C9 /* Products */; + ProjectRef = 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */; + }, { ProductGroup = 14A2D43D1AC3E41A00CC738A /* Products */; ProjectRef = 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */; @@ -241,6 +264,13 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 1338BC261B04C7080064A9C9 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 1338BC251B04C7080064A9C9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 1341801B1AA91740003F314A /* libRCTNetwork.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj index 3928adc33..b51d08574 100644 --- a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj +++ b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 00481BE81AC0C86700671115 /* libRCTWebSocketDebugger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00481BE61AC0C7FA00671115 /* libRCTWebSocketDebugger.a */; }; 008F07F31AC5B25A0029DE68 /* main.jsbundle in Resources */ = {isa = PBXBuildFile; fileRef = 008F07F21AC5B25A0029DE68 /* main.jsbundle */; }; 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; 00C302E61ABCBA2D00DB3ED1 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302B41ABCB8E700DB3ED1 /* libRCTAdSupport.a */; }; @@ -16,6 +15,7 @@ 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; 00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* SampleAppTests.m */; }; + 1338BBC51B04AC310064A9C9 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1338BBB81B04A6AE0064A9C9 /* libRCTWebSocket.a */; }; 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; @@ -27,13 +27,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 00481BE51AC0C7FA00671115 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; - remoteInfo = RCTWebSocketDebugger; - }; 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; @@ -83,6 +76,13 @@ remoteGlobalIDString = 13B07F861A680F5B00A75B9A; remoteInfo = SampleApp; }; + 1338BBB71B04A6AE0064A9C9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; @@ -114,7 +114,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = ""; }; 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = iOS/main.jsbundle; sourceTree = ""; }; 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; 00C302AF1ABCB8E700DB3ED1 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; @@ -125,6 +124,7 @@ 00E356EE1AD99517003FC87E /* SampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* SampleAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleAppTests.m; sourceTree = ""; }; + 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = iOS/AppDelegate.h; sourceTree = ""; }; @@ -151,30 +151,22 @@ buildActionMask = 2147483647; files = ( 146834051AC3E58100842450 /* libReact.a in Frameworks */, - 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, - 00481BE81AC0C86700671115 /* libRCTWebSocketDebugger.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, 00C302E61ABCBA2D00DB3ED1 /* libRCTAdSupport.a in Frameworks */, 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, - 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 1338BBC51B04AC310064A9C9 /* libRCTWebSocket.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 00481BDC1AC0C7FA00671115 /* Products */ = { - isa = PBXGroup; - children = ( - 00481BE61AC0C7FA00671115 /* libRCTWebSocketDebugger.a */, - ); - name = Products; - sourceTree = ""; - }; 00C302A81ABCB8CE00DB3ED1 /* Products */ = { isa = PBXGroup; children = ( @@ -240,6 +232,14 @@ name = "Supporting Files"; sourceTree = ""; }; + 1338BBB41B04A6AE0064A9C9 /* Products */ = { + isa = PBXGroup; + children = ( + 1338BBB81B04A6AE0064A9C9 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; 139105B71AF99BAD00B5F7CC /* Products */ = { isa = PBXGroup; children = ( @@ -291,7 +291,7 @@ 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, - 00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */, + 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -427,8 +427,8 @@ ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; }, { - ProductGroup = 00481BDC1AC0C7FA00671115 /* Products */; - ProjectRef = 00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */; + ProductGroup = 1338BBB41B04A6AE0064A9C9 /* Products */; + ProjectRef = 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */; }, { ProductGroup = 146834001AC3E56700842450 /* Products */; @@ -444,13 +444,6 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 00481BE61AC0C7FA00671115 /* libRCTWebSocketDebugger.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTWebSocketDebugger.a; - remoteRef = 00481BE51AC0C7FA00671115 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -493,6 +486,13 @@ remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 1338BBB81B04A6AE0064A9C9 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 1338BBB71B04A6AE0064A9C9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 698fd3b3f..ca09dcaaf 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* UIExplorerTests.m */; }; - 00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D277131AB8C2C700DC1E48 /* libRCTWebSocketDebugger.a */; }; + 1338BBE31B04ACF90064A9C9 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1338BB9C1B04A6390064A9C9 /* libRCTWebSocket.a */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; @@ -34,12 +34,12 @@ remoteGlobalIDString = 13B07F861A680F5B00A75B9A; remoteInfo = UIExplorer; }; - 00D277121AB8C2C700DC1E48 /* PBXContainerItemProxy */ = { + 1338BB9B1B04A6390064A9C9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */; + containerPortal = 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */; proxyType = 2; - remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; - remoteInfo = RCTWebSocketDebugger; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; }; 13417FE71AA91428003F314A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -124,7 +124,7 @@ 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 004D28A21AAF61C70097A701 /* UIExplorerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIExplorerTests.m; sourceTree = ""; }; - 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = ""; }; + 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; @@ -159,7 +159,6 @@ files = ( 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */, 14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */, - 00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */, 58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */, D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */, 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */, @@ -169,6 +168,7 @@ 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */, 134180011AA9153C003F314A /* libRCTText.a in Frameworks */, 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */, + 1338BBE31B04ACF90064A9C9 /* libRCTWebSocket.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -192,14 +192,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 00D2770F1AB8C2C700DC1E48 /* Products */ = { - isa = PBXGroup; - children = ( - 00D277131AB8C2C700DC1E48 /* libRCTWebSocketDebugger.a */, - ); - name = Products; - sourceTree = ""; - }; 1316A21D1AA397F400C0188E /* Libraries */ = { isa = PBXGroup; children = ( @@ -213,12 +205,20 @@ 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */, 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */, 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, - 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */, D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */, + 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */, ); name = Libraries; sourceTree = ""; }; + 1338BB981B04A6390064A9C9 /* Products */ = { + isa = PBXGroup; + children = ( + 1338BB9C1B04A6390064A9C9 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; 13417FE41AA91428003F314A /* Products */ = { isa = PBXGroup; children = ( @@ -447,8 +447,8 @@ ProjectRef = D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */; }, { - ProductGroup = 00D2770F1AB8C2C700DC1E48 /* Products */; - ProjectRef = 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */; + ProductGroup = 1338BB981B04A6390064A9C9 /* Products */; + ProjectRef = 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */; }, { ProductGroup = 14AADF001AC3DB95002390C9 /* Products */; @@ -464,11 +464,11 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 00D277131AB8C2C700DC1E48 /* libRCTWebSocketDebugger.a */ = { + 1338BB9C1B04A6390064A9C9 /* libRCTWebSocket.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; - path = libRCTWebSocketDebugger.a; - remoteRef = 00D277121AB8C2C700DC1E48 /* PBXContainerItemProxy */; + path = libRCTWebSocket.a; + remoteRef = 1338BB9B1B04A6390064A9C9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 13417FE81AA91428003F314A /* libRCTImage.a */ = { diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index b810c5bc3..ec12a99e6 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -41,12 +41,12 @@ function handleErrorWithRedBox(e, isFatal) { } } -function setupRedBoxErrorHandler() { +function setUpRedBoxErrorHandler() { var ErrorUtils = require('ErrorUtils'); ErrorUtils.setGlobalHandler(handleErrorWithRedBox); } -function setupRedBoxConsoleErrorHandler() { +function setUpRedBoxConsoleErrorHandler() { // ExceptionsManager transitively requires Promise so we install it after var ExceptionsManager = require('ExceptionsManager'); var Platform = require('Platform'); @@ -63,7 +63,7 @@ function setupRedBoxConsoleErrorHandler() { * implement our own custom timing bridge that should be immune to * unexplainably dropped timing signals. */ -function setupTimers() { +function setUpTimers() { var JSTimers = require('JSTimers'); GLOBAL.setTimeout = JSTimers.setTimeout; GLOBAL.setInterval = JSTimers.setInterval; @@ -78,7 +78,7 @@ function setupTimers() { }; } -function setupAlert() { +function setUpAlert() { var RCTAlertManager = require('NativeModules').AlertManager; if (!GLOBAL.alert) { GLOBAL.alert = function(text) { @@ -92,13 +92,13 @@ function setupAlert() { } } -function setupPromise() { +function setUpPromise() { // The native Promise implementation throws the following error: // ERROR: Event loop not supported. GLOBAL.Promise = require('Promise'); } -function setupXHR() { +function setUpXHR() { // The native XMLHttpRequest in Chrome dev tools is CORS aware and won't // let you fetch anything from the internet GLOBAL.XMLHttpRequest = require('XMLHttpRequest'); @@ -110,15 +110,20 @@ function setupXHR() { GLOBAL.Response = fetchPolyfill.Response; } -function setupGeolocation() { +function setUpGeolocation() { GLOBAL.navigator = GLOBAL.navigator || {}; GLOBAL.navigator.geolocation = require('Geolocation'); } -setupRedBoxErrorHandler(); -setupTimers(); -setupAlert(); -setupPromise(); -setupXHR(); -setupRedBoxConsoleErrorHandler(); -setupGeolocation(); +function setUpWebSockets() { + GLOBAL.WebSocket = require('WebSocket'); +} + +setUpRedBoxErrorHandler(); +setUpTimers(); +setUpAlert(); +setUpPromise(); +setUpXHR(); +setUpRedBoxConsoleErrorHandler(); +setUpGeolocation(); +setUpWebSockets(); diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj b/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj deleted file mode 100644 index 38ac20c73..000000000 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj +++ /dev/null @@ -1,275 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */; }; - 13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20411AE707C5005F5298 /* SRWebSocket.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 832C817E1AAF6DEF007FA2F7 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketExecutor.h; sourceTree = ""; }; - 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketExecutor.m; sourceTree = ""; }; - 13AF20401AE707C5005F5298 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; - 13AF20411AE707C5005F5298 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; - 832C81801AAF6DEF007FA2F7 /* libRCTWebSocketDebugger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocketDebugger.a; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 832C817D1AAF6DEF007FA2F7 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 832C81771AAF6DEF007FA2F7 = { - isa = PBXGroup; - children = ( - 13AF20401AE707C5005F5298 /* SRWebSocket.h */, - 13AF20411AE707C5005F5298 /* SRWebSocket.m */, - 00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */, - 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */, - 832C81811AAF6DEF007FA2F7 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - }; - 832C81811AAF6DEF007FA2F7 /* Products */ = { - isa = PBXGroup; - children = ( - 832C81801AAF6DEF007FA2F7 /* libRCTWebSocketDebugger.a */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 832C817F1AAF6DEF007FA2F7 /* RCTWebSocketDebugger */ = { - isa = PBXNativeTarget; - buildConfigurationList = 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTWebSocketDebugger" */; - buildPhases = ( - 832C817C1AAF6DEF007FA2F7 /* Sources */, - 832C817D1AAF6DEF007FA2F7 /* Frameworks */, - 832C817E1AAF6DEF007FA2F7 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RCTWebSocketDebugger; - productName = RCTWebSocketDebugger; - productReference = 832C81801AAF6DEF007FA2F7 /* libRCTWebSocketDebugger.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 832C81781AAF6DEF007FA2F7 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0620; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 832C817F1AAF6DEF007FA2F7 = { - CreatedOnToolsVersion = 6.2; - }; - }; - }; - buildConfigurationList = 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTWebSocketDebugger" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 832C81771AAF6DEF007FA2F7; - productRefGroup = 832C81811AAF6DEF007FA2F7 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 832C817F1AAF6DEF007FA2F7 /* RCTWebSocketDebugger */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 832C817C1AAF6DEF007FA2F7 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */, - 00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 832C81921AAF6DF0007FA2F7 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 8.2; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 832C81931AAF6DF0007FA2F7 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 8.2; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 832C81951AAF6DF0007FA2F7 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-llibicucore", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - RUN_CLANG_STATIC_ANALYZER = YES; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 832C81961AAF6DF0007FA2F7 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-llibicucore", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTWebSocketDebugger" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 832C81921AAF6DF0007FA2F7 /* Debug */, - 832C81931AAF6DF0007FA2F7 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTWebSocketDebugger" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 832C81951AAF6DF0007FA2F7 /* Debug */, - 832C81961AAF6DF0007FA2F7 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 832C81781AAF6DEF007FA2F7 /* Project object */; -} diff --git a/Libraries/RCTWebSocketDebugger/SRWebSocket.h b/Libraries/RCTWebSocketDebugger/SRWebSocket.h deleted file mode 100644 index 5cce725a3..000000000 --- a/Libraries/RCTWebSocketDebugger/SRWebSocket.h +++ /dev/null @@ -1,132 +0,0 @@ -// -// Copyright 2012 Square Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import -#import - -typedef enum { - SR_CONNECTING = 0, - SR_OPEN = 1, - SR_CLOSING = 2, - SR_CLOSED = 3, -} SRReadyState; - -typedef enum SRStatusCode : NSInteger { - SRStatusCodeNormal = 1000, - SRStatusCodeGoingAway = 1001, - SRStatusCodeProtocolError = 1002, - SRStatusCodeUnhandledType = 1003, - // 1004 reserved. - SRStatusNoStatusReceived = 1005, - // 1004-1006 reserved. - SRStatusCodeInvalidUTF8 = 1007, - SRStatusCodePolicyViolated = 1008, - SRStatusCodeMessageTooBig = 1009, -} SRStatusCode; - -@class SRWebSocket; - -extern NSString *const SRWebSocketErrorDomain; -extern NSString *const SRHTTPResponseErrorKey; - -#pragma mark - SRWebSocketDelegate - -@protocol SRWebSocketDelegate; - -#pragma mark - SRWebSocket - -@interface SRWebSocket : NSObject - -@property (nonatomic, weak) id delegate; - -@property (nonatomic, readonly) SRReadyState readyState; -@property (nonatomic, readonly, retain) NSURL *url; - -// This returns the negotiated protocol. -// It will be nil until after the handshake completes. -@property (nonatomic, readonly, copy) NSString *protocol; - -// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. -- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; -- (id)initWithURLRequest:(NSURLRequest *)request; - -// Some helper constructors. -- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; -- (id)initWithURL:(NSURL *)url; - -// Delegate queue will be dispatch_main_queue by default. -// You cannot set both OperationQueue and dispatch_queue. -- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; -- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; - -// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes. -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; -- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - -// SRWebSockets are intended for one-time-use only. Open should be called once and only once. -- (void)open; - -- (void)close; -- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; - -// Send a UTF8 String or Data. -- (void)send:(id)data; - -// Send Data (can be nil) in a ping message. -- (void)sendPing:(NSData *)data; - -@end - -#pragma mark - SRWebSocketDelegate - -@protocol SRWebSocketDelegate - -// message will either be an NSString if the server is using text -// or NSData if the server is using binary. -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; - -@optional - -- (void)webSocketDidOpen:(SRWebSocket *)webSocket; -- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; -- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; -- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload; - -@end - -#pragma mark - NSURLRequest (CertificateAdditions) - -@interface NSURLRequest (CertificateAdditions) - -@property (nonatomic, retain, readonly) NSArray *SR_SSLPinnedCertificates; - -@end - -#pragma mark - NSMutableURLRequest (CertificateAdditions) - -@interface NSMutableURLRequest (CertificateAdditions) - -@property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates; - -@end - -#pragma mark - NSRunLoop (SRWebSocket) - -@interface NSRunLoop (SRWebSocket) - -+ (NSRunLoop *)SR_networkRunLoop; - -@end diff --git a/Libraries/RCTWebSocketDebugger/SRWebSocket.m b/Libraries/RCTWebSocketDebugger/SRWebSocket.m deleted file mode 100644 index e98e9e456..000000000 --- a/Libraries/RCTWebSocketDebugger/SRWebSocket.m +++ /dev/null @@ -1,1779 +0,0 @@ -// -// Copyright 2012 Square Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - - -#import "SRWebSocket.h" - -#import - -#pragma clang diagnostic ignored "-Wshadow" - -//NOTE: libicucore ins't actually needed for the socket to function -//and by commenting this out, we avoid the need to import it into every app. - -//#if TARGET_OS_IPHONE -//#define HAS_ICU -//#endif - -#ifdef HAS_ICU -#import -#endif - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -#import -#import - -#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE -#define sr_dispatch_retain(x) -#define sr_dispatch_release(x) -#define maybe_bridge(x) ((__bridge void *) x) -#else -#define sr_dispatch_retain(x) dispatch_retain(x) -#define sr_dispatch_release(x) dispatch_release(x) -#define maybe_bridge(x) (x) -#endif - -#if !__has_feature(objc_arc) -#error SocketRocket must be compiled with ARC enabled -#endif - - -typedef enum { - SROpCodeTextFrame = 0x1, - SROpCodeBinaryFrame = 0x2, - // 3-7 reserved. - SROpCodeConnectionClose = 0x8, - SROpCodePing = 0x9, - SROpCodePong = 0xA, - // B-F reserved. -} SROpCode; - -typedef struct { - BOOL fin; -// BOOL rsv1; -// BOOL rsv2; -// BOOL rsv3; - uint8_t opcode; - BOOL masked; - uint64_t payload_length; -} frame_header; - -static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - -static inline int32_t validate_dispatch_data_partial_string(NSData *data); -static inline void SRFastLog(NSString *format, ...); - -@interface NSData (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; - -@end - - -@interface NSString (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; - -@end - - -@interface NSURL (SRWebSocket) - -// The origin isn't really applicable for a native application. -// So instead, just map ws -> http and wss -> https. -- (NSString *)SR_origin; - -@end - - -@interface _SRRunLoopThread : NSThread - -@property (nonatomic, readonly) NSRunLoop *runLoop; - -@end - - -static NSString *newSHA1String(const char *bytes, size_t length) { - uint8_t md[CC_SHA1_DIGEST_LENGTH]; - - assert(length >= 0); - assert(length <= UINT32_MAX); - CC_SHA1(bytes, (CC_LONG)length, md); - - NSData *data = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH]; - -#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0) \ - || (__MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9) - - if (![NSData instancesRespondToSelector:@selector(base64EncodedStringWithOptions:)]) { - return [data base64Encoding]; - } - -#endif - - return [data base64EncodedStringWithOptions:0]; -} - -@implementation NSData (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; -{ - return newSHA1String(self.bytes, self.length); -} - -@end - - -@implementation NSString (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; -{ - return newSHA1String(self.UTF8String, self.length); -} - -@end - -NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; -NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; - -// Returns number of bytes consumed. Returning 0 means you didn't match. -// Sends bytes to callback handler; -typedef size_t (^stream_scanner)(NSData *collected_data); - -typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data); - -@interface SRIOConsumer : NSObject { - stream_scanner _scanner; - data_callback _handler; - size_t _bytesNeeded; - BOOL _readToCurrentFrame; - BOOL _unmaskBytes; -} -@property (nonatomic, copy, readonly) stream_scanner consumer; -@property (nonatomic, copy, readonly) data_callback handler; -@property (nonatomic, assign) size_t bytesNeeded; -@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; -@property (nonatomic, assign, readonly) BOOL unmaskBytes; - -@end - -// This class is not thread-safe, and is expected to always be run on the same queue. -@interface SRIOConsumerPool : NSObject - -- (id)initWithBufferCapacity:(NSUInteger)poolSize; - -- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -- (void)returnConsumer:(SRIOConsumer *)consumer; - -@end - -@interface SRWebSocket () - -- (void)_writeData:(NSData *)data; -- (void)_closeWithProtocolError:(NSString *)message; -- (void)_failWithError:(NSError *)error; - -- (void)_disconnect; - -- (void)_readFrameNew; -- (void)_readFrameContinue; - -- (void)_pumpScanner; - -- (void)_pumpWriting; - -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; -- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; -- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; -- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; - -- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; - -- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; -- (void)_SR_commonInit; - -- (void)_initializeStreams; -- (void)_connect; - -@property (nonatomic) SRReadyState readyState; - -@property (nonatomic) NSOperationQueue *delegateOperationQueue; -@property (nonatomic) dispatch_queue_t delegateDispatchQueue; - -@end - - -@implementation SRWebSocket { - NSInteger _webSocketVersion; - - NSOperationQueue *_delegateOperationQueue; - dispatch_queue_t _delegateDispatchQueue; - - dispatch_queue_t _workQueue; - NSMutableArray *_consumers; - - NSInputStream *_inputStream; - NSOutputStream *_outputStream; - - NSMutableData *_readBuffer; - NSUInteger _readBufferOffset; - - NSMutableData *_outputBuffer; - NSUInteger _outputBufferOffset; - - uint8_t _currentFrameOpcode; - size_t _currentFrameCount; - size_t _readOpCount; - uint32_t _currentStringScanPosition; - NSMutableData *_currentFrameData; - - NSString *_closeReason; - - NSString *_secKey; - - BOOL _pinnedCertFound; - - uint8_t _currentReadMaskKey[4]; - size_t _currentReadMaskOffset; - - BOOL _consumerStopped; - - BOOL _closeWhenFinishedWriting; - BOOL _failed; - - BOOL _secure; - NSURLRequest *_urlRequest; - - CFHTTPMessageRef _receivedHTTPHeaders; - - BOOL _sentClose; - BOOL _didFail; - int _closeCode; - - BOOL _isPumping; - - NSMutableSet *_scheduledRunloops; - - // We use this to retain ourselves. - __strong SRWebSocket *_selfRetain; - - NSArray *_requestedProtocols; - SRIOConsumerPool *_consumerPool; -} - -@synthesize delegate = _delegate; -@synthesize url = _url; -@synthesize readyState = _readyState; -@synthesize protocol = _protocol; - -static __strong NSData *CRLFCRLF; - -+ (void)initialize; -{ - CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; -} - -- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; -{ - self = [super init]; - if (self) { - assert(request.URL); - _url = request.URL; - _urlRequest = request; - - _requestedProtocols = [protocols copy]; - - [self _SR_commonInit]; - } - - return self; -} - -- (id)initWithURLRequest:(NSURLRequest *)request; -{ - return [self initWithURLRequest:request protocols:nil]; -} - -- (id)initWithURL:(NSURL *)url; -{ - return [self initWithURL:url protocols:nil]; -} - -- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; -{ - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; - return [self initWithURLRequest:request protocols:protocols]; -} - -- (void)_SR_commonInit; -{ - NSString *scheme = _url.scheme.lowercaseString; - assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); - - if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { - _secure = YES; - } - - _readyState = SR_CONNECTING; - _consumerStopped = YES; - _webSocketVersion = 13; - - _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); - - // Going to set a specific on the queue so we can validate we're on the work queue - dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL); - - _delegateDispatchQueue = dispatch_get_main_queue(); - sr_dispatch_retain(_delegateDispatchQueue); - - _readBuffer = [[NSMutableData alloc] init]; - _outputBuffer = [[NSMutableData alloc] init]; - - _currentFrameData = [[NSMutableData alloc] init]; - - _consumers = [[NSMutableArray alloc] init]; - - _consumerPool = [[SRIOConsumerPool alloc] init]; - - _scheduledRunloops = [[NSMutableSet alloc] init]; - - [self _initializeStreams]; - - // default handlers -} - -- (void)assertOnWorkQueue; -{ - assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue)); -} - -- (void)dealloc -{ - _inputStream.delegate = nil; - _outputStream.delegate = nil; - - [_inputStream close]; - [_outputStream close]; - - sr_dispatch_release(_workQueue); - _workQueue = NULL; - - if (_receivedHTTPHeaders) { - CFRelease(_receivedHTTPHeaders); - _receivedHTTPHeaders = NULL; - } - - if (_delegateDispatchQueue) { - sr_dispatch_release(_delegateDispatchQueue); - _delegateDispatchQueue = NULL; - } -} - -#ifndef NDEBUG - -- (void)setReadyState:(SRReadyState)aReadyState; -{ - [self willChangeValueForKey:@"readyState"]; - assert(aReadyState > _readyState); - _readyState = aReadyState; - [self didChangeValueForKey:@"readyState"]; -} - -#endif - -- (void)open; -{ - assert(_url); - NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once"); - - _selfRetain = self; - - [self _connect]; -} - -// Calls block on delegate queue -- (void)_performDelegateBlock:(dispatch_block_t)block; -{ - if (_delegateOperationQueue) { - [_delegateOperationQueue addOperationWithBlock:block]; - } else { - assert(_delegateDispatchQueue); - dispatch_async(_delegateDispatchQueue, block); - } -} - -- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; -{ - if (queue) { - sr_dispatch_retain(queue); - } - - if (_delegateDispatchQueue) { - sr_dispatch_release(_delegateDispatchQueue); - } - - _delegateDispatchQueue = queue; -} - -- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; -{ - NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); - - if (acceptHeader == nil) { - return NO; - } - - NSString *concattedString = [_secKey stringByAppendingString:SRWebSocketAppendToSecKeyString]; - NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; - - return [acceptHeader isEqualToString:expectedAccept]; -} - -- (void)_HTTPHeadersDidFinish; -{ - NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); - - if (responseCode >= 400) { - SRFastLog(@"Request failed with response code %d", responseCode); - [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode], SRHTTPResponseErrorKey:@(responseCode)}]]; - return; - } - - if(![self _checkHandshake:_receivedHTTPHeaders]) { - [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; - return; - } - - NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); - if (negotiatedProtocol) { - // Make sure we requested the protocol - if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { - [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]]; - return; - } - - _protocol = negotiatedProtocol; - } - - self.readyState = SR_OPEN; - - if (!_didFail) { - [self _readFrameNew]; - } - - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { - [self.delegate webSocketDidOpen:self]; - }; - }]; -} - - -- (void)_readHTTPHeader; -{ - if (_receivedHTTPHeaders == NULL) { - _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); - } - - [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) { - CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); - - if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { - SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); - [self _HTTPHeadersDidFinish]; - } else { - [self _readHTTPHeader]; - } - }]; -} - -- (void)didConnect -{ - SRFastLog(@"Connected"); - CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); - - // Set host first so it defaults - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); - - NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; - SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); - -#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0) \ - || (__MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9) - - if (![NSData instancesRespondToSelector:@selector(base64EncodedStringWithOptions:)]) { - _secKey = [keyBytes base64Encoding]; - } else - -#endif - - { - _secKey = [keyBytes base64EncodedStringWithOptions:0]; - } - - assert([_secKey length] == 24); - - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); - - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin); - - if (_requestedProtocols) { - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); - } - - [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); - }]; - - NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); - - CFRelease(request); - - [self _writeData:message]; - [self _readHTTPHeader]; -} - -- (void)_initializeStreams; -{ - assert(_url.port.unsignedIntValue <= UINT32_MAX); - uint32_t port = _url.port.unsignedIntValue; - if (port == 0) { - if (!_secure) { - port = 80; - } else { - port = 443; - } - } - NSString *host = _url.host; - - CFReadStreamRef readStream = NULL; - CFWriteStreamRef writeStream = NULL; - - CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); - - _outputStream = CFBridgingRelease(writeStream); - _inputStream = CFBridgingRelease(readStream); - - - if (_secure) { - NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; - - [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; - - // If we're using pinned certs, don't validate the certificate chain - if ([_urlRequest SR_SSLPinnedCertificates].count) { - [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; - } - -#if DEBUG - [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; - NSLog(@"SocketRocket: In debug mode. Allowing connection to any root cert"); -#endif - - [_outputStream setProperty:SSLOptions - forKey:(__bridge id)kCFStreamPropertySSLSettings]; - } - - _inputStream.delegate = self; - _outputStream.delegate = self; -} - -- (void)_connect; -{ - if (!_scheduledRunloops.count) { - [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; - } - - - [_outputStream open]; - [_inputStream open]; -} - -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; -{ - [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; - [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; - - [_scheduledRunloops addObject:@[aRunLoop, mode]]; -} - -- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; -{ - [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; - [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; - - [_scheduledRunloops removeObject:@[aRunLoop, mode]]; -} - -- (void)close; -{ - [self closeWithCode:SRStatusCodeNormal reason:nil]; -} - -- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; -{ - assert(code); - dispatch_async(_workQueue, ^{ - if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { - return; - } - - BOOL wasConnecting = self.readyState == SR_CONNECTING; - - self.readyState = SR_CLOSING; - - SRFastLog(@"Closing with code %d reason %@", code, reason); - - if (wasConnecting) { - [self _disconnect]; - return; - } - - size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; - NSData *payload = mutablePayload; - - ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); - - if (reason) { - NSRange remainingRange = {0}; - - NSUInteger usedLength = 0; - - BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; - - assert(success); - assert(remainingRange.length == 0); - - if (usedLength != maxMsgSize) { - payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; - } - } - - - [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; - }); -} - -- (void)_closeWithProtocolError:(NSString *)message; -{ - // Need to shunt this on the _callbackQueue first to see if they received any messages - [self _performDelegateBlock:^{ - [self closeWithCode:SRStatusCodeProtocolError reason:message]; - dispatch_async(_workQueue, ^{ - [self _disconnect]; - }); - }]; -} - -- (void)_failWithError:(NSError *)error; -{ - dispatch_async(_workQueue, ^{ - if (self.readyState != SR_CLOSED) { - _failed = YES; - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { - [self.delegate webSocket:self didFailWithError:error]; - } - }]; - - self.readyState = SR_CLOSED; - _selfRetain = nil; - - SRFastLog(@"Failing with error %@", error.localizedDescription); - - [self _disconnect]; - } - }); -} - -- (void)_writeData:(NSData *)data; -{ - [self assertOnWorkQueue]; - - if (_closeWhenFinishedWriting) { - return; - } - [_outputBuffer appendData:data]; - [self _pumpWriting]; -} - -- (void)send:(id)data; -{ - NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); - // TODO: maybe not copy this for performance - data = [data copy]; - dispatch_async(_workQueue, ^{ - if ([data isKindOfClass:[NSString class]]) { - [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; - } else if ([data isKindOfClass:[NSData class]]) { - [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; - } else if (data == nil) { - [self _sendFrameWithOpcode:SROpCodeTextFrame data:data]; - } else { - assert(NO); - } - }); -} - -- (void)sendPing:(NSData *)data; -{ - NSAssert(self.readyState == SR_OPEN, @"Invalid State: Cannot call send: until connection is open"); - // TODO: maybe not copy this for performance - data = [data copy] ?: [NSData data]; // It's okay for a ping to be empty - dispatch_async(_workQueue, ^{ - [self _sendFrameWithOpcode:SROpCodePing data:data]; - }); -} - -- (void)handlePing:(NSData *)pingData; -{ - // Need to pingpong this off _callbackQueue first to make sure messages happen in order - [self _performDelegateBlock:^{ - dispatch_async(_workQueue, ^{ - [self _sendFrameWithOpcode:SROpCodePong data:pingData]; - }); - }]; -} - -- (void)handlePong:(NSData *)pongData; -{ - SRFastLog(@"Received pong"); - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didReceivePong:)]) { - [self.delegate webSocket:self didReceivePong:pongData]; - } - }]; -} - -- (void)_handleMessage:(id)message -{ - SRFastLog(@"Received message"); - [self _performDelegateBlock:^{ - [self.delegate webSocket:self didReceiveMessage:message]; - }]; -} - - -static inline BOOL closeCodeIsValid(int closeCode) { - if (closeCode < 1000) { - return NO; - } - - if (closeCode >= 1000 && closeCode <= 1011) { - if (closeCode == 1004 || - closeCode == 1005 || - closeCode == 1006) { - return NO; - } - return YES; - } - - if (closeCode >= 3000 && closeCode <= 3999) { - return YES; - } - - if (closeCode >= 4000 && closeCode <= 4999) { - return YES; - } - - return NO; -} - -// Note from RFC: -// -// If there is a body, the first two -// bytes of the body MUST be a 2-byte unsigned integer (in network byte -// order) representing a status code with value /code/ defined in -// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 -// encoded data with value /reason/, the interpretation of which is not -// defined by this specification. - -- (void)handleCloseWithData:(NSData *)data; -{ - size_t dataSize = data.length; - __block uint16_t closeCode = 0; - - SRFastLog(@"Received close frame"); - - if (dataSize == 1) { - // TODO handle error - [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; - return; - } else if (dataSize >= 2) { - [data getBytes:&closeCode length:sizeof(closeCode)]; - _closeCode = EndianU16_BtoN(closeCode); - if (!closeCodeIsValid(_closeCode)) { - [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; - return; - } - if (dataSize > 2) { - _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; - if (!_closeReason) { - [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; - return; - } - } - } else { - _closeCode = SRStatusNoStatusReceived; - } - - [self assertOnWorkQueue]; - - if (self.readyState == SR_OPEN) { - [self closeWithCode:1000 reason:nil]; - } - dispatch_async(_workQueue, ^{ - [self _disconnect]; - }); -} - -- (void)_disconnect; -{ - [self assertOnWorkQueue]; - SRFastLog(@"Trying to disconnect"); - _closeWhenFinishedWriting = YES; - [self _pumpWriting]; -} - -- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; -{ - // Check that the current data is valid UTF8 - - BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose); - if (!isControlFrame) { - [self _readFrameNew]; - } else { - dispatch_async(_workQueue, ^{ - [self _readFrameContinue]; - }); - } - - switch (opcode) { - case SROpCodeTextFrame: { - NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; - if (str == nil && frameData) { - [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; - dispatch_async(_workQueue, ^{ - [self _disconnect]; - }); - - return; - } - [self _handleMessage:str]; - break; - } - case SROpCodeBinaryFrame: - [self _handleMessage:[frameData copy]]; - break; - case SROpCodeConnectionClose: - [self handleCloseWithData:frameData]; - break; - case SROpCodePing: - [self handlePing:frameData]; - break; - case SROpCodePong: - [self handlePong:frameData]; - break; - default: - [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]]; - // TODO: Handle invalid opcode - break; - } -} - -- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; -{ - assert(frame_header.opcode != 0); - - if (self.readyState != SR_OPEN) { - return; - } - - - BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.opcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose); - - if (isControlFrame && !frame_header.fin) { - [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; - return; - } - - if (isControlFrame && frame_header.payload_length >= 126) { - [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; - return; - } - - if (!isControlFrame) { - _currentFrameOpcode = frame_header.opcode; - _currentFrameCount += 1; - } - - if (frame_header.payload_length == 0) { - if (isControlFrame) { - [self _handleFrameWithData:curData opCode:frame_header.opcode]; - } else { - if (frame_header.fin) { - [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; - } else { - // TODO add assert that opcode is not a control; - [self _readFrameContinue]; - } - } - } else { - assert(frame_header.payload_length <= SIZE_T_MAX); - [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) { - if (isControlFrame) { - [self _handleFrameWithData:newData opCode:frame_header.opcode]; - } else { - if (frame_header.fin) { - [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode]; - } else { - // TODO add assert that opcode is not a control; - [self _readFrameContinue]; - } - - } - } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; - } -} - -/* From RFC: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ - */ - -static const uint8_t SRFinMask = 0x80; -static const uint8_t SROpCodeMask = 0x0F; -static const uint8_t SRRsvMask = 0x70; -static const uint8_t SRMaskMask = 0x80; -static const uint8_t SRPayloadLenMask = 0x7F; - - -- (void)_readFrameContinue; -{ - assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); - - [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) { - __block frame_header header = {0}; - - const uint8_t *headerBuffer = data.bytes; - assert(data.length >= 2); - - if (headerBuffer[0] & SRRsvMask) { - [self _closeWithProtocolError:@"Server used RSV bits"]; - return; - } - - uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]); - - BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose); - - if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) { - [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; - return; - } - - if (receivedOpcode == 0 && self->_currentFrameCount == 0) { - [self _closeWithProtocolError:@"cannot continue a message"]; - return; - } - - header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode; - - header.fin = !!(SRFinMask & headerBuffer[0]); - - - header.masked = !!(SRMaskMask & headerBuffer[1]); - header.payload_length = SRPayloadLenMask & headerBuffer[1]; - - headerBuffer = NULL; - - if (header.masked) { - [self _closeWithProtocolError:@"Client must receive unmasked data"]; - } - - size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; - - if (header.payload_length == 126) { - extra_bytes_needed += sizeof(uint16_t); - } else if (header.payload_length == 127) { - extra_bytes_needed += sizeof(uint64_t); - } - - if (extra_bytes_needed == 0) { - [self _handleFrameHeader:header curData:self->_currentFrameData]; - } else { - [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) { - size_t mapped_size = data.length; - const void *mapped_buffer = data.bytes; - size_t offset = 0; - - if (header.payload_length == 126) { - assert(mapped_size >= sizeof(uint16_t)); - uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); - header.payload_length = newLen; - offset += sizeof(uint16_t); - } else if (header.payload_length == 127) { - assert(mapped_size >= sizeof(uint64_t)); - header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); - offset += sizeof(uint64_t); - } else { - assert(header.payload_length < 126 && header.payload_length >= 0); - } - - - if (header.masked) { - assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); - memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey)); - } - - [self _handleFrameHeader:header curData:self->_currentFrameData]; - } readToCurrentFrame:NO unmaskBytes:NO]; - } - } readToCurrentFrame:NO unmaskBytes:NO]; -} - -- (void)_readFrameNew; -{ - dispatch_async(_workQueue, ^{ - [_currentFrameData setLength:0]; - - _currentFrameOpcode = 0; - _currentFrameCount = 0; - _readOpCount = 0; - _currentStringScanPosition = 0; - - [self _readFrameContinue]; - }); -} - -- (void)_pumpWriting; -{ - [self assertOnWorkQueue]; - - NSUInteger dataLength = _outputBuffer.length; - if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { - NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; - if (bytesWritten == -1) { - [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; - return; - } - - _outputBufferOffset += bytesWritten; - - if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { - _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; - _outputBufferOffset = 0; - } - } - - if (_closeWhenFinishedWriting && - _outputBuffer.length - _outputBufferOffset == 0 && - (_inputStream.streamStatus != NSStreamStatusNotOpen && - _inputStream.streamStatus != NSStreamStatusClosed) && - !_sentClose) { - _sentClose = YES; - - [_outputStream close]; - [_inputStream close]; - - - for (NSArray *runLoop in [_scheduledRunloops copy]) { - [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; - } - - if (!_failed) { - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { - [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; - } - }]; - } - - _selfRetain = nil; - } -} - -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; -{ - [self assertOnWorkQueue]; - [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; -} - -- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -{ - [self assertOnWorkQueue]; - assert(dataLength); - - [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; - [self _pumpScanner]; -} - -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; -{ - [self assertOnWorkQueue]; - [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; - [self _pumpScanner]; -} - - -static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; - -- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; -{ - [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; -} - -- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; -{ - // TODO optimize so this can continue from where we last searched - stream_scanner consumer = ^size_t(NSData *data) { - __block size_t found_size = 0; - __block size_t match_count = 0; - - size_t size = data.length; - const unsigned char *buffer = data.bytes; - for (size_t i = 0; i < size; i++ ) { - if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { - match_count += 1; - if (match_count == length) { - found_size = i + 1; - break; - } - } else { - match_count = 0; - } - } - return found_size; - }; - [self _addConsumerWithScanner:consumer callback:dataHandler]; -} - - -// Returns true if did work -- (BOOL)_innerPumpScanner { - - BOOL didWork = NO; - - if (self.readyState >= SR_CLOSING) { - return didWork; - } - - if (!_consumers.count) { - return didWork; - } - - size_t curSize = _readBuffer.length - _readBufferOffset; - if (!curSize) { - return didWork; - } - - SRIOConsumer *consumer = [_consumers objectAtIndex:0]; - - size_t bytesNeeded = consumer.bytesNeeded; - - size_t foundSize = 0; - if (consumer.consumer) { - NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; - foundSize = consumer.consumer(tempView); - } else { - assert(consumer.bytesNeeded); - if (curSize >= bytesNeeded) { - foundSize = bytesNeeded; - } else if (consumer.readToCurrentFrame) { - foundSize = curSize; - } - } - - NSData *slice = nil; - if (consumer.readToCurrentFrame || foundSize) { - NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); - slice = [_readBuffer subdataWithRange:sliceRange]; - - _readBufferOffset += foundSize; - - if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { - _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; - } - - if (consumer.unmaskBytes) { - NSMutableData *mutableSlice = [slice mutableCopy]; - - NSUInteger len = mutableSlice.length; - uint8_t *bytes = mutableSlice.mutableBytes; - - for (NSUInteger i = 0; i < len; i++) { - bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; - _currentReadMaskOffset += 1; - } - - slice = mutableSlice; - } - - if (consumer.readToCurrentFrame) { - [_currentFrameData appendData:slice]; - - _readOpCount += 1; - - if (_currentFrameOpcode == SROpCodeTextFrame) { - // Validate UTF8 stuff. - size_t currentDataSize = _currentFrameData.length; - if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) { - // TODO: Optimize the crap out of this. Don't really have to copy all the data each time - - size_t scanSize = currentDataSize - _currentStringScanPosition; - - NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; - int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); - - if (valid_utf8_size == -1) { - [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; - dispatch_async(_workQueue, ^{ - [self _disconnect]; - }); - return didWork; - } else { - _currentStringScanPosition += valid_utf8_size; - } - } - - } - - consumer.bytesNeeded -= foundSize; - - if (consumer.bytesNeeded == 0) { - [_consumers removeObjectAtIndex:0]; - consumer.handler(self, nil); - [_consumerPool returnConsumer:consumer]; - didWork = YES; - } - } else if (foundSize) { - [_consumers removeObjectAtIndex:0]; - consumer.handler(self, slice); - [_consumerPool returnConsumer:consumer]; - didWork = YES; - } - } - return didWork; -} - --(void)_pumpScanner; -{ - [self assertOnWorkQueue]; - - if (!_isPumping) { - _isPumping = YES; - } else { - return; - } - - while ([self _innerPumpScanner]) { - - } - - _isPumping = NO; -} - -//#define NOMASK - -static const size_t SRFrameHeaderOverhead = 32; - -- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; -{ - [self assertOnWorkQueue]; - - if (nil == data) { - return; - } - - NSAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"NSString or NSData"); - - size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; - - NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; - if (!frame) { - [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; - return; - } - uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; - - // set fin - frame_buffer[0] = SRFinMask | opcode; - - BOOL useMask = YES; -#ifdef NOMASK - useMask = NO; -#endif - - if (useMask) { - // set the mask and header - frame_buffer[1] |= SRMaskMask; - } - - size_t frame_buffer_size = 2; - - const uint8_t *unmasked_payload = NULL; - if ([data isKindOfClass:[NSData class]]) { - unmasked_payload = (uint8_t *)[data bytes]; - } else if ([data isKindOfClass:[NSString class]]) { - unmasked_payload = (const uint8_t *)[data UTF8String]; - } else { - return; - } - - if (payloadLength < 126) { - frame_buffer[1] |= payloadLength; - } else if (payloadLength <= UINT16_MAX) { - frame_buffer[1] |= 126; - *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); - frame_buffer_size += sizeof(uint16_t); - } else { - frame_buffer[1] |= 127; - *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); - frame_buffer_size += sizeof(uint64_t); - } - - if (!useMask) { - for (size_t i = 0; i < payloadLength; i++) { - frame_buffer[frame_buffer_size] = unmasked_payload[i]; - frame_buffer_size += 1; - } - } else { - uint8_t *mask_key = frame_buffer + frame_buffer_size; - SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); - frame_buffer_size += sizeof(uint32_t); - - // TODO: could probably optimize this with SIMD - for (size_t i = 0; i < payloadLength; i++) { - frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; - frame_buffer_size += 1; - } - } - - assert(frame_buffer_size <= [frame length]); - frame.length = frame_buffer_size; - - [self _writeData:frame]; -} - -- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; -{ - if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { - - NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates]; - if (sslCerts) { - SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; - if (secTrust) { - NSInteger numCerts = SecTrustGetCertificateCount(secTrust); - for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); - NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); - - for (id ref in sslCerts) { - SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; - NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); - - if ([trustedCertData isEqualToData:certData]) { - _pinnedCertFound = YES; - break; - } - } - } - } - - if (!_pinnedCertFound) { - dispatch_async(_workQueue, ^{ - [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]]; - }); - return; - } - } - } - - dispatch_async(_workQueue, ^{ - switch (eventCode) { - case NSStreamEventOpenCompleted: { - SRFastLog(@"NSStreamEventOpenCompleted %@", aStream); - if (self.readyState >= SR_CLOSING) { - return; - } - assert(_readBuffer); - - if (self.readyState == SR_CONNECTING && aStream == _inputStream) { - [self didConnect]; - } - [self _pumpWriting]; - [self _pumpScanner]; - break; - } - - case NSStreamEventErrorOccurred: { - SRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); - /// TODO specify error better! - [self _failWithError:aStream.streamError]; - _readBufferOffset = 0; - [_readBuffer setLength:0]; - break; - - } - - case NSStreamEventEndEncountered: { - [self _pumpScanner]; - SRFastLog(@"NSStreamEventEndEncountered %@", aStream); - if (aStream.streamError) { - [self _failWithError:aStream.streamError]; - } else { - if (self.readyState != SR_CLOSED) { - self.readyState = SR_CLOSED; - _selfRetain = nil; - } - - if (!_sentClose && !_failed) { - _sentClose = YES; - // If we get closed in this state it's probably not clean because we should be sending this when we send messages - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { - [self.delegate webSocket:self didCloseWithCode:SRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO]; - } - }]; - } - } - - break; - } - - case NSStreamEventHasBytesAvailable: { - SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); - const int bufferSize = 2048; - uint8_t buffer[bufferSize]; - - while (_inputStream.hasBytesAvailable) { - NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize]; - - if (bytes_read > 0) { - [_readBuffer appendBytes:buffer length:bytes_read]; - } else if (bytes_read < 0) { - [self _failWithError:_inputStream.streamError]; - } - - if (bytes_read != bufferSize) { - break; - } - }; - [self _pumpScanner]; - break; - } - - case NSStreamEventHasSpaceAvailable: { - SRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); - [self _pumpWriting]; - break; - } - - default: - SRFastLog(@"(default) %@", aStream); - break; - } - }); -} - -@end - - -@implementation SRIOConsumer - -@synthesize bytesNeeded = _bytesNeeded; -@synthesize consumer = _scanner; -@synthesize handler = _handler; -@synthesize readToCurrentFrame = _readToCurrentFrame; -@synthesize unmaskBytes = _unmaskBytes; - -- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -{ - _scanner = [scanner copy]; - _handler = [handler copy]; - _bytesNeeded = bytesNeeded; - _readToCurrentFrame = readToCurrentFrame; - _unmaskBytes = unmaskBytes; - assert(_scanner || _bytesNeeded); -} - - -@end - - -@implementation SRIOConsumerPool { - NSUInteger _poolSize; - NSMutableArray *_bufferedConsumers; -} - -- (id)initWithBufferCapacity:(NSUInteger)poolSize; -{ - self = [super init]; - if (self) { - _poolSize = poolSize; - _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; - } - return self; -} - -- (id)init -{ - return [self initWithBufferCapacity:8]; -} - -- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -{ - SRIOConsumer *consumer = nil; - if (_bufferedConsumers.count) { - consumer = [_bufferedConsumers lastObject]; - [_bufferedConsumers removeLastObject]; - } else { - consumer = [[SRIOConsumer alloc] init]; - } - - [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; - - return consumer; -} - -- (void)returnConsumer:(SRIOConsumer *)consumer; -{ - if (_bufferedConsumers.count < _poolSize) { - [_bufferedConsumers addObject:consumer]; - } -} - -@end - - -@implementation NSURLRequest (CertificateAdditions) - -- (NSArray *)SR_SSLPinnedCertificates; -{ - return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; -} - -@end - -@implementation NSMutableURLRequest (CertificateAdditions) - -- (NSArray *)SR_SSLPinnedCertificates; -{ - return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; -} - -- (void)setSR_SSLPinnedCertificates:(NSArray *)SR_SSLPinnedCertificates; -{ - [NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCertificates" inRequest:self]; -} - -@end - -@implementation NSURL (SRWebSocket) - -- (NSString *)SR_origin; -{ - NSString *scheme = [self.scheme lowercaseString]; - - if ([scheme isEqualToString:@"wss"]) { - scheme = @"https"; - } else if ([scheme isEqualToString:@"ws"]) { - scheme = @"http"; - } - - if (self.port) { - return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, self.port]; - } else { - return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; - } -} - -@end - -//#define SR_ENABLE_LOG - -static inline void SRFastLog(NSString *format, ...) { -#ifdef SR_ENABLE_LOG - __block va_list arg_list; - va_start (arg_list, format); - - NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; - - va_end(arg_list); - - NSLog(@"[SR] %@", formattedString); -#endif -} - - -#ifdef HAS_ICU - -static inline int32_t validate_dispatch_data_partial_string(NSData *data) { - if ([data length] > INT32_MAX) { - // INT32_MAX is the limit so long as this Framework is using 32 bit ints everywhere. - return -1; - } - - int32_t size = (int32_t)[data length]; - - const void * contents = [data bytes]; - const uint8_t *str = (const uint8_t *)contents; - - UChar32 codepoint = 1; - int32_t offset = 0; - int32_t lastOffset = 0; - while(offset < size && codepoint > 0) { - lastOffset = offset; - U8_NEXT(str, offset, size, codepoint); - } - - if (codepoint == -1) { - // Check to see if the last byte is valid or whether it was just continuing - if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) { - - size = -1; - } else { - uint8_t leadByte = str[lastOffset]; - U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); - - for (int i = lastOffset + 1; i < offset; i++) { - if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { - size = -1; - } - } - - if (size != -1) { - size = lastOffset; - } - } - } - - if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { - size = -1; - } - - return size; -} - -#else - -// This is a hack, and probably not optimal -static inline int32_t validate_dispatch_data_partial_string(NSData *data) { - static const int maxCodepointSize = 3; - - for (int i = 0; i < maxCodepointSize; i++) { - NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; - if (str) { - return (int32_t)(data.length - i); - } - } - - return -1; -} - -#endif - -static _SRRunLoopThread *networkThread = nil; -static NSRunLoop *networkRunLoop = nil; - -@implementation NSRunLoop (SRWebSocket) - -+ (NSRunLoop *)SR_networkRunLoop { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - networkThread = [[_SRRunLoopThread alloc] init]; - networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; - [networkThread start]; - networkRunLoop = networkThread.runLoop; - }); - - return networkRunLoop; -} - -@end - - -@implementation _SRRunLoopThread { - dispatch_group_t _waitGroup; -} - -@synthesize runLoop = _runLoop; - -- (void)dealloc -{ - sr_dispatch_release(_waitGroup); -} - -- (id)init -{ - self = [super init]; - if (self) { - _waitGroup = dispatch_group_create(); - dispatch_group_enter(_waitGroup); - } - return self; -} - -- (void)main; -{ - @autoreleasepool { - _runLoop = [NSRunLoop currentRunLoop]; - dispatch_group_leave(_waitGroup); - - NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; - [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; - - while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { - - } - assert(NO); - } -} - -- (NSRunLoop *)runLoop; -{ - dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); - return _runLoop; -} - -@end diff --git a/Libraries/WebSocket/RCTSRWebSocket.h b/Libraries/WebSocket/RCTSRWebSocket.h new file mode 100644 index 000000000..ccaa46455 --- /dev/null +++ b/Libraries/WebSocket/RCTSRWebSocket.h @@ -0,0 +1,132 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +typedef enum { + RCTSR_CONNECTING = 0, + RCTSR_OPEN = 1, + RCTSR_CLOSING = 2, + RCTSR_CLOSED = 3, +} RCTSRReadyState; + +typedef enum RCTSRStatusCode : NSInteger { + RCTSRStatusCodeNormal = 1000, + RCTSRStatusCodeGoingAway = 1001, + RCTSRStatusCodeProtocolError = 1002, + RCTSRStatusCodeUnhandledType = 1003, + // 1004 reserved. + RCTSRStatusNoStatusReceived = 1005, + // 1004-1006 reserved. + RCTSRStatusCodeInvalidUTF8 = 1007, + RCTSRStatusCodePolicyViolated = 1008, + RCTSRStatusCodeMessageTooBig = 1009, +} RCTSRStatusCode; + +@class RCTSRWebSocket; + +extern NSString *const RCTSRWebSocketErrorDomain; +extern NSString *const RCTSRHTTPResponseErrorKey; + +#pragma mark - RCTSRWebSocketDelegate + +@protocol RCTSRWebSocketDelegate; + +#pragma mark - RCTSRWebSocket + +@interface RCTSRWebSocket : NSObject + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly) RCTSRReadyState readyState; +@property (nonatomic, readonly, strong) NSURL *url; + +// This returns the negotiated protocol. +// It will be nil until after the handshake completes. +@property (nonatomic, readonly, copy) NSString *protocol; + +// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithURLRequest:(NSURLRequest *)request; + +// Some helper constructors. +- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +- (instancetype)initWithURL:(NSURL *)url; + +// Delegate queue will be dispatch_main_queue by default. +// You cannot set both OperationQueue and dispatch_queue. +- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; + +// By default, it will schedule itself on +[NSRunLoop RCTSR_networkRunLoop] using defaultModes. +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; + +// RCTSRWebSockets are intended for one-time-use only. Open should be called once and only once. +- (void)open; + +- (void)close; +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; + +// Send a UTF8 String or Data. +- (void)send:(id)data; + +// Send Data (can be nil) in a ping message. +- (void)sendPing:(NSData *)data; + +@end + +#pragma mark - RCTSRWebSocketDelegate + +@protocol RCTSRWebSocketDelegate + +// message will either be an NSString if the server is using text +// or NSData if the server is using binary. +- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message; + +@optional + +- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket; +- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; +- (void)webSocket:(RCTSRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload; + +@end + +#pragma mark - NSURLRequest (CertificateAdditions) + +@interface NSURLRequest (CertificateAdditions) + +@property (nonatomic, readonly, copy) NSArray *RCTSR_SSLPinnedCertificates; + +@end + +#pragma mark - NSMutableURLRequest (CertificateAdditions) + +@interface NSMutableURLRequest (CertificateAdditions) + +@property (nonatomic, copy) NSArray *RCTSR_SSLPinnedCertificates; + +@end + +#pragma mark - NSRunLoop (RCTSRWebSocket) + +@interface NSRunLoop (RCTSRWebSocket) + ++ (NSRunLoop *)RCTSR_networkRunLoop; + +@end diff --git a/Libraries/WebSocket/RCTSRWebSocket.m b/Libraries/WebSocket/RCTSRWebSocket.m new file mode 100644 index 000000000..6d36d80b2 --- /dev/null +++ b/Libraries/WebSocket/RCTSRWebSocket.m @@ -0,0 +1,1627 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "RCTSRWebSocket.h" + +#import +#import + +#import + +#import + +#import "RCTAssert.h" +#import "RCTLog.h" + +typedef NS_ENUM(NSInteger, RCTSROpCode) { + RCTSROpCodeTextFrame = 0x1, + RCTSROpCodeBinaryFrame = 0x2, + // 3-7 reserved. + RCTSROpCodeConnectionClose = 0x8, + RCTSROpCodePing = 0x9, + RCTSROpCodePong = 0xA, + // B-F reserved. +}; + +typedef struct { + BOOL fin; + // BOOL rsv1; + // BOOL rsv2; + // BOOL rsv3; + uint8_t opcode; + BOOL masked; + uint64_t payload_length; +} frame_header; + +static NSString *const RCTSRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +static inline int32_t validate_dispatch_data_partial_string(NSData *data); +static inline void RCTSRFastLog(NSString *format, ...); + +@interface NSData (RCTSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSString (RCTSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSURL (RCTSRWebSocket) + +// The origin isn't really applicable for a native application. +// So instead, just map ws -> http and wss -> https. +- (NSString *)RCTSR_origin; + +@end + + +@interface _RCTSRRunLoopThread : NSThread + +@property (nonatomic, readonly) NSRunLoop *runLoop; + +@end + + +static NSString *newSHA1String(const char *bytes, size_t length) +{ + uint8_t md[CC_SHA1_DIGEST_LENGTH]; + + assert(length >= 0); + assert(length <= UINT32_MAX); + CC_SHA1(bytes, (CC_LONG)length, md); + + NSData *data = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH]; + return [data base64EncodedStringWithOptions:0]; +} + +@implementation NSData (RCTSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.bytes, self.length); +} + +@end + + +@implementation NSString (RCTSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.UTF8String, self.length); +} + +@end + +NSString *const RCTSRWebSocketErrorDomain = @"RCTSRWebSocketErrorDomain"; +NSString *const RCTSRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; + +// Returns number of bytes consumed. Returning 0 means you didn't match. +// Sends bytes to callback handler; +typedef size_t (^stream_scanner)(NSData *collected_data); + +typedef void (^data_callback)(RCTSRWebSocket *webSocket, NSData *data); + +@interface RCTSRIOConsumer : NSObject + +@property (nonatomic, copy, readonly) stream_scanner consumer; +@property (nonatomic, copy, readonly) data_callback handler; +@property (nonatomic, assign) size_t bytesNeeded; +@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; +@property (nonatomic, assign, readonly) BOOL unmaskBytes; + +@end + +// This class is not thread-safe, and is expected to always be run on the same queue. +@interface RCTSRIOConsumerPool : NSObject + +- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize NS_DESIGNATED_INITIALIZER; + +- (RCTSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)returnConsumer:(RCTSRIOConsumer *)consumer; + +@end + +@interface RCTSRWebSocket () + +- (void)_writeData:(NSData *)data; +- (void)_closeWithProtocolError:(NSString *)message; +- (void)_failWithError:(NSError *)error; + +- (void)_disconnect; + +- (void)_readFrameNew; +- (void)_readFrameContinue; + +- (void)_pumpScanner; + +- (void)_pumpWriting; + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; + +- (void)_sendFrameWithOpcode:(RCTSROpCode)opcode data:(id)data; + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +- (void)_RCTSR_commonInit; + +- (void)_initializeStreams; +- (void)_connect; + +@property (nonatomic, assign) RCTSRReadyState readyState; + +@property (nonatomic, strong) NSOperationQueue *delegateOperationQueue; +@property (nonatomic, strong) dispatch_queue_t delegateDispatchQueue; + +@end + + +@implementation RCTSRWebSocket +{ + NSInteger _webSocketVersion; + + NSOperationQueue *_delegateOperationQueue; + dispatch_queue_t _delegateDispatchQueue; + + dispatch_queue_t _workQueue; + NSMutableArray *_consumers; + + NSInputStream *_inputStream; + NSOutputStream *_outputStream; + + NSMutableData *_readBuffer; + NSUInteger _readBufferOffset; + + NSMutableData *_outputBuffer; + NSUInteger _outputBufferOffset; + + uint8_t _currentFrameOpcode; + size_t _currentFrameCount; + size_t _readOpCount; + uint32_t _currentStringScanPosition; + NSMutableData *_currentFrameData; + + NSString *_closeReason; + + NSString *_secKey; + + BOOL _pinnedCertFound; + + uint8_t _currentReadMaskKey[4]; + size_t _currentReadMaskOffset; + + BOOL _consumerStopped; + + BOOL _closeWhenFinishedWriting; + BOOL _failed; + + BOOL _secure; + NSURLRequest *_urlRequest; + + CFHTTPMessageRef _receivedHTTPHeaders; + + BOOL _sentClose; + BOOL _didFail; + int _closeCode; + + BOOL _isPumping; + + NSMutableSet *_scheduledRunloops; + + // We use this to retain ourselves. + __strong RCTSRWebSocket *_selfRetain; + + NSArray *_requestedProtocols; + RCTSRIOConsumerPool *_consumerPool; +} + +static __strong NSData *CRLFCRLF; + ++ (void)initialize; +{ + CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; +} + +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +{ + if ((self = [super init])) { + assert(request.URL); + _url = request.URL; + _urlRequest = request; + + _requestedProtocols = [protocols copy]; + + [self _RCTSR_commonInit]; + } + + return self; +} + +- (instancetype)initWithURLRequest:(NSURLRequest *)request; +{ + return [self initWithURLRequest:request protocols:nil]; +} + +- (instancetype)initWithURL:(NSURL *)url; +{ + return [self initWithURL:url protocols:nil]; +} + +- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +{ + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + return [self initWithURLRequest:request protocols:protocols]; +} + +- (void)_RCTSR_commonInit; +{ + NSString *scheme = _url.scheme.lowercaseString; + assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); + + if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { + _secure = YES; + } + + _readyState = RCTSR_CONNECTING; + _consumerStopped = YES; + _webSocketVersion = 13; + + _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + + // Going to set a specific on the queue so we can validate we're on the work queue + dispatch_queue_set_specific(_workQueue, (__bridge void *)self, (__bridge void *)_workQueue, NULL); + + _delegateDispatchQueue = dispatch_get_main_queue(); + + _readBuffer = [[NSMutableData alloc] init]; + _outputBuffer = [[NSMutableData alloc] init]; + + _currentFrameData = [[NSMutableData alloc] init]; + + _consumers = [[NSMutableArray alloc] init]; + + _consumerPool = [[RCTSRIOConsumerPool alloc] init]; + + _scheduledRunloops = [[NSMutableSet alloc] init]; + + [self _initializeStreams]; + + // default handlers +} + +- (void)assertOnWorkQueue; +{ + assert(dispatch_get_specific((__bridge void *)self) == (__bridge void *)_workQueue); +} + +- (void)dealloc +{ + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + [_inputStream close]; + [_outputStream close]; + + _workQueue = NULL; + + if (_receivedHTTPHeaders) { + CFRelease(_receivedHTTPHeaders); + _receivedHTTPHeaders = NULL; + } + + if (_delegateDispatchQueue) { + _delegateDispatchQueue = NULL; + } +} + +#ifndef NDEBUG + +- (void)setReadyState:(RCTSRReadyState)aReadyState; +{ + [self willChangeValueForKey:@"readyState"]; + assert(aReadyState > _readyState); + _readyState = aReadyState; + [self didChangeValueForKey:@"readyState"]; +} + +#endif + +- (void)open; +{ + assert(_url); + RCTAssert(_readyState == RCTSR_CONNECTING, @"Cannot call -(void)open on RCTSRWebSocket more than once"); + + _selfRetain = self; + + [self _connect]; +} + +// Calls block on delegate queue +- (void)_performDelegateBlock:(dispatch_block_t)block; +{ + if (_delegateOperationQueue) { + [_delegateOperationQueue addOperationWithBlock:block]; + } else { + assert(_delegateDispatchQueue); + dispatch_async(_delegateDispatchQueue, block); + } +} + +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; +{ + _delegateDispatchQueue = queue; +} + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +{ + NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); + + if (acceptHeader == nil) { + return NO; + } + + NSString *concattedString = [_secKey stringByAppendingString:RCTSRWebSocketAppendToSecKeyString]; + NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; + + return [acceptHeader isEqualToString:expectedAccept]; +} + +- (void)_HTTPHeadersDidFinish; +{ + NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); + + if (responseCode >= 400) { + RCTSRFastLog(@"Request failed with response code %d", responseCode); + [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode], RCTSRHTTPResponseErrorKey:@(responseCode)}]]; + return; + } + + if(![self _checkHandshake:_receivedHTTPHeaders]) { + [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2133 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"]}]]; + return; + } + + NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); + if (negotiatedProtocol) { + // Make sure we requested the protocol + if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { + [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2133 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"]}]]; + return; + } + + _protocol = negotiatedProtocol; + } + + self.readyState = RCTSR_OPEN; + + if (!_didFail) { + [self _readFrameNew]; + } + + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { + [self.delegate webSocketDidOpen:self]; + }; + }]; +} + +- (void)_readHTTPHeader; +{ + if (_receivedHTTPHeaders == NULL) { + _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); + } + + [self _readUntilHeaderCompleteWithCallback:^(RCTSRWebSocket *socket, NSData *data) { + CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + + if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { + RCTSRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); + [socket _HTTPHeadersDidFinish]; + } else { + [socket _readHTTPHeader]; + } + }]; +} + +- (void)didConnect +{ + RCTSRFastLog(@"Connected"); + CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); + + // Set host first so it defaults + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); + + NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; + SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); + _secKey = [keyBytes base64EncodedStringWithOptions:0]; + assert([_secKey length] == 24); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.RCTSR_origin); + + if (_requestedProtocols) { + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); + } + + [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + + NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); + + CFRelease(request); + + [self _writeData:message]; + [self _readHTTPHeader]; +} + +- (void)_initializeStreams; +{ + assert(_url.port.unsignedIntValue <= UINT32_MAX); + uint32_t port = _url.port.unsignedIntValue; + if (port == 0) { + if (!_secure) { + port = 80; + } else { + port = 443; + } + } + NSString *host = _url.host; + + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); + + _outputStream = CFBridgingRelease(writeStream); + _inputStream = CFBridgingRelease(readStream); + + + if (_secure) { + NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; + + [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // If we're using pinned certs, don't validate the certificate chain + if ([_urlRequest RCTSR_SSLPinnedCertificates].count) { + [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + } + +#if DEBUG + [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + RCTLogInfo(@"SocketRocket: In debug mode. Allowing connection to any root cert"); +#endif + + [_outputStream setProperty:SSLOptions + forKey:(__bridge id)kCFStreamPropertySSLSettings]; + } + + _inputStream.delegate = self; + _outputStream.delegate = self; +} + +- (void)_connect; +{ + if (!_scheduledRunloops.count) { + [self scheduleInRunLoop:[NSRunLoop RCTSR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + } + + [_outputStream open]; + [_inputStream open]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; + [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops addObject:@[aRunLoop, mode]]; +} + +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; + [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops removeObject:@[aRunLoop, mode]]; +} + +- (void)close; +{ + [self closeWithCode:RCTSRStatusCodeNormal reason:nil]; +} + +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; +{ + assert(code); + dispatch_async(_workQueue, ^{ + if (self.readyState == RCTSR_CLOSING || self.readyState == RCTSR_CLOSED) { + return; + } + + BOOL wasConnecting = self.readyState == RCTSR_CONNECTING; + + self.readyState = RCTSR_CLOSING; + + RCTSRFastLog(@"Closing with code %d reason %@", code, reason); + + if (wasConnecting) { + [self _disconnect]; + return; + } + + size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; + NSData *payload = mutablePayload; + + ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); + + if (reason) { + NSRange remainingRange = {0}; + + NSUInteger usedLength = 0; + + BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; + + assert(success); + assert(remainingRange.length == 0); + + if (usedLength != maxMsgSize) { + payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; + } + } + + [self _sendFrameWithOpcode:RCTSROpCodeConnectionClose data:payload]; + }); +} + +- (void)_closeWithProtocolError:(NSString *)message; +{ + // Need to shunt this on the _callbackQueue first to see if they received any messages + [self _performDelegateBlock:^{ + [self closeWithCode:RCTSRStatusCodeProtocolError reason:message]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + }]; +} + +- (void)_failWithError:(NSError *)error; +{ + dispatch_async(_workQueue, ^{ + if (self.readyState != RCTSR_CLOSED) { + _failed = YES; + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { + [self.delegate webSocket:self didFailWithError:error]; + } + }]; + + self.readyState = RCTSR_CLOSED; + _selfRetain = nil; + + RCTSRFastLog(@"Failing with error %@", error.localizedDescription); + + [self _disconnect]; + } + }); +} + +- (void)_writeData:(NSData *)data; +{ + [self assertOnWorkQueue]; + + if (_closeWhenFinishedWriting) { + return; + } + [_outputBuffer appendData:data]; + [self _pumpWriting]; +} + +- (void)send:(id)data; +{ + RCTAssert(self.readyState != RCTSR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); + // TODO: maybe not copy this for performance + data = [data copy]; + dispatch_async(_workQueue, ^{ + if ([data isKindOfClass:[NSString class]]) { + [self _sendFrameWithOpcode:RCTSROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; + } else if ([data isKindOfClass:[NSData class]]) { + [self _sendFrameWithOpcode:RCTSROpCodeBinaryFrame data:data]; + } else if (data == nil) { + [self _sendFrameWithOpcode:RCTSROpCodeTextFrame data:data]; + } else { + assert(NO); + } + }); +} + +- (void)sendPing:(NSData *)data; +{ + RCTAssert(self.readyState == RCTSR_OPEN, @"Invalid State: Cannot call send: until connection is open"); + // TODO: maybe not copy this for performance + data = [data copy] ?: [NSData data]; // It's okay for a ping to be empty + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:RCTSROpCodePing data:data]; + }); +} + +- (void)handlePing:(NSData *)pingData; +{ + // Need to pingpong this off _callbackQueue first to make sure messages happen in order + [self _performDelegateBlock:^{ + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:RCTSROpCodePong data:pingData]; + }); + }]; +} + +- (void)handlePong:(NSData *)pongData; +{ + RCTSRFastLog(@"Received pong"); + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didReceivePong:)]) { + [self.delegate webSocket:self didReceivePong:pongData]; + } + }]; +} + +- (void)_handleMessage:(id)message +{ + RCTSRFastLog(@"Received message"); + [self _performDelegateBlock:^{ + [self.delegate webSocket:self didReceiveMessage:message]; + }]; +} + +static inline BOOL closeCodeIsValid(int closeCode) +{ + if (closeCode < 1000) { + return NO; + } + + if (closeCode >= 1000 && closeCode <= 1011) { + if (closeCode == 1004 || + closeCode == 1005 || + closeCode == 1006) { + return NO; + } + return YES; + } + + if (closeCode >= 3000 && closeCode <= 3999) { + return YES; + } + + if (closeCode >= 4000 && closeCode <= 4999) { + return YES; + } + + return NO; +} + +// Note from RFC: +// +// If there is a body, the first two +// bytes of the body MUST be a 2-byte unsigned integer (in network byte +// order) representing a status code with value /code/ defined in +// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 +// encoded data with value /reason/, the interpretation of which is not +// defined by this specification. + +- (void)handleCloseWithData:(NSData *)data; +{ + size_t dataSize = data.length; + __block uint16_t closeCode = 0; + + RCTSRFastLog(@"Received close frame"); + + if (dataSize == 1) { + // TODO: handle error + [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; + return; + } else if (dataSize >= 2) { + [data getBytes:&closeCode length:sizeof(closeCode)]; + _closeCode = EndianU16_BtoN(closeCode); + if (!closeCodeIsValid(_closeCode)) { + [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; + return; + } + if (dataSize > 2) { + _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; + if (!_closeReason) { + [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; + return; + } + } + } else { + _closeCode = RCTSRStatusNoStatusReceived; + } + + [self assertOnWorkQueue]; + + if (self.readyState == RCTSR_OPEN) { + [self closeWithCode:1000 reason:nil]; + } + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); +} + +- (void)_disconnect; +{ + [self assertOnWorkQueue]; + RCTSRFastLog(@"Trying to disconnect"); + _closeWhenFinishedWriting = YES; + [self _pumpWriting]; +} + +- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; +{ + // Check that the current data is valid UTF8 + + BOOL isControlFrame = (opcode == RCTSROpCodePing || opcode == RCTSROpCodePong || opcode == RCTSROpCodeConnectionClose); + if (!isControlFrame) { + [self _readFrameNew]; + } else { + dispatch_async(_workQueue, ^{ + [self _readFrameContinue]; + }); + } + + switch (opcode) { + case RCTSROpCodeTextFrame: { + NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; + if (str == nil && frameData) { + [self closeWithCode:RCTSRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + + return; + } + [self _handleMessage:str]; + break; + } + case RCTSROpCodeBinaryFrame: + [self _handleMessage:[frameData copy]]; + break; + case RCTSROpCodeConnectionClose: + [self handleCloseWithData:frameData]; + break; + case RCTSROpCodePing: + [self handlePing:frameData]; + break; + case RCTSROpCodePong: + [self handlePong:frameData]; + break; + default: + [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]]; + // TODO: Handle invalid opcode + break; + } +} + +- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; +{ + assert(frame_header.opcode != 0); + + if (self.readyState != RCTSR_OPEN) { + return; + } + + BOOL isControlFrame = (frame_header.opcode == RCTSROpCodePing || frame_header.opcode == RCTSROpCodePong || frame_header.opcode == RCTSROpCodeConnectionClose); + + if (isControlFrame && !frame_header.fin) { + [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; + return; + } + + if (isControlFrame && frame_header.payload_length >= 126) { + [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; + return; + } + + if (!isControlFrame) { + _currentFrameOpcode = frame_header.opcode; + _currentFrameCount += 1; + } + + if (frame_header.payload_length == 0) { + if (isControlFrame) { + [self _handleFrameWithData:curData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO: add assert that opcode is not a control; + [self _readFrameContinue]; + } + } + } else { + assert(frame_header.payload_length <= SIZE_T_MAX); + [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(RCTSRWebSocket *socket, NSData *newData) { + if (isControlFrame) { + [socket _handleFrameWithData:newData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [socket _handleFrameWithData:socket->_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO: add assert that opcode is not a control; + [socket _readFrameContinue]; + } + + } + } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; + } +} + +/* From RFC: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + */ + +static const uint8_t RCTSRFinMask = 0x80; +static const uint8_t RCTSROpCodeMask = 0x0F; +static const uint8_t RCTSRRsvMask = 0x70; +static const uint8_t RCTSRMaskMask = 0x80; +static const uint8_t RCTSRPayloadLenMask = 0x7F; + +- (void)_readFrameContinue; +{ + assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); + + [self _addConsumerWithDataLength:2 callback:^(RCTSRWebSocket *socket, NSData *data) { + __block frame_header header = {0}; + + const uint8_t *headerBuffer = data.bytes; + assert(data.length >= 2); + + if (headerBuffer[0] & RCTSRRsvMask) { + [socket _closeWithProtocolError:@"Server used RSV bits"]; + return; + } + + uint8_t receivedOpcode = (RCTSROpCodeMask &headerBuffer[0]); + + BOOL isControlFrame = (receivedOpcode == RCTSROpCodePing || receivedOpcode == RCTSROpCodePong || receivedOpcode == RCTSROpCodeConnectionClose); + + if (!isControlFrame && receivedOpcode != 0 && socket->_currentFrameCount > 0) { + [socket _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; + return; + } + + if (receivedOpcode == 0 && socket->_currentFrameCount == 0) { + [socket _closeWithProtocolError:@"cannot continue a message"]; + return; + } + + header.opcode = receivedOpcode == 0 ? socket->_currentFrameOpcode : receivedOpcode; + + header.fin = !!(RCTSRFinMask &headerBuffer[0]); + + + header.masked = !!(RCTSRMaskMask &headerBuffer[1]); + header.payload_length = RCTSRPayloadLenMask & headerBuffer[1]; + + headerBuffer = NULL; + + if (header.masked) { + [socket _closeWithProtocolError:@"Client must receive unmasked data"]; + } + + size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; + + if (header.payload_length == 126) { + extra_bytes_needed += sizeof(uint16_t); + } else if (header.payload_length == 127) { + extra_bytes_needed += sizeof(uint64_t); + } + + if (extra_bytes_needed == 0) { + [socket _handleFrameHeader:header curData:socket->_currentFrameData]; + } else { + [socket _addConsumerWithDataLength:extra_bytes_needed callback:^(RCTSRWebSocket *_socket, NSData *_data) { + size_t mapped_size = _data.length; + const void *mapped_buffer = _data.bytes; + size_t offset = 0; + + if (header.payload_length == 126) { + assert(mapped_size >= sizeof(uint16_t)); + uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); + header.payload_length = newLen; + offset += sizeof(uint16_t); + } else if (header.payload_length == 127) { + assert(mapped_size >= sizeof(uint64_t)); + header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); + offset += sizeof(uint64_t); + } else { + assert(header.payload_length < 126 && header.payload_length >= 0); + } + + if (header.masked) { + assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); + memcpy(_socket->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(_socket->_currentReadMaskKey)); + } + + [_socket _handleFrameHeader:header curData:_socket->_currentFrameData]; + } readToCurrentFrame:NO unmaskBytes:NO]; + } + } readToCurrentFrame:NO unmaskBytes:NO]; +} + +- (void)_readFrameNew; +{ + dispatch_async(_workQueue, ^{ + [_currentFrameData setLength:0]; + + _currentFrameOpcode = 0; + _currentFrameCount = 0; + _readOpCount = 0; + _currentStringScanPosition = 0; + + [self _readFrameContinue]; + }); +} + +- (void)_pumpWriting; +{ + [self assertOnWorkQueue]; + + NSUInteger dataLength = _outputBuffer.length; + if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { + NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; + if (bytesWritten == -1) { + [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2145 userInfo:@{NSLocalizedDescriptionKey: @"Error writing to stream"}]]; + return; + } + + _outputBufferOffset += bytesWritten; + + if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { + _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; + _outputBufferOffset = 0; + } + } + + if (_closeWhenFinishedWriting && + _outputBuffer.length - _outputBufferOffset == 0 && + (_inputStream.streamStatus != NSStreamStatusNotOpen && + _inputStream.streamStatus != NSStreamStatusClosed) && + !_sentClose) { + _sentClose = YES; + + [_outputStream close]; + [_inputStream close]; + + for (NSArray *runLoop in [_scheduledRunloops copy]) { + [self unscheduleFromRunLoop:runLoop[0] forMode:runLoop[1]]; + } + + if (!_failed) { + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; + } + }]; + } + + _selfRetain = nil; + } +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +{ + [self assertOnWorkQueue]; + [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; +} + +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + [self assertOnWorkQueue]; + assert(dataLength); + + [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; + [self _pumpScanner]; +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +{ + [self assertOnWorkQueue]; + [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; + [self _pumpScanner]; +} + +static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; + +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; +{ + [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; +} + +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +{ + // TODO: optimize so this can continue from where we last searched + stream_scanner consumer = ^size_t(NSData *data) { + __block size_t found_size = 0; + __block size_t match_count = 0; + + size_t size = data.length; + const unsigned char *buffer = data.bytes; + for (size_t i = 0; i < size; i++ ) { + if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { + match_count += 1; + if (match_count == length) { + found_size = i + 1; + break; + } + } else { + match_count = 0; + } + } + return found_size; + }; + [self _addConsumerWithScanner:consumer callback:dataHandler]; +} + +// Returns true if did work +- (BOOL)_innerPumpScanner +{ + BOOL didWork = NO; + + if (self.readyState >= RCTSR_CLOSING) { + return didWork; + } + + if (!_consumers.count) { + return didWork; + } + + size_t curSize = _readBuffer.length - _readBufferOffset; + if (!curSize) { + return didWork; + } + + RCTSRIOConsumer *consumer = _consumers[0]; + + size_t bytesNeeded = consumer.bytesNeeded; + + size_t foundSize = 0; + if (consumer.consumer) { + NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; + foundSize = consumer.consumer(tempView); + } else { + assert(consumer.bytesNeeded); + if (curSize >= bytesNeeded) { + foundSize = bytesNeeded; + } else if (consumer.readToCurrentFrame) { + foundSize = curSize; + } + } + + NSData *slice = nil; + if (consumer.readToCurrentFrame || foundSize) { + NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); + slice = [_readBuffer subdataWithRange:sliceRange]; + + _readBufferOffset += foundSize; + + if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { + _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; + } + + if (consumer.unmaskBytes) { + NSMutableData *mutableSlice = [slice mutableCopy]; + + NSUInteger len = mutableSlice.length; + uint8_t *bytes = mutableSlice.mutableBytes; + + for (NSUInteger i = 0; i < len; i++) { + bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; + _currentReadMaskOffset += 1; + } + + slice = mutableSlice; + } + + if (consumer.readToCurrentFrame) { + [_currentFrameData appendData:slice]; + + _readOpCount += 1; + + if (_currentFrameOpcode == RCTSROpCodeTextFrame) { + // Validate UTF8 stuff. + size_t currentDataSize = _currentFrameData.length; + if (_currentFrameOpcode == RCTSROpCodeTextFrame && currentDataSize > 0) { + // TODO: Optimize this. Don't really have to copy all the data each time + + size_t scanSize = currentDataSize - _currentStringScanPosition; + + NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; + int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); + + if (valid_utf8_size == -1) { + [self closeWithCode:RCTSRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + return didWork; + } else { + _currentStringScanPosition += valid_utf8_size; + } + } + } + + consumer.bytesNeeded -= foundSize; + + if (consumer.bytesNeeded == 0) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, nil); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } else if (foundSize) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, slice); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } + return didWork; +} + +- (void)_pumpScanner; +{ + [self assertOnWorkQueue]; + + if (!_isPumping) { + _isPumping = YES; + } else { + return; + } + + while ([self _innerPumpScanner]) { + + } + + _isPumping = NO; +} + +//#define NOMASK + +static const size_t RCTSRFrameHeaderOverhead = 32; + +- (void)_sendFrameWithOpcode:(RCTSROpCode)opcode data:(id)data; +{ + [self assertOnWorkQueue]; + + if (nil == data) { + return; + } + + RCTAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"NSString or NSData"); + + size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; + + NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + RCTSRFrameHeaderOverhead]; + if (!frame) { + [self closeWithCode:RCTSRStatusCodeMessageTooBig reason:@"Message too big"]; + return; + } + uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; + + // set fin + frame_buffer[0] = RCTSRFinMask | opcode; + + BOOL useMask = YES; +#ifdef NOMASK + useMask = NO; +#endif + + if (useMask) { + // set the mask and header + frame_buffer[1] |= RCTSRMaskMask; + } + + size_t frame_buffer_size = 2; + + const uint8_t *unmasked_payload = NULL; + if ([data isKindOfClass:[NSData class]]) { + unmasked_payload = (uint8_t *)[data bytes]; + } else if ([data isKindOfClass:[NSString class]]) { + unmasked_payload = (const uint8_t *)[data UTF8String]; + } else { + return; + } + + if (payloadLength < 126) { + frame_buffer[1] |= payloadLength; + } else if (payloadLength <= UINT16_MAX) { + frame_buffer[1] |= 126; + *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); + frame_buffer_size += sizeof(uint16_t); + } else { + frame_buffer[1] |= 127; + *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); + frame_buffer_size += sizeof(uint64_t); + } + + if (!useMask) { + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i]; + frame_buffer_size += 1; + } + } else { + uint8_t *mask_key = frame_buffer + frame_buffer_size; + SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); + frame_buffer_size += sizeof(uint32_t); + + // TODO: could probably optimize this with SIMD + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; + frame_buffer_size += 1; + } + } + + assert(frame_buffer_size <= [frame length]); + frame.length = frame_buffer_size; + + [self _writeData:frame]; +} + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; +{ + if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + + NSArray *sslCerts = [_urlRequest RCTSR_SSLPinnedCertificates]; + if (sslCerts) { + SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; + if (secTrust) { + NSInteger numCerts = SecTrustGetCertificateCount(secTrust); + for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); + NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); + + for (id ref in sslCerts) { + SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; + NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); + + if ([trustedCertData isEqualToData:certData]) { + _pinnedCertFound = YES; + break; + } + } + } + } + + if (!_pinnedCertFound) { + dispatch_async(_workQueue, ^{ + [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:23556 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Invalid server cert"]}]]; + }); + return; + } + } + } + + dispatch_async(_workQueue, ^{ + switch (eventCode) { + case NSStreamEventOpenCompleted: { + RCTSRFastLog(@"NSStreamEventOpenCompleted %@", aStream); + if (self.readyState >= RCTSR_CLOSING) { + return; + } + assert(_readBuffer); + + if (self.readyState == RCTSR_CONNECTING && aStream == _inputStream) { + [self didConnect]; + } + [self _pumpWriting]; + [self _pumpScanner]; + break; + } + + case NSStreamEventErrorOccurred: { + RCTSRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); + // TODO: specify error better! + [self _failWithError:aStream.streamError]; + _readBufferOffset = 0; + [_readBuffer setLength:0]; + break; + + } + + case NSStreamEventEndEncountered: { + [self _pumpScanner]; + RCTSRFastLog(@"NSStreamEventEndEncountered %@", aStream); + if (aStream.streamError) { + [self _failWithError:aStream.streamError]; + } else { + if (self.readyState != RCTSR_CLOSED) { + self.readyState = RCTSR_CLOSED; + _selfRetain = nil; + } + + if (!_sentClose && !_failed) { + _sentClose = YES; + // If we get closed in this state it's probably not clean because we should be sending this when we send messages + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:RCTSRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO]; + } + }]; + } + } + + break; + } + + case NSStreamEventHasBytesAvailable: { + RCTSRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); + const int bufferSize = 2048; + uint8_t buffer[bufferSize]; + + while (_inputStream.hasBytesAvailable) { + NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize]; + + if (bytes_read > 0) { + [_readBuffer appendBytes:buffer length:bytes_read]; + } else if (bytes_read < 0) { + [self _failWithError:_inputStream.streamError]; + } + + if (bytes_read != bufferSize) { + break; + } + }; + [self _pumpScanner]; + break; + } + + case NSStreamEventHasSpaceAvailable: { + RCTSRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); + [self _pumpWriting]; + break; + } + + default: + RCTSRFastLog(@"(default) %@", aStream); + break; + } + }); +} + +@end + +@implementation RCTSRIOConsumer + +- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + _consumer = [scanner copy]; + _handler = [handler copy]; + _bytesNeeded = bytesNeeded; + _readToCurrentFrame = readToCurrentFrame; + _unmaskBytes = unmaskBytes; + assert(_consumer || _bytesNeeded); +} + +@end + +@implementation RCTSRIOConsumerPool +{ + NSUInteger _poolSize; + NSMutableArray *_bufferedConsumers; +} + +- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize; +{ + if ((self = [super init])) { + _poolSize = poolSize; + _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; + } + return self; +} + +- (instancetype)init +{ + return [self initWithBufferCapacity:8]; +} + +- (RCTSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + RCTSRIOConsumer *consumer = nil; + if (_bufferedConsumers.count) { + consumer = [_bufferedConsumers lastObject]; + [_bufferedConsumers removeLastObject]; + } else { + consumer = [[RCTSRIOConsumer alloc] init]; + } + + [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; + + return consumer; +} + +- (void)returnConsumer:(RCTSRIOConsumer *)consumer; +{ + if (_bufferedConsumers.count < _poolSize) { + [_bufferedConsumers addObject:consumer]; + } +} + +@end + +@implementation NSURLRequest (CertificateAdditions) + +- (NSArray *)RCTSR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"RCTSR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSMutableURLRequest (CertificateAdditions) + +- (NSArray *)RCTSR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"RCTSR_SSLPinnedCertificates" inRequest:self]; +} + +- (void)setRCTSR_SSLPinnedCertificates:(NSArray *)RCTSR_SSLPinnedCertificates; +{ + [NSURLProtocol setProperty:RCTSR_SSLPinnedCertificates forKey:@"RCTSR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSURL (RCTSRWebSocket) + +- (NSString *)RCTSR_origin; +{ + NSString *scheme = [self.scheme lowercaseString]; + + if ([scheme isEqualToString:@"wss"]) { + scheme = @"https"; + } else if ([scheme isEqualToString:@"ws"]) { + scheme = @"http"; + } + + if (self.port) { + return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, self.port]; + } else { + return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; + } +} + +@end + +//#define RCTSR_ENABLE_LOG + +static inline void RCTSRFastLog(NSString *format, ...) +{ +#ifdef RCTSR_ENABLE_LOG + __block va_list arg_list; + va_start (arg_list, format); + + NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; + + va_end(arg_list); + + RCTLogInfo(@"[RCTSR] %@", formattedString); +#endif +} + +// This is a hack, and probably not optimal +static inline int32_t validate_dispatch_data_partial_string(NSData *data) +{ + static const int maxCodepointSize = 3; + + for (int i = 0; i < maxCodepointSize; i++) { + NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; + if (str) { + return (int32_t)data.length - i; + } + } + + return -1; +} + +static _RCTSRRunLoopThread *networkThread = nil; +static NSRunLoop *networkRunLoop = nil; + +@implementation NSRunLoop (RCTSRWebSocket) + ++ (NSRunLoop *)RCTSR_networkRunLoop +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + networkThread = [[_RCTSRRunLoopThread alloc] init]; + networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; + [networkThread start]; + networkRunLoop = networkThread.runLoop; + }); + + return networkRunLoop; +} + +@end + +@implementation _RCTSRRunLoopThread +{ + dispatch_group_t _waitGroup; +} + +@synthesize runLoop = _runLoop; + +- (instancetype)init +{ + if ((self = [super init])) { + _waitGroup = dispatch_group_create(); + dispatch_group_enter(_waitGroup); + } + return self; +} + +- (void)main; +{ + @autoreleasepool { + _runLoop = [NSRunLoop currentRunLoop]; + dispatch_group_leave(_waitGroup); + + NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; + [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; + + while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { } + assert(NO); + } +} + +- (NSRunLoop *)runLoop; +{ + dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); + return _runLoop; +} + +@end diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h b/Libraries/WebSocket/RCTWebSocketExecutor.h similarity index 88% rename from Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h rename to Libraries/WebSocket/RCTWebSocketExecutor.h index 9993cbc5a..2c17541f1 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h +++ b/Libraries/WebSocket/RCTWebSocketExecutor.h @@ -15,7 +15,7 @@ @interface RCTWebSocketExecutor : NSObject -- (instancetype)initWithURL:(NSURL *)URL; +- (instancetype)initWithURL:(NSURL *)URL NS_DESIGNATED_INITIALIZER; @end diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m similarity index 89% rename from Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m rename to Libraries/WebSocket/RCTWebSocketExecutor.m index 16a378ccf..97095a5c9 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -16,16 +16,17 @@ #import "RCTLog.h" #import "RCTSparseArray.h" #import "RCTUtils.h" -#import "SRWebSocket.h" +#import "RCTSRWebSocket.h" -typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); +typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); + +@interface RCTWebSocketExecutor () -@interface RCTWebSocketExecutor () @end @implementation RCTWebSocketExecutor { - SRWebSocket *_socket; + RCTSRWebSocket *_socket; dispatch_queue_t _jsQueue; RCTSparseArray *_callbacks; dispatch_semaphore_t _socketOpenSemaphore; @@ -42,7 +43,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); if (self = [super init]) { _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); - _socket = [[SRWebSocket alloc] initWithURL:URL]; + _socket = [[RCTSRWebSocket alloc] initWithURL:URL]; _socket.delegate = self; _callbacks = [[RCTSparseArray alloc] init]; _injectedObjects = [[NSMutableDictionary alloc] init]; @@ -95,28 +96,28 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); return runtimeIsReady == 0 && initError == nil; } -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message +- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message { NSError *error = nil; NSDictionary *reply = RCTJSONParse(message, &error); NSNumber *messageID = reply[@"replyID"]; - WSMessageCallback callback = _callbacks[messageID]; + RCTWSMessageCallback callback = _callbacks[messageID]; if (callback) { callback(error, reply); } } -- (void)webSocketDidOpen:(SRWebSocket *)webSocket +- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket { dispatch_semaphore_signal(_socketOpenSemaphore); } -- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error +- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error { RCTLogError(@"WebSocket connection failed with error %@", error); } -- (void)sendMessage:(NSDictionary *)message context:(NSNumber *)executorID waitForReply:(WSMessageCallback)callback +- (void)sendMessage:(NSDictionary *)message context:(NSNumber *)executorID waitForReply:(RCTWSMessageCallback)callback { static NSUInteger lastID = 10000; @@ -190,7 +191,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); - (BOOL)isValid { - return _socket != nil && _socket.readyState == SR_OPEN; + return _socket != nil && _socket.readyState == RCTSR_OPEN; } - (void)dealloc diff --git a/Libraries/WebSocket/RCTWebSocketManager.h b/Libraries/WebSocket/RCTWebSocketManager.h new file mode 100644 index 000000000..196486f25 --- /dev/null +++ b/Libraries/WebSocket/RCTWebSocketManager.h @@ -0,0 +1,14 @@ +/** + * 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. + */ + +#import "RCTBridgeModule.h" + +@interface RCTWebSocketManager : NSObject + +@end diff --git a/Libraries/WebSocket/RCTWebSocketManager.m b/Libraries/WebSocket/RCTWebSocketManager.m new file mode 100644 index 000000000..a403e0a0a --- /dev/null +++ b/Libraries/WebSocket/RCTWebSocketManager.m @@ -0,0 +1,116 @@ +/** + * 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. + */ + +#import "RCTWebSocketManager.h" + +#import "RCTBridge.h" +#import "RCTEventDispatcher.h" +#import "RCTSRWebSocket.h" +#import "RCTSparseArray.h" + +@implementation RCTSRWebSocket (React) + +- (NSNumber *)reactTag +{ + return objc_getAssociatedObject(self, _cmd); +} + +- (void)setReactTag:(NSNumber *)reactTag +{ + objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +@end + +@interface RCTWebSocketManager () + +@end + +@implementation RCTWebSocketManager +{ + RCTSparseArray *_sockets; +} + +RCT_EXPORT_MODULE() + +@synthesize bridge = _bridge; + +- (instancetype)init +{ + if ((self = [super init])) { + _sockets = [[RCTSparseArray alloc] init]; + } + return self; +} + +- (void)dealloc +{ + for (RCTSRWebSocket *socket in _sockets.allObjects) { + socket.delegate = nil; + [socket close]; + } +} + +RCT_EXPORT_METHOD(connect:(NSURL *)URL socketID:(NSNumber *)socketID) +{ + RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURL:URL]; + webSocket.delegate = self; + webSocket.reactTag = socketID; + _sockets[socketID] = webSocket; + [webSocket open]; +} + +RCT_EXPORT_METHOD(send:(NSString *)message socketID:(NSNumber *)socketID) +{ + [_sockets[socketID] send:message]; +} + +RCT_EXPORT_METHOD(close:(NSNumber *)socketID) +{ + [_sockets[socketID] close]; + _sockets[socketID] = nil; +} + +#pragma mark - RCTSRWebSocketDelegate methods + +- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketMessage" body:@{ + @"data": message, + @"id": webSocket.reactTag + }]; +} + +- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketOpen" body:@{ + @"id": webSocket.reactTag + }]; +} + +- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketFailed" body:@{ + @"message":[error localizedDescription], + @"id": webSocket.reactTag + }]; +} + +- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code + reason:(NSString *)reason wasClean:(BOOL)wasClean +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketClosed" body:@{ + @"code": @(code), + @"reason": reason, + @"clean": @(wasClean), + @"id": webSocket.reactTag + }]; +} + +@end diff --git a/Libraries/WebSocket/WebSocket.android.js b/Libraries/WebSocket/WebSocket.android.js new file mode 100644 index 000000000..301f92f49 --- /dev/null +++ b/Libraries/WebSocket/WebSocket.android.js @@ -0,0 +1,39 @@ +/** + * 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 WebSocket + * + */ +'use strict'; + +var WebSocketBase = require('WebSocketBase'); + +class WebSocket extends WebSocketBase { + + connectToSocketImpl(url: string): void { + console.warn('WebSocket is not yet supported on Android'); + } + + closeConnectionImpl(): void{ + console.warn('WebSocket is not yet supported on Android'); + } + + cancelConnectionImpl(): void { + console.warn('WebSocket is not yet supported on Android'); + } + + sendStringImpl(message: string): void { + console.warn('WebSocket is not yet supported on Android'); + } + + sendArrayBufferImpl(): void { + console.warn('WebSocket is not yet supported on Android'); + } +} + +module.exports = WebSocket; diff --git a/Libraries/WebSocket/WebSocket.ios.js b/Libraries/WebSocket/WebSocket.ios.js new file mode 100644 index 000000000..e48d7882b --- /dev/null +++ b/Libraries/WebSocket/WebSocket.ios.js @@ -0,0 +1,104 @@ +/** + * 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 WebSocket + * + */ +'use strict'; + +var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var RCTWebSocketManager = require('NativeModules').WebSocketManager; + +var WebSocketBase = require('WebSocketBase'); + +var WebSocketId = 0; + +class WebSocket extends WebSocketBase { + _socketId: number; + _subs: any; + + connectToSocketImpl(url: string): void { + this._socketId = WebSocketId++; + RCTWebSocketManager.connect(url, this._socketId); + this._registerEvents(this._socketId); + } + + closeConnectionImpl(): void { + RCTWebSocketManager.close(this._socketId); + } + + cancelConnectionImpl(): void { + RCTWebSocketManager.close(this._socketId); + } + + sendStringImpl(message: string): void { + RCTWebSocketManager.send(message, this._socketId); + } + + sendArrayBufferImpl(): void { + // TODO + console.warn('Sending ArrayBuffers is not yet supported'); + } + + _unregisterEvents(): void { + this._subs.forEach(e => e.remove()); + this._subs = []; + } + + _registerEvents(id: number): void { + this._subs = [ + RCTDeviceEventEmitter.addListener( + 'websocketMessage', + function(ev) { + if (ev.id !== id) { + return; + } + this.onmessage && this.onmessage({ + data: ev.data + }); + }.bind(this) + ), + RCTDeviceEventEmitter.addListener( + 'websocketOpen', + function(ev) { + if (ev.id !== id) { + return; + } + this.readyState = this.OPEN; + this.onopen && this.onopen(); + }.bind(this) + ), + RCTDeviceEventEmitter.addListener( + 'websocketClosed', + function(ev) { + if (ev.id !== id) { + return; + } + this.readyState = this.CLOSED; + this.onclose && this.onclose(ev); + this._unregisterEvents(); + RCTWebSocketManager.close(id); + }.bind(this) + ), + RCTDeviceEventEmitter.addListener( + 'websocketFailed', + function(ev) { + if (ev.id !== id) { + return; + } + this.onerror && this.onerror(new Error(ev.message)); + this._unregisterEvents(); + RCTWebSocketManager.close(id); + }.bind(this) + ) + ]; + } + +} + +module.exports = WebSocket; diff --git a/Libraries/WebSocket/WebSocketBase.js b/Libraries/WebSocket/WebSocketBase.js new file mode 100644 index 000000000..73cc62cc9 --- /dev/null +++ b/Libraries/WebSocket/WebSocketBase.js @@ -0,0 +1,97 @@ +/** + * 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 WebSocketBase + * + */ +'use strict'; + +/** + * Shared base for platform-specific WebSocket implementations. + */ +class WebSocketBase { + CONNECTING: number; + OPEN: number; + CLOSING: number; + CLOSED: number; + + onclose: ?Function; + onerror: ?Function; + onmessage: ?Function; + onopen: ?Function; + + binaryType: ?string; + bufferedAmount: number; + extension: ?string; + protocol: ?string; + readyState: number; + url: ?string; + + constructor(url: string, protocols: ?any) { + this.CONNECTING = 0; + this.OPEN = 1; + this.CLOSING = 2; + this.CLOSED = 3; + + if (!protocols) { + protocols = []; + } + + this.connectToSocketImpl(url); + } + + close(): void { + if (this.readyState === WebSocketBase.CLOSING || + this.readyState === WebSocketBase.CLOSED) { + return; + } + + if (this.readyState === WebSocketBase.CONNECTING) { + this.cancelConnectionImpl(); + } + + this.closeConnectionImpl(); + } + + send(data: any): void { + if (this.readyState === WebSocketBase.CONNECTING) { + throw new Error('INVALID_STATE_ERR'); + } + + if (typeof data === 'string') { + this.sendStringImpl(data); + } else if (data instanceof ArrayBuffer) { + this.sendArrayBufferImpl(data); + } else { + throw new Error('Not supported data type'); + } + } + + closeConnectionImpl(): void { + throw new Error('Subclass must define closeConnectionImpl method'); + } + + connectToSocketImpl(): void { + throw new Error('Subclass must define connectToSocketImpl method'); + } + + cancelConnectionImpl(): void { + throw new Error('Subclass must define cancelConnectionImpl method'); + } + + sendStringImpl(): void { + throw new Error('Subclass must define sendStringImpl method'); + } + + sendArrayBufferImpl(): void { + throw new Error('Subclass must define sendArrayBufferImpl method'); + } + +} + +module.exports = WebSocketBase; diff --git a/React.podspec b/React.podspec index eec148520..32f21edad 100644 --- a/React.podspec +++ b/React.podspec @@ -98,8 +98,9 @@ Pod::Spec.new do |s| ss.preserve_paths = "Libraries/Vibration/*.js" end - s.subspec 'RCTWebSocketDebugger' do |ss| + s.subspec 'RCTWebSocket' do |ss| ss.dependency 'React/Core' - ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}" + ss.source_files = "Libraries/WebSocket/*.{h,m}" + ss.preserve_paths = "Libraries/WebSocket/*.js" end end diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 80964e938..4e05f0e54 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -81,7 +81,7 @@ RCT_EXPORT_MODULE() object:nil]; [notificationCenter addObserver:self - selector:@selector(updateSettings) + selector:@selector(settingsDidChange) name:NSUserDefaultsDidChangeNotification object:nil]; @@ -94,13 +94,11 @@ RCT_EXPORT_MODULE() _settings = [[NSMutableDictionary alloc] init]; // Delay setup until after Bridge init - __weak RCTDevMenu *weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf updateSettings]; - }); + [self settingsDidChange]; #if TARGET_IPHONE_SIMULATOR + __weak RCTDevMenu *weakSelf = self; RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; // Toggle debug menu @@ -127,6 +125,15 @@ RCT_EXPORT_MODULE() return dispatch_get_main_queue(); } +- (void)settingsDidChange +{ + // Needed to prevent a race condition when reloading + __weak RCTDevMenu *weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf updateSettings]; + }); +} + - (void)updateSettings { NSDictionary *settings = [_defaults objectForKey:RCTDevMenuSettingsKey]; @@ -269,6 +276,14 @@ RCT_EXPORT_METHOD(reload) } case 1: { Class cls = NSClassFromString(@"RCTWebSocketExecutor"); + if (!cls) { + [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable" + message:@"You need to include the RCTWebSocket library to enable Chrome debugging" + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil] show]; + return; + } self.executorClass = (_executorClass == cls) ? Nil : cls; break; } From 711dd6602ea5bae2bb781098a576e62cdd5dd072 Mon Sep 17 00:00:00 2001 From: Eric Sauter Date: Thu, 14 May 2015 08:58:31 -0700 Subject: [PATCH 13/21] Fix lint errors and warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Fix the following problems resulting from running `npm run lint`: ``` Examples/Movies/SearchScreen.js 118:4 error 'fetch' is not defined no-undef 177:4 error 'fetch' is not defined no-undef Examples/UIExplorer/MapViewExample.js 32:1 warning Missing semicolon semi Examples/UIExplorer/NavigatorIOSColorsExample.js 48:26 warning ['lightContent'] is better written in dot notation dot-notation Examples/UIExplorer/TabBarIOSExample.js 81:16 warning Trailing spaces not allowed no-trailing-spaces Examples/UIExplorer/TextInputExample.js 390:7 warning Missing semicolon semi ✖ 6 problems ``` Closes https://github.com/facebook/react-native/pull/1254 Github Author: Eric Sauter Test Plan: Imported from GitHub, without a `Test Plan:` line. --- .eslintrc | 1 + Examples/UIExplorer/ExampleTypes.js | 1 - Examples/UIExplorer/MapViewExample.js | 2 +- Examples/UIExplorer/NavigatorIOSColorsExample.js | 2 +- Examples/UIExplorer/TabBarIOSExample.js | 2 +- Examples/UIExplorer/TextInputExample.js | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.eslintrc b/.eslintrc index a81fb56f3..da893862d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,6 +16,7 @@ "document": false, "escape": false, "exports": false, + "fetch": false, "global": false, "jest": false, "Map": true, diff --git a/Examples/UIExplorer/ExampleTypes.js b/Examples/UIExplorer/ExampleTypes.js index 7f6ff08d8..44db06a6c 100644 --- a/Examples/UIExplorer/ExampleTypes.js +++ b/Examples/UIExplorer/ExampleTypes.js @@ -27,4 +27,3 @@ export type ExampleModule = { examples: Array; external?: bool; }; - diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index e8dae25ac..5df00236d 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -29,7 +29,7 @@ var regionText = { longitude: '0', latitudeDelta: '0', longitudeDelta: '0', -} +}; var MapRegionInput = React.createClass({ diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js index 386586f23..a045807c7 100644 --- a/Examples/UIExplorer/NavigatorIOSColorsExample.js +++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js @@ -45,7 +45,7 @@ var NavigatorIOSColors = React.createClass({ render: function() { // Set StatusBar with light contents to get better contrast - StatusBarIOS.setStyle(StatusBarIOS.Style['lightContent']); + StatusBarIOS.setStyle(StatusBarIOS.Style.lightContent); return ( {this._renderContent('#21551C', 'Green Tab')} diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js index 922dd9607..10064491d 100644 --- a/Examples/UIExplorer/TextInputExample.js +++ b/Examples/UIExplorer/TextInputExample.js @@ -387,7 +387,7 @@ exports.examples = [ - ) + ); } } ]; From e9db0338d27b2d4c11505262d53dc5d26a41fade Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Thu, 14 May 2015 09:38:45 -0700 Subject: [PATCH 14/21] [React Native] Enable accessibility on RCTText --- Libraries/Text/RCTText.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m index 9d2ade8e4..632857f01 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -25,6 +25,9 @@ if ((self = [super initWithFrame:frame])) { _textStorage = [[NSTextStorage alloc] init]; + self.accessibilityElement = YES; + self.accessibilityTraits |= UIAccessibilityTraitStaticText; + self.opaque = NO; self.contentMode = UIViewContentModeRedraw; } @@ -127,4 +130,11 @@ return reactTag ?: self.reactTag; } +#pragma mark - Accessibility + +- (NSString *)accessibilityLabel +{ + return _textStorage.string; +} + @end From 4e412381f24c310eb6e4c4c3e6081ba77f11ae15 Mon Sep 17 00:00:00 2001 From: James Ide Date: Thu, 14 May 2015 09:37:39 -0700 Subject: [PATCH 15/21] [TextView] Define missing properties and add getters Summary: Some of the RCTTextView properties weren't set up correctly which would cause bugs when you'd set a property and then unset it, trying to revert to the default. This requires reading the default value from the dummy view instance, but some of these properties didn't have getters which was causing issues. Fixes #1174 Closes https://github.com/facebook/react-native/pull/1175 Github Author: James Ide Test Plan: Create a `` component. Give it a style with `color: 'blue'`, and then on the next render pass remove the style. No more red box. --- Libraries/Text/RCTTextView.h | 4 +++- Libraries/Text/RCTTextView.m | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index 19f2fea39..014e35315 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -21,8 +21,10 @@ @property (nonatomic, assign) BOOL selectTextOnFocus; @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; +@property (nonatomic, copy) NSString *text; +@property (nonatomic, strong) UIColor *textColor; @property (nonatomic, strong) UIColor *placeholderTextColor; -@property (nonatomic, assign) UIFont *font; +@property (nonatomic, strong) UIFont *font; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index c5947f317..fa5b2bf8a 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -72,13 +72,22 @@ } } +- (UIFont *)font +{ + return _textView.font; +} + - (void)setFont:(UIFont *)font { - _font = font; - _textView.font = _font; + _textView.font = font; [self updatePlaceholder]; } +- (UIColor *)textColor +{ + return _textView.textColor; +} + - (void)setTextColor:(UIColor *)textColor { _textView.textColor = textColor; @@ -106,6 +115,11 @@ [self updateFrames]; } +- (NSString *)text +{ + return _textView.text; +} + - (void)setText:(NSString *)text { if (![text isEqualToString:_textView.text]) { @@ -146,7 +160,6 @@ - (void)textViewDidBeginEditing:(UITextView *)textView { if (_clearTextOnFocus) { - [_textView setText:@""]; _textView.text = @""; [self _setPlaceholderVisibility]; } From 75ed1dd49e4142d01e4d61ff7a677b505041c059 Mon Sep 17 00:00:00 2001 From: Yuta Okazaki Date: Thu, 14 May 2015 09:45:38 -0700 Subject: [PATCH 16/21] Fix typo of function name in ListView documentation Summary: When I read documents, I usually 'search within page' to see where they talk about specific things. So I found this fix to be pretty useful. Hope it'll be merged! Closes https://github.com/facebook/react-native/pull/1146 Github Author: Yuta Okazaki Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/CustomComponents/ListView/ListView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 0a03bd38b..abec1a6e0 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -87,7 +87,7 @@ var SCROLLVIEW_REF = 'listviewscroll'; * smoothly while dynamically loading potentially very large (or conceptually * infinite) data sets: * - * * Only re-render changed rows - the hasRowChanged function provided to the + * * Only re-render changed rows - the rowHasChanged function provided to the * data source tells the ListView if it needs to re-render a row because the * source data has changed - see ListViewDataSource for more details. * From 766983f69b1127dce3b8a0f6c0de8a208eb9e1c8 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 14 May 2015 10:15:05 -0700 Subject: [PATCH 17/21] [react native] Bump jest-cli version to 0.4.3 in RN packages --- package.json | 2 +- packager/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2cc5ec6f8..e47335eef 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "yargs": "1.3.2" }, "devDependencies": { - "jest-cli": "0.2.1", + "jest-cli": "0.4.3", "eslint": "0.9.2" } } diff --git a/packager/package.json b/packager/package.json index 9ffaf9e70..3eaf2f3b9 100644 --- a/packager/package.json +++ b/packager/package.json @@ -25,7 +25,7 @@ }, "dependencies": {}, "devDependencies": { - "jest-cli": "0.2.1", + "jest-cli": "0.4.3", "eslint": "0.9.2" } } From 7c3070628acc91a7fec03b493b3303cf6da179b1 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 14 May 2015 11:53:22 -0700 Subject: [PATCH 18/21] Fixed infinite #clowntown error loop --- .../Initialization/ExceptionsManager.js | 6 ++++-- .../Initialization/loadSourceMap.js | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index 91afe2008..224ce0966 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -48,8 +48,10 @@ function reportException(e: Exception, isFatal: bool, stack?: any) { var prettyStack = parseErrorStack(e, map); RCTExceptionsManager.updateExceptionMessage(e.message, prettyStack); }) - .then(null, error => { - console.error('#CLOWNTOWN (error while displaying error): ' + error.message); + .catch(error => { + // This can happen in a variety of normal situations, such as + // Network module not being available, or when running locally + console.warn('Unable to load source map: ' + error.message); }); } } diff --git a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js index a826db7bf..c3499ac9f 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js +++ b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js @@ -13,10 +13,13 @@ 'use strict'; var Promise = require('Promise'); -var RCTSourceCode = require('NativeModules').SourceCode; +var NativeModules = require('NativeModules'); var SourceMapConsumer = require('SourceMap').SourceMapConsumer; var SourceMapURL = require('./source-map-url'); +var RCTSourceCode = NativeModules.SourceCode; +var RCTDataManager = NativeModules.DataManager; + function loadSourceMap(): Promise { return fetchSourceMap() .then(map => new SourceMapConsumer(map)); @@ -31,17 +34,31 @@ function fetchSourceMap(): Promise { return Promise.reject(new Error('RCTSourceCode module is not available')); } + if (!RCTDataManager) { + // Used internally by fetch + return Promise.reject(new Error('RCTDataManager module is not available')); + } + return new Promise(RCTSourceCode.getScriptText) .then(extractSourceMapURL) + .then((url) => { + if (url === null) { + return Promise.reject(new Error('No source map URL found. May be running from bundled file.')); + } + return Promise.resolve(url); + }) .then(fetch) .then(response => response.text()) } -function extractSourceMapURL({url, text, fullSourceMappingURL}): string { +function extractSourceMapURL({url, text, fullSourceMappingURL}): ?string { if (fullSourceMappingURL) { return fullSourceMappingURL; } var mapURL = SourceMapURL.getFrom(text); + if (!mapURL) { + return null; + } var baseURL = url.match(/(.+:\/\/.*?)\//)[1]; return baseURL + mapURL; } From d2f79d835d7efa768c65c26ecf42002e9f18cf2d Mon Sep 17 00:00:00 2001 From: Georgiy Kassabli Date: Thu, 14 May 2015 19:45:00 -0100 Subject: [PATCH 19/21] Fixing misprint in RCText.m --- Libraries/Text/RCTText.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m index 632857f01..b4fcc6951 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -25,7 +25,7 @@ if ((self = [super initWithFrame:frame])) { _textStorage = [[NSTextStorage alloc] init]; - self.accessibilityElement = YES; + self.isAccessibilityElement = YES; self.accessibilityTraits |= UIAccessibilityTraitStaticText; self.opaque = NO; From 9c8e085677da67327ef1aa6170b40f70b9f6b3ea Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 14 May 2015 18:38:22 -0300 Subject: [PATCH 20/21] [ReactNative] Fix Movies and SampleApp missing reference to RCTWebSocket --- .../Movies/Movies.xcodeproj/project.pbxproj | 30 ------------------- .../SampleApp.xcodeproj/project.pbxproj | 30 ------------------- 2 files changed, 60 deletions(-) diff --git a/Examples/Movies/Movies.xcodeproj/project.pbxproj b/Examples/Movies/Movies.xcodeproj/project.pbxproj index aaabcbead..e86674f68 100644 --- a/Examples/Movies/Movies.xcodeproj/project.pbxproj +++ b/Examples/Movies/Movies.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 1338BC281B04C72D0064A9C9 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1338BC261B04C7080064A9C9 /* libRCTWebSocket.a */; }; 1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341801B1AA91740003F314A /* libRCTNetwork.a */; }; 13442C061AA90EA00037E5B0 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13442C051AA90E7D0037E5B0 /* libRCTImage.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; @@ -20,13 +19,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 1338BC251B04C7080064A9C9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 3C86DF461ADF2C930047B81A; - remoteInfo = RCTWebSocket; - }; 1341801A1AA91740003F314A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 134180151AA91740003F314A /* RCTNetwork.xcodeproj */; @@ -65,7 +57,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/Text/../WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 134180151AA91740003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; 13442C001AA90E7D0037E5B0 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Movies.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Movies.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -90,21 +81,12 @@ 1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */, 13442C061AA90EA00037E5B0 /* libRCTImage.a in Frameworks */, 58C5726B1AA6239E00CDF9C8 /* libRCTText.a in Frameworks */, - 1338BC281B04C72D0064A9C9 /* libRCTWebSocket.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1338BC1E1B04C7070064A9C9 /* Products */ = { - isa = PBXGroup; - children = ( - 1338BC261B04C7080064A9C9 /* libRCTWebSocket.a */, - ); - name = Products; - sourceTree = ""; - }; 134180161AA91740003F314A /* Products */ = { isa = PBXGroup; children = ( @@ -158,7 +140,6 @@ 134180151AA91740003F314A /* RCTNetwork.xcodeproj */, 13442C001AA90E7D0037E5B0 /* RCTImage.xcodeproj */, 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */, - 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -247,10 +228,6 @@ ProductGroup = 58C572571AA6236500CDF9C8 /* Products */; ProjectRef = 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */; }, - { - ProductGroup = 1338BC1E1B04C7070064A9C9 /* Products */; - ProjectRef = 1338BC1D1B04C7070064A9C9 /* RCTWebSocket.xcodeproj */; - }, { ProductGroup = 14A2D43D1AC3E41A00CC738A /* Products */; ProjectRef = 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */; @@ -264,13 +241,6 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 1338BC261B04C7080064A9C9 /* libRCTWebSocket.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTWebSocket.a; - remoteRef = 1338BC251B04C7080064A9C9 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 1341801B1AA91740003F314A /* libRCTNetwork.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj index b51d08574..adaa6f58b 100644 --- a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj +++ b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj @@ -15,7 +15,6 @@ 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; 00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* SampleAppTests.m */; }; - 1338BBC51B04AC310064A9C9 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1338BBB81B04A6AE0064A9C9 /* libRCTWebSocket.a */; }; 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; @@ -76,13 +75,6 @@ remoteGlobalIDString = 13B07F861A680F5B00A75B9A; remoteInfo = SampleApp; }; - 1338BBB71B04A6AE0064A9C9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 3C86DF461ADF2C930047B81A; - remoteInfo = RCTWebSocket; - }; 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; @@ -124,7 +116,6 @@ 00E356EE1AD99517003FC87E /* SampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* SampleAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleAppTests.m; sourceTree = ""; }; - 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = iOS/AppDelegate.h; sourceTree = ""; }; @@ -160,7 +151,6 @@ 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, - 1338BBC51B04AC310064A9C9 /* libRCTWebSocket.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -232,14 +222,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 1338BBB41B04A6AE0064A9C9 /* Products */ = { - isa = PBXGroup; - children = ( - 1338BBB81B04A6AE0064A9C9 /* libRCTWebSocket.a */, - ); - name = Products; - sourceTree = ""; - }; 139105B71AF99BAD00B5F7CC /* Products */ = { isa = PBXGroup; children = ( @@ -291,7 +273,6 @@ 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, - 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -426,10 +407,6 @@ ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; }, - { - ProductGroup = 1338BBB41B04A6AE0064A9C9 /* Products */; - ProjectRef = 1338BBB31B04A6AE0064A9C9 /* RCTWebSocket.xcodeproj */; - }, { ProductGroup = 146834001AC3E56700842450 /* Products */; ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; @@ -486,13 +463,6 @@ remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 1338BBB81B04A6AE0064A9C9 /* libRCTWebSocket.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTWebSocket.a; - remoteRef = 1338BBB71B04A6AE0064A9C9 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; From 22fb03d7e0e68efe8cdb4eaddf1f5ff84a273877 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 14 May 2015 18:30:34 -0300 Subject: [PATCH 21/21] [ReactNative] Fix UIExplorer missing reference to RCTWebSocket --- .../UIExplorer.xcodeproj/project.pbxproj | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index ca09dcaaf..8b94a27cf 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* UIExplorerTests.m */; }; - 1338BBE31B04ACF90064A9C9 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1338BB9C1B04A6390064A9C9 /* libRCTWebSocket.a */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; @@ -34,13 +33,6 @@ remoteGlobalIDString = 13B07F861A680F5B00A75B9A; remoteInfo = UIExplorer; }; - 1338BB9B1B04A6390064A9C9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 3C86DF461ADF2C930047B81A; - remoteInfo = RCTWebSocket; - }; 13417FE71AA91428003F314A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */; @@ -124,7 +116,6 @@ 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 004D28A21AAF61C70097A701 /* UIExplorerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIExplorerTests.m; sourceTree = ""; }; - 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; @@ -168,7 +159,6 @@ 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */, 134180011AA9153C003F314A /* libRCTText.a in Frameworks */, 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */, - 1338BBE31B04ACF90064A9C9 /* libRCTWebSocket.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -206,19 +196,10 @@ 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */, 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */, - 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */, ); name = Libraries; sourceTree = ""; }; - 1338BB981B04A6390064A9C9 /* Products */ = { - isa = PBXGroup; - children = ( - 1338BB9C1B04A6390064A9C9 /* libRCTWebSocket.a */, - ); - name = Products; - sourceTree = ""; - }; 13417FE41AA91428003F314A /* Products */ = { isa = PBXGroup; children = ( @@ -446,10 +427,6 @@ ProductGroup = D85B82921AB6D5CE003F4FE2 /* Products */; ProjectRef = D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */; }, - { - ProductGroup = 1338BB981B04A6390064A9C9 /* Products */; - ProjectRef = 1338BB971B04A6390064A9C9 /* RCTWebSocket.xcodeproj */; - }, { ProductGroup = 14AADF001AC3DB95002390C9 /* Products */; ProjectRef = 14AADEFF1AC3DB95002390C9 /* React.xcodeproj */; @@ -464,13 +441,6 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 1338BB9C1B04A6390064A9C9 /* libRCTWebSocket.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTWebSocket.a; - remoteRef = 1338BB9B1B04A6390064A9C9 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 13417FE81AA91428003F314A /* libRCTImage.a */ = { isa = PBXReferenceProxy; fileType = archive.ar;