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-25 21:52:18 +00:00
# import "RCTConvert.h"
2015-04-02 14:33:21 +00:00
# import "RCTSourceCode.h"
# import "RCTUtils.h"
2015-10-21 17:53:35 +00:00
# import "RCTPerformanceLogger.h"
2015-04-02 14:33:21 +00:00
2016-03-17 17:34:46 +00:00
# include < sys / stat . h >
uint32_t const RCTRAMBundleMagicNumber = 0 xFB0BD1E5 ;
2015-04-02 14:33:21 +00:00
@ implementation RCTJavaScriptLoader
2015-08-24 10:14:33 +00:00
RCT_NOT _IMPLEMENTED ( - ( instancetype ) init )
2015-06-15 14:53:45 +00:00
2015-07-28 22:48:46 +00:00
+ ( void ) loadBundleAtURL : ( NSURL * ) scriptURL onComplete : ( RCTSourceLoadBlock ) onComplete
2015-04-02 14:33:21 +00:00
{
2016-05-23 18:03:17 +00:00
NSString * unsanitizedScriptURLString = scriptURL . absoluteString ;
2015-04-25 21:52:18 +00:00
// Sanitize the script URL
2016-05-23 18:03:17 +00:00
scriptURL = [ RCTConvert NSURL : unsanitizedScriptURLString ] ;
2015-04-25 21:52:18 +00:00
2015-10-08 19:28:38 +00:00
if ( ! scriptURL ) {
2016-05-23 18:03:17 +00:00
NSString * errorDescription = [ NSString stringWithFormat : @ "No script URL provided."
@ "unsanitizedScriptURLString:(%@)" , unsanitizedScriptURLString ] ;
2015-04-11 22:08:00 +00:00
NSError * error = [ NSError errorWithDomain : @ "JavaScriptLoader" code : 1 userInfo : @ {
2016-05-23 18:03:17 +00:00
NSLocalizedDescriptionKey : errorDescription
2015-04-11 22:08:00 +00:00
} ] ;
2016-07-07 14:20:03 +00:00
onComplete ( error , nil , 0 ) ;
2015-04-02 14:33:21 +00:00
return ;
2015-04-11 22:08:00 +00:00
}
2015-10-08 19:28:38 +00:00
// Load local script file
if ( scriptURL . fileURL ) {
2016-07-07 20:31:21 +00:00
// Load the first 4 bytes to check if the bundle is regular or RAM ( "Random Access Modules" bundle ) .
// The RAM bundle has a magic number in the 4 first bytes ` ( 0 xFB0BD1E5 ) ` .
// The benefit of RAM bundle over a regular bundle is that we can lazily inject
// modules into JSC as they ' re required .
FILE * bundle = fopen ( scriptURL . path . UTF8String , "r" ) ;
if ( ! bundle ) {
onComplete ( RCTErrorWithMessage ( [ NSString stringWithFormat : @ "Error opening bundle %@" , scriptURL . path ] ) , nil , 0 ) ;
return ;
}
uint32_t magicNumber ;
size_t readResult = fread ( & magicNumber , sizeof ( magicNumber ) , 1 , bundle ) ;
fclose ( bundle ) ;
if ( readResult ! = 1 ) {
onComplete ( RCTErrorWithMessage ( @ "Error reading bundle" ) , nil , 0 ) ;
return ;
}
magicNumber = NSSwapLittleIntToHost ( magicNumber ) ;
if ( magicNumber = = RCTRAMBundleMagicNumber ) {
NSData * source = [ NSData dataWithBytes : & magicNumber length : sizeof ( magicNumber ) ] ;
2016-07-07 20:31:20 +00:00
NSError * error = nil ;
2016-03-21 10:20:49 +00:00
int64_t sourceLength = 0 ;
2016-07-07 20:31:21 +00:00
struct stat statInfo ;
if ( stat ( scriptURL . path . UTF8String , & statInfo ) ! = 0 ) {
error = RCTErrorWithMessage ( @ "Error reading bundle" ) ;
2016-03-17 17:34:46 +00:00
} else {
2016-07-07 20:31:21 +00:00
sourceLength = statInfo . st_size ;
2016-03-17 17:34:46 +00:00
}
2016-07-07 14:20:03 +00:00
onComplete ( error , source , sourceLength ) ;
2016-07-10 19:23:42 +00:00
} else {
// Reading in a large bundle can be slow . Dispatch to the background queue to do it .
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
NSError * error = nil ;
NSData * source = [ NSData dataWithContentsOfFile : scriptURL . path
options : NSDataReadingMappedIfSafe
error : & error ] ;
onComplete ( error , source , source . length ) ;
} ) ;
2016-07-07 20:31:21 +00:00
}
2015-10-08 19:28:38 +00:00
return ;
}
// Load remote script file
2015-04-02 14:33:21 +00:00
NSURLSessionDataTask * task = [ [ NSURLSession sharedSession ] dataTaskWithURL : scriptURL completionHandler :
^ ( NSData * data , NSURLResponse * response , NSError * error ) {
2015-04-25 21:52:18 +00:00
// Handle general request errors
if ( error ) {
2015-08-24 10:14:33 +00:00
if ( [ error . domain isEqualToString : NSURLErrorDomain ] ) {
2015-09-01 12:12:48 +00:00
NSString * desc = [ @ "Could not connect to development server.\n\nEnsure the following:\n- Node server is running and available on the same network - run 'npm start' from react-native root\n- Node server URL is correctly set in AppDelegate\n\nURL: " stringByAppendingString : scriptURL . absoluteString ] ;
2015-04-25 21:52:18 +00:00
NSDictionary * userInfo = @ {
NSLocalizedDescriptionKey : desc ,
2015-08-24 10:14:33 +00:00
NSLocalizedFailureReasonErrorKey : error . localizedDescription ,
2015-04-25 21:52:18 +00:00
NSUnderlyingErrorKey : error ,
} ;
error = [ NSError errorWithDomain : @ "JSServer"
code : error . code
userInfo : userInfo ] ;
}
2016-07-07 14:20:03 +00:00
onComplete ( error , nil , 0 ) ;
2015-04-25 21:52:18 +00:00
return ;
}
2015-04-23 21:39:51 +00:00
2015-04-25 21:52:18 +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 ) ;
}
}
// Handle HTTP errors
2015-08-24 10:14:33 +00:00
if ( [ response isKindOfClass : [ NSHTTPURLResponse class ] ] && ( ( NSHTTPURLResponse * ) response ) . statusCode ! = 200 ) {
2015-10-16 15:10:25 +00:00
NSString * rawText = [ [ NSString alloc ] initWithData : data encoding : encoding ] ;
2015-04-25 21:52:18 +00:00
NSDictionary * userInfo ;
NSDictionary * errorDetails = RCTJSONParse ( rawText , nil ) ;
if ( [ errorDetails isKindOfClass : [ NSDictionary class ] ] &&
[ errorDetails [ @ "errors" ] isKindOfClass : [ NSArray class ] ] ) {
2015-11-03 22:45:46 +00:00
NSMutableArray < NSDictionary * > * fakeStack = [ NSMutableArray new ] ;
2015-04-25 21:52:18 +00:00
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"
2015-08-24 10:14:33 +00:00
code : ( ( NSHTTPURLResponse * ) response ) . statusCode
2015-04-25 21:52:18 +00:00
userInfo : userInfo ] ;
2016-07-07 14:20:03 +00:00
onComplete ( error , nil , 0 ) ;
2015-04-25 21:52:18 +00:00
return ;
}
2016-07-07 14:20:03 +00:00
onComplete ( nil , data , data . length ) ;
2015-04-25 21:52:18 +00:00
} ] ;
2015-04-02 14:33:21 +00:00
[ task resume ] ;
}
@ end