Skip to content

Commit 4242bd9

Browse files
committed
Replace percent escapes in file URL before using as path
Summary: This is to fix a bug that prevents bundling of projects that contain spaces (or other special characters) in their names. #### Reproduction steps before the fix 1. Create a project with a space in the name: ![screen shot 2015-04-16 at 17 23 46](https://cloud.githubusercontent.com/assets/1121616/7176887/63af36de-e45d-11e4-9aa9-40586560b716.png) 2. Follow the steps in `OPTION 2` for running from a bundled file, i.e. create the `main.bundle` file, add it to the project if is not there already, and uncomment `jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];` 3. Run the application. This is what happens: ![screen shot 2015-04-16 at 17 27 48](https://cloud.githubusercontent.com/assets/1121616/7176955/f139764a-e45d-11e4-8dc8-3c13aab70828.png) To prove that it has to do with a space in the name, refactor the project name to not contain a space: ![screen shot 2015-04-16 at 17 28 27](https://cloud.githubusercontent.com/assets/1121616/7176966/056b6c9a Closes #876 Github Author: Herman Schaaf <[email protected]> Test Plan: Imported from GitHub, without a `Test Plan:` line.
1 parent 357a545 commit 4242bd9

File tree

1 file changed

+71
-108
lines changed

1 file changed

+71
-108
lines changed

React/Base/RCTJavaScriptLoader.m

Lines changed: 71 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,15 @@
1010
#import "RCTJavaScriptLoader.h"
1111

1212
#import "RCTBridge.h"
13-
#import "RCTInvalidating.h"
14-
#import "RCTLog.h"
15-
#import "RCTRedBox.h"
13+
#import "RCTConvert.h"
1614
#import "RCTSourceCode.h"
1715
#import "RCTUtils.h"
1816

19-
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
20-
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
21-
22-
#define CACHE_DIR @"RCTJSBundleCache"
23-
24-
#pragma mark - Application Engine
25-
26-
/**
27-
* TODO:
28-
* - Add window resize rotation events matching the DOM API.
29-
* - Device pixel ration hooks.
30-
* - Source maps.
31-
*/
3217
@implementation RCTJavaScriptLoader
3318
{
3419
__weak RCTBridge *_bridge;
3520
}
3621

37-
/**
38-
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
39-
* engine in its own dedicated thread.
40-
*
41-
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
42-
* additional GCD dispatch per frame and likely makes it so that other UIThread
43-
* operations don't delay the dispatch (so we can begin working in JS much
44-
* faster.) Event handling must still be sent via a GCD dispatch, of course.
45-
*
46-
* We must add the display link to two runloops in order to get setTimeouts to
47-
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
48-
* TODO: We can invent a `requestAnimationFrame` and
49-
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
50-
* an animation.
51-
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
52-
*
53-
*/
5422
- (instancetype)initWithBridge:(RCTBridge *)bridge
5523
{
5624
if ((self = [super init])) {
@@ -61,92 +29,87 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
6129

6230
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
6331
{
32+
NSURL *originalURL = scriptURL;
33+
if (!scriptURL.scheme || [scriptURL isFileURL]) {
34+
scriptURL = [RCTConvert NSURL:scriptURL.path];
35+
}
36+
6437
if (scriptURL == nil) {
6538
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
66-
NSLocalizedDescriptionKey: @"No script URL provided"
39+
NSLocalizedDescriptionKey: originalURL ? [NSString stringWithFormat:@"Script URL '%@' could not be found.", originalURL] : @"No script URL provided."
6740
}];
6841
onComplete(error);
6942
return;
7043
}
7144

72-
if ([scriptURL isFileURL]) {
73-
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
74-
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];
75-
76-
if (![localPath hasPrefix:bundlePath]) {
77-
NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath];
78-
scriptURL = [NSURL fileURLWithPath:absolutePath];
79-
}
80-
}
81-
8245
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
8346
^(NSData *data, NSURLResponse *response, NSError *error) {
8447

85-
// Handle general request errors
86-
if (error) {
87-
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
88-
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]];
89-
NSDictionary *userInfo = @{
90-
NSLocalizedDescriptionKey: desc,
91-
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
92-
NSUnderlyingErrorKey: error,
93-
};
94-
error = [NSError errorWithDomain:@"JSServer"
95-
code:error.code
96-
userInfo:userInfo];
97-
}
98-
onComplete(error);
99-
return;
100-
}
101-
102-
// Parse response as text
103-
NSStringEncoding encoding = NSUTF8StringEncoding;
104-
if (response.textEncodingName != nil) {
105-
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
106-
if (cfEncoding != kCFStringEncodingInvalidId) {
107-
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
108-
}
109-
}
110-
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
111-
112-
// Handle HTTP errors
113-
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
114-
NSDictionary *userInfo;
115-
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
116-
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
117-
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
118-
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
119-
for (NSDictionary *err in errorDetails[@"errors"]) {
120-
[fakeStack addObject: @{
121-
@"methodName": err[@"description"] ?: @"",
122-
@"file": err[@"filename"] ?: @"",
123-
@"lineNumber": err[@"lineNumber"] ?: @0
124-
}];
125-
}
126-
userInfo = @{
127-
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
128-
@"stack": fakeStack,
129-
};
130-
} else {
131-
userInfo = @{NSLocalizedDescriptionKey: rawText};
132-
}
133-
error = [NSError errorWithDomain:@"JSServer"
134-
code:[(NSHTTPURLResponse *)response statusCode]
135-
userInfo:userInfo];
136-
137-
onComplete(error);
138-
return;
139-
}
140-
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
141-
sourceCodeModule.scriptURL = scriptURL;
142-
sourceCodeModule.scriptText = rawText;
48+
// Handle general request errors
49+
if (error) {
50+
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
51+
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]];
52+
NSDictionary *userInfo = @{
53+
NSLocalizedDescriptionKey: desc,
54+
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
55+
NSUnderlyingErrorKey: error,
56+
};
57+
error = [NSError errorWithDomain:@"JSServer"
58+
code:error.code
59+
userInfo:userInfo];
60+
}
61+
onComplete(error);
62+
return;
63+
}
14364

144-
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
145-
dispatch_async(dispatch_get_main_queue(), ^{
146-
onComplete(scriptError);
147-
});
148-
}];
149-
}];
65+
// Parse response as text
66+
NSStringEncoding encoding = NSUTF8StringEncoding;
67+
if (response.textEncodingName != nil) {
68+
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
69+
if (cfEncoding != kCFStringEncodingInvalidId) {
70+
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
71+
}
72+
}
73+
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
74+
75+
// Handle HTTP errors
76+
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
77+
NSDictionary *userInfo;
78+
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
79+
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
80+
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
81+
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
82+
for (NSDictionary *err in errorDetails[@"errors"]) {
83+
[fakeStack addObject: @{
84+
@"methodName": err[@"description"] ?: @"",
85+
@"file": err[@"filename"] ?: @"",
86+
@"lineNumber": err[@"lineNumber"] ?: @0
87+
}];
88+
}
89+
userInfo = @{
90+
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
91+
@"stack": fakeStack,
92+
};
93+
} else {
94+
userInfo = @{NSLocalizedDescriptionKey: rawText};
95+
}
96+
error = [NSError errorWithDomain:@"JSServer"
97+
code:[(NSHTTPURLResponse *)response statusCode]
98+
userInfo:userInfo];
99+
100+
onComplete(error);
101+
return;
102+
}
103+
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
104+
sourceCodeModule.scriptURL = scriptURL;
105+
sourceCodeModule.scriptText = rawText;
106+
107+
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
108+
dispatch_async(dispatch_get_main_queue(), ^{
109+
onComplete(scriptError);
110+
});
111+
}];
112+
}];
150113

151114
[task resume];
152115
}

0 commit comments

Comments
 (0)