2015-03-23 20:28:42 +00:00
/ * *
* 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 .
* /
2015-03-19 16:55:40 +00:00
# import "RCTWebSocketExecutor.h"
# import "RCTLog.h"
# import "RCTUtils.h"
# import "SRWebSocket.h"
typedef void ( ^ WSMessageCallback ) ( NSError * error , NSDictionary * reply ) ;
@ interface RCTWebSocketExecutor ( ) < SRWebSocketDelegate >
@ end
@ implementation RCTWebSocketExecutor {
SRWebSocket * _socket ;
NSOperationQueue * _jsQueue ;
NSMutableDictionary * _callbacks ;
dispatch_semaphore _t _socketOpenSemaphore ;
NSMutableDictionary * _injectedObjects ;
}
- ( instancetype ) init
{
2015-03-19 19:10:41 +00:00
return [ self initWithURL : [ NSURL URLWithString : @ "http://localhost:8081/debugger-proxy" ] ] ;
2015-03-19 16:55:40 +00:00
}
- ( instancetype ) initWithURL : ( NSURL * ) url
{
if ( self = [ super init ] ) {
_jsQueue = [ [ NSOperationQueue alloc ] init ] ;
_jsQueue . maxConcurrentOperationCount = 1 ;
_socket = [ [ SRWebSocket alloc ] initWithURL : url ] ;
_socket . delegate = self ;
_callbacks = [ NSMutableDictionary dictionary ] ;
_injectedObjects = [ NSMutableDictionary dictionary ] ;
[ _socket setDelegateOperationQueue : _jsQueue ] ;
2015-03-19 19:10:41 +00:00
NSURL * startDevToolsURL = [ NSURL URLWithString : @ "/launch-chrome-devtools" relativeToURL : url ] ;
[ NSURLConnection connectionWithRequest : [ NSURLRequest requestWithURL : startDevToolsURL ] delegate : nil ] ;
2015-03-19 16:55:40 +00:00
if ( ! [ self connectToProxy ] ) {
2015-04-08 00:53:56 +00:00
RCTLogError ( @ "Connection to %@ timed out. Are you running node proxy? If you are running on the device check if you have the right IP address on `RCTWebSocketExecutor.m` file." , url ) ;
2015-03-19 16:55:40 +00:00
[ self invalidate ] ;
return nil ;
}
NSInteger retries = 3 ;
BOOL runtimeIsReady = [ self prepareJSRuntime ] ;
while ( ! runtimeIsReady && retries > 0 ) {
runtimeIsReady = [ self prepareJSRuntime ] ;
retries - - ;
}
if ( ! runtimeIsReady ) {
RCTLogError ( @ "Runtime is not ready. Do you have Chrome open?" ) ;
[ self invalidate ] ;
return nil ;
}
}
return self ;
}
- ( BOOL ) connectToProxy
{
_socketOpenSemaphore = dispatch_semaphore _create ( 0 ) ;
[ _socket open ] ;
long connected = dispatch_semaphore _wait ( _socketOpenSemaphore , dispatch_time ( DISPATCH_TIME _NOW , NSEC_PER _SEC * 2 ) ) ;
return connected = = 0 ;
}
- ( BOOL ) prepareJSRuntime
{
__block NSError * initError ;
dispatch_semaphore _t s = dispatch_semaphore _create ( 0 ) ;
[ self sendMessage : @ { @ "method" : @ "prepareJSRuntime" } waitForReply : ^ ( NSError * error , NSDictionary * reply ) {
initError = error ;
dispatch_semaphore _signal ( s ) ;
} ] ;
long runtimeIsReady = dispatch_semaphore _wait ( s , dispatch_time ( DISPATCH_TIME _NOW , NSEC_PER _SEC ) ) ;
return runtimeIsReady = = 0 && initError = = nil ;
}
- ( void ) webSocket : ( SRWebSocket * ) webSocket didReceiveMessage : ( id ) message
{
NSError * error = nil ;
NSDictionary * reply = RCTJSONParse ( message , & error ) ;
NSUInteger messageID = [ reply [ @ "replyID" ] integerValue ] ;
WSMessageCallback callback = [ _callbacks objectForKey : @ ( messageID ) ] ;
if ( callback ) {
callback ( error , reply ) ;
}
}
- ( void ) webSocketDidOpen : ( SRWebSocket * ) webSocket
{
dispatch_semaphore _signal ( _socketOpenSemaphore ) ;
}
- ( void ) webSocket : ( SRWebSocket * ) webSocket didFailWithError : ( NSError * ) error
{
RCTLogError ( @ "WebSocket connection failed with error %@" , error ) ;
}
- ( void ) webSocket : ( SRWebSocket * ) webSocket didCloseWithCode : ( NSInteger ) code reason : ( NSString * ) reason wasClean : ( BOOL ) wasClean
{
}
- ( void ) sendMessage : ( NSDictionary * ) message waitForReply : ( WSMessageCallback ) callback
{
static NSUInteger lastID = 10000 ;
[ _jsQueue addOperationWithBlock : ^ {
if ( ! self . valid ) {
2015-04-07 14:36:26 +00:00
NSError * error = [ NSError errorWithDomain : @ "WS" code : 1 userInfo : @ {
NSLocalizedDescriptionKey : @ "socket closed"
} ] ;
2015-03-19 16:55:40 +00:00
callback ( error , nil ) ;
return ;
}
NSUInteger expectedID = lastID + + ;
_callbacks [ @ ( expectedID ) ] = [ callback copy ] ;
NSMutableDictionary * messageWithID = [ message mutableCopy ] ;
messageWithID [ @ "id" ] = @ ( expectedID ) ;
[ _socket send : RCTJSONStringify ( messageWithID , NULL ) ] ;
} ] ;
}
- ( void ) executeApplicationScript : ( NSString * ) script sourceURL : ( NSURL * ) url onComplete : ( RCTJavaScriptCompleteBlock ) onComplete
{
NSDictionary * message = @ { @ "method" : NSStringFromSelector ( _cmd ) , @ "url" : [ url absoluteString ] , @ "inject" : _injectedObjects } ;
[ self sendMessage : message waitForReply : ^ ( NSError * error , NSDictionary * reply ) {
onComplete ( error ) ;
} ] ;
}
- ( void ) executeJSCall : ( NSString * ) name method : ( NSString * ) method arguments : ( NSArray * ) arguments callback : ( RCTJavaScriptCallback ) onComplete
{
RCTAssert ( onComplete ! = nil , @ "callback was missing for exec JS call" ) ;
NSDictionary * message = @ { @ "method" : NSStringFromSelector ( _cmd ) , @ "moduleName" : name , @ "moduleMethod" : method , @ "arguments" : arguments } ;
[ self sendMessage : message waitForReply : ^ ( NSError * socketError , NSDictionary * reply ) {
if ( socketError ) {
onComplete ( nil , socketError ) ;
return ;
}
NSString * result = reply [ @ "result" ] ;
id objcValue = RCTJSONParse ( result , NULL ) ;
onComplete ( objcValue , nil ) ;
} ] ;
}
- ( void ) injectJSONText : ( NSString * ) script asGlobalObjectNamed : ( NSString * ) objectName callback : ( RCTJavaScriptCompleteBlock ) onComplete
{
[ _jsQueue addOperationWithBlock : ^ {
[ _injectedObjects setObject : script forKey : objectName ] ;
onComplete ( nil ) ;
} ] ;
}
- ( void ) invalidate
{
2015-04-02 14:33:21 +00:00
[ _jsQueue cancelAllOperations ] ;
2015-03-19 16:55:40 +00:00
_socket . delegate = nil ;
[ _socket closeWithCode : 1000 reason : @ "Invalidated" ] ;
_socket = nil ;
}
- ( BOOL ) isValid
{
return _socket ! = nil && _socket . readyState = = SR_OPEN ;
}
- ( void ) dealloc
{
RCTAssert ( ! self . valid , @ "-invalidate must be called before -dealloc" ) ;
}
@ end