2015-04-19 19:55:46 +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-04-02 14:33:21 +00:00
# import "RCTJavaScriptLoader.h"
# import "RCTBridge.h"
2015-04-23 21:39:51 +00:00
# import "RCTInvalidating.h"
# import "RCTLog.h"
# import "RCTRedBox.h"
2015-04-02 14:33:21 +00:00
# import "RCTSourceCode.h"
# import "RCTUtils.h"
2015-04-23 21:39:51 +00:00
# define NO_REMOTE _MODULE @ "Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
# define NO_LOCAL _BUNDLE @ "Could not load local bundle %@. Ensure file exists."
# define CACHE_DIR @ "RCTJSBundleCache"
# pragma mark - Application Engine
/ * *
* TODO :
* - Add window resize rotation events matching the DOM API .
* - Device pixel ration hooks .
* - Source maps .
* /
2015-04-02 14:33:21 +00:00
@ implementation RCTJavaScriptLoader
{
2015-04-10 14:28:10 +00:00
__weak RCTBridge * _bridge ;
2015-04-02 14:33:21 +00:00
}
2015-04-23 21:39:51 +00:00
/ * *
* ` CADisplayLink` code copied from Ejecta but we ' ve placed the JavaScriptCore
* engine in its own dedicated thread .
*
* TODO : Try adding to the ` RCTJavaScriptExecutor` ' s thread runloop . Removes one
* additional GCD dispatch per frame and likely makes it so that other UIThread
* operations don ' t delay the dispatch ( so we can begin working in JS much
* faster . ) Event handling must still be sent via a GCD dispatch , of course .
*
* We must add the display link to two runloops in order to get setTimeouts to
* fire during scrolling . ( ` NSDefaultRunLoopMode` and ` UITrackingRunLoopMode` )
* TODO : We can invent a ` requestAnimationFrame` and
* ` requestAvailableAnimationFrame` to control if callbacks can be fired during
* an animation .
* http : // stackoverflow . com / questions / 12622800 / why - does - uiscrollview - pause - my - cadisplaylink
*
* /
2015-04-02 14:33:21 +00:00
- ( instancetype ) initWithBridge : ( RCTBridge * ) bridge
{
2015-04-11 22:08:00 +00:00
if ( ( self = [ super init ] ) ) {
2015-04-02 14:33:21 +00:00
_bridge = bridge ;
}
return self ;
}
- ( void ) loadBundleAtURL : ( NSURL * ) scriptURL onComplete : ( void ( ^ ) ( NSError * ) ) onComplete
{
2015-04-02 22:26:39 +00:00
if ( scriptURL = = nil ) {
2015-04-11 22:08:00 +00:00
NSError * error = [ NSError errorWithDomain : @ "JavaScriptLoader" code : 1 userInfo : @ {
2015-04-23 21:39:51 +00:00
NSLocalizedDescriptionKey : @ "No script URL provided"
2015-04-11 22:08:00 +00:00
} ] ;
2015-04-02 14:33:21 +00:00
onComplete ( error ) ;
return ;
2015-04-11 22:08:00 +00:00
}
2015-04-23 21:39:51 +00:00
if ( [ scriptURL isFileURL ] ) {
NSString * bundlePath = [ [ NSBundle bundleForClass : [ self class ] ] resourcePath ] ;
NSString * localPath = [ scriptURL . absoluteString substringFromIndex : @ "file://" . length ] ;
if ( ! [ localPath hasPrefix : bundlePath ] ) {
NSString * absolutePath = [ NSString stringWithFormat : @ "%@/%@" , bundlePath , localPath ] ;
scriptURL = [ NSURL fileURLWithPath : absolutePath ] ;
}
}
2015-04-02 14:33:21 +00:00
NSURLSessionDataTask * task = [ [ NSURLSession sharedSession ] dataTaskWithURL : scriptURL completionHandler :
^ ( NSData * data , NSURLResponse * response , NSError * error ) {
2015-04-23 21:39:51 +00:00
// Handle general request errors
if ( error ) {
if ( [ [ error domain ] isEqualToString : NSURLErrorDomain ] ) {
NSString * desc = [ @ "Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString : [ scriptURL absoluteString ] ] ;
NSDictionary * userInfo = @ {
NSLocalizedDescriptionKey : desc ,
NSLocalizedFailureReasonErrorKey : [ error localizedDescription ] ,
NSUnderlyingErrorKey : error ,
} ;
error = [ NSError errorWithDomain : @ "JSServer"
code : error . code
userInfo : userInfo ] ;
}
onComplete ( error ) ;
return ;
}
2015-04-02 14:33:21 +00:00
2015-04-23 21:39:51 +00:00
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding ;
if ( response . textEncodingName ! = nil ) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding ( ( CFStringRef ) response . textEncodingName ) ;
if ( cfEncoding ! = kCFStringEncodingInvalidId ) {
encoding = CFStringConvertEncodingToNSStringEncoding ( cfEncoding ) ;
}
}
NSString * rawText = [ [ NSString alloc ] initWithData : data encoding : encoding ] ;
// Handle HTTP errors
if ( [ response isKindOfClass : [ NSHTTPURLResponse class ] ] && [ ( NSHTTPURLResponse * ) response statusCode ] ! = 200 ) {
NSDictionary * userInfo ;
NSDictionary * errorDetails = RCTJSONParse ( rawText , nil ) ;
if ( [ errorDetails isKindOfClass : [ NSDictionary class ] ] &&
[ errorDetails [ @ "errors" ] isKindOfClass : [ NSArray class ] ] ) {
NSMutableArray * fakeStack = [ [ NSMutableArray alloc ] init ] ;
for ( NSDictionary * err in errorDetails [ @ "errors" ] ) {
[ fakeStack addObject : @ {
@ "methodName" : err [ @ "description" ] ? : @ "" ,
@ "file" : err [ @ "filename" ] ? : @ "" ,
@ "lineNumber" : err [ @ "lineNumber" ] ? : @ 0
} ] ;
}
userInfo = @ {
NSLocalizedDescriptionKey : errorDetails [ @ "message" ] ? : @ "No message provided" ,
@ "stack" : fakeStack ,
} ;
} else {
userInfo = @ { NSLocalizedDescriptionKey : rawText } ;
}
error = [ NSError errorWithDomain : @ "JSServer"
code : [ ( NSHTTPURLResponse * ) response statusCode ]
userInfo : userInfo ] ;
onComplete ( error ) ;
return ;
}
RCTSourceCode * sourceCodeModule = _bridge . modules [ RCTBridgeModuleNameForClass ( [ RCTSourceCode class ] ) ] ;
sourceCodeModule . scriptURL = scriptURL ;
sourceCodeModule . scriptText = rawText ;
[ _bridge enqueueApplicationScript : rawText url : scriptURL onComplete : ^ ( NSError * scriptError ) {
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
onComplete ( scriptError ) ;
} ) ;
} ] ;
} ] ;
2015-04-02 14:33:21 +00:00
[ task resume ] ;
}
@ end