From 3ccd99fb5399995e476f9b57802d7651179b5752 Mon Sep 17 00:00:00 2001 From: Nathan Azaria Date: Tue, 31 May 2016 04:56:49 -0700 Subject: [PATCH] Added RCTBundleURLProvider Reviewed By: javache Differential Revision: D3352568 fbshipit-source-id: fbba6771a1c581e2676bd0f81d3da62dbf21916b --- .../UIExplorer.xcodeproj/project.pbxproj | 4 + .../RCTBundleURLProviderTests.m | 103 +++++++++++ React/Base/RCTBundleURLProvider.h | 38 ++++ React/Base/RCTBundleURLProvider.m | 167 ++++++++++++++++++ React/React.xcodeproj/project.pbxproj | 6 + 5 files changed, 318 insertions(+) create mode 100644 Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m create mode 100644 React/Base/RCTBundleURLProvider.h create mode 100644 React/Base/RCTBundleURLProvider.m diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 7a307ded6..d2b3784bb 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ 27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; }; + 68FF44381CF6111500720EFD /* RCTBundleURLProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; }; 8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */; }; @@ -248,6 +249,7 @@ 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; + 68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBundleURLProviderTests.m; sourceTree = ""; }; 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = ""; }; 8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderTests.m; sourceTree = ""; }; 8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderHelpers.m; sourceTree = ""; }; @@ -415,6 +417,7 @@ isa = PBXGroup; children = ( 13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */, + 68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */, 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */, 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */, 1497CFA61B21F5E400C1F8F2 /* RCTJSCExecutorTests.m */, @@ -903,6 +906,7 @@ 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */, 13B6C1A31C34225900D3FAF5 /* RCTURLUtilsTests.m in Sources */, 8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */, + 68FF44381CF6111500720EFD /* RCTBundleURLProviderTests.m in Sources */, 8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m new file mode 100644 index 000000000..bb044887c --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m @@ -0,0 +1,103 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#import + +#import "RCTBundleURLProvider.h" +#import "RCTUtils.h" + + +static NSString *const testFile = @"test.jsbundle"; +static NSString *const mainBundle = @"main.jsbundle"; + +static NSURL *mainBundleURL() +{ + return [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:mainBundle]; +} + +static NSURL *localhostBundleURL() +{ + return [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true&minify=false", testFile]]; +} + +static NSURL *ipBundleURL() +{ + return [NSURL URLWithString:[NSString stringWithFormat:@"http://192.168.1.1:8081/%@.bundle?platform=ios&dev=true&minify=false", testFile]]; +} + +@implementation NSBundle (RCTBundleURLProviderTests) + +- (NSURL *)RCT_URLForResource:(NSString *)name withExtension:(NSString *)ext +{ + // Ensure that test files is always reported as existing + if ([[name stringByAppendingFormat:@".%@", ext] isEqualToString:mainBundle]) { + return [[self bundleURL] URLByAppendingPathComponent:mainBundle]; + } + return [self RCT_URLForResource:name withExtension:ext]; +} + +@end + +@interface RCTBundleURLProviderTests : XCTestCase +@end + +@implementation RCTBundleURLProviderTests + +- (void)setUp +{ + [super setUp]; + + RCTSwapInstanceMethods([NSBundle class], + @selector(URLForResource:withExtension:), + @selector(RCT_URLForResource:withExtension:)); + [[RCTBundleURLProvider sharedSettings] setDefaults]; +} + +- (void)tearDown +{ + RCTSwapInstanceMethods([NSBundle class], + @selector(URLForResource:withExtension:), + @selector(RCT_URLForResource:withExtension:)); + + [super tearDown]; +} + +- (void)testBundleURL +{ + RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings]; + settings.jsLocation = nil; + NSURL *URL = [settings jsBundleURLForBundleRoot:testFile fallbackResource:nil]; + if (!getenv("CI_USE_PACKAGER")) { + XCTAssertEqualObjects(URL, mainBundleURL()); + } else { + XCTAssertEqualObjects(URL, localhostBundleURL()); + } +} + +- (void)testLocalhostURL +{ + RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings]; + settings.jsLocation = @"localhost"; + NSURL *URL = [settings jsBundleURLForBundleRoot:testFile fallbackResource:nil]; + XCTAssertEqualObjects(URL, localhostBundleURL()); +} + +- (void)testIPURL +{ + RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings]; + settings.jsLocation = @"192.168.1.1"; + NSURL *URL = [settings jsBundleURLForBundleRoot:testFile fallbackResource:nil]; + XCTAssertEqualObjects(URL, ipBundleURL()); +} + +@end diff --git a/React/Base/RCTBundleURLProvider.h b/React/Base/RCTBundleURLProvider.h new file mode 100644 index 000000000..4ae517d6d --- /dev/null +++ b/React/Base/RCTBundleURLProvider.h @@ -0,0 +1,38 @@ +/** + * 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 + +@interface RCTBundleURLProvider : NSObject + +extern NSString *const RCTBundleURLProviderUpdatedNotification; + +/** + * Set default settings on NSUserDefaults. + */ +- (void)setDefaults; + +/** + * Returns the jsBundleURL for a given bundle entrypoint and + * the fallback offline JS bundle if the packager is not running. + */ +- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + fallbackResource:(NSString *)resourceName; + +/** + * The IP address or hostname of the packager. + */ +@property (nonatomic, copy) NSString *jsLocation; + +@property (nonatomic, assign) BOOL enableLiveReload; +@property (nonatomic, assign) BOOL enableMinification; +@property (nonatomic, assign) BOOL enableDev; + ++ (instancetype)sharedSettings; +@end diff --git a/React/Base/RCTBundleURLProvider.m b/React/Base/RCTBundleURLProvider.m new file mode 100644 index 000000000..f931b9a62 --- /dev/null +++ b/React/Base/RCTBundleURLProvider.m @@ -0,0 +1,167 @@ +/** + * 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 "RCTBundleURLProvider.h" +#import "RCTDefines.h" +#import "RCTConvert.h" + +NSString *const RCTBundleURLProviderUpdatedNotification = @"RCTBundleURLProviderUpdatedNotification"; + +static NSString *const kRCTJsLocationKey = @"RCT_jsLocation"; +static NSString *const kRCTEnableLiveReloadKey = @"RCT_enableLiveReload"; +static NSString *const kRCTEnableDevKey = @"RCT_enableDev"; +static NSString *const kRCTEnableMinificationKey = @"RCT_enableMinification"; + +static NSString *const kDefaultPort = @"8081"; + +@implementation RCTBundleURLProvider + +- (NSDictionary *)defaults +{ + static NSDictionary *defaults; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaults = @{ + kRCTEnableLiveReloadKey: @NO, + kRCTEnableDevKey: @YES, + kRCTEnableMinificationKey: @NO, + }; + }); + return defaults; +} + +- (void)settingsUpdated +{ + [[NSNotificationCenter defaultCenter] postNotificationName:RCTBundleURLProviderUpdatedNotification object:self]; +} + +- (void)setDefaults +{ + [[NSUserDefaults standardUserDefaults] registerDefaults:[self defaults]]; + [self settingsUpdated]; +} + +- (BOOL)isPackagerRunning:(NSString *)host +{ + if (RCT_DEV) { + NSURL *url = [[NSURL URLWithString:serverRootWithHost(host)] URLByAppendingPathComponent:@"status"]; + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + NSURLResponse *response; + NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL]; + NSString *status = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return [status isEqualToString:@"packager-status:running"]; + } + return NO; +} + +static NSString *serverRootWithHost(NSString *host) +{ + return [NSString stringWithFormat:@"http://%@:%@/", host, kDefaultPort]; +} + +- (NSString *)guessPackagerHost +{ + NSString *host = @"localhost"; + //TODO: Implement automatic IP address detection + if ([self isPackagerRunning:host]) { + return host; + } + return nil; +} + + +- (NSString *)packagerServerRoot +{ + NSString *location = [self jsLocation]; + if (location != nil) { + return serverRootWithHost(location); + } else { + NSString *host = [self guessPackagerHost]; + if (!host) { + return nil; + } else { + return serverRootWithHost(host); + } + } +} + +- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackResource:(NSString *)resourceName +{ + resourceName = resourceName ?: @"main"; + NSString *serverRoot = [self packagerServerRoot]; + if (!serverRoot) { + return [[NSBundle mainBundle] URLForResource:resourceName withExtension:@"jsbundle"]; + } else { + NSString *fullBundlePath = [serverRoot stringByAppendingFormat:@"%@.bundle", bundleRoot]; + if ([fullBundlePath hasPrefix:@"http"]) { + NSString *dev = [self enableDev] ? @"true" : @"false"; + NSString *min = [self enableMinification] ? @"true": @"false"; + fullBundlePath = [fullBundlePath stringByAppendingFormat:@"?platform=ios&dev=%@&minify=%@", dev, min]; + } + return [NSURL URLWithString:fullBundlePath]; + } +} + +- (void)updateDefaults:(id)object forKey:(NSString *)key +{ + [[NSUserDefaults standardUserDefaults] setObject:object forKey:key]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self settingsUpdated]; +} + +- (BOOL)enableDev +{ + return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableDevKey]; +} + +- (BOOL)enableLiveReload +{ + return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableLiveReloadKey]; +} + +- (BOOL)enableMinification +{ + return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableMinificationKey]; +} + +- (NSString *)jsLocation +{ + return [[NSUserDefaults standardUserDefaults] stringForKey:kRCTJsLocationKey]; +} + +- (void)setEnableDev:(BOOL)enableDev +{ + [self updateDefaults:@(enableDev) forKey:kRCTEnableDevKey]; +} + +- (void)setEnableEnableLiveReload:(BOOL)enableLiveReload +{ + [self updateDefaults:@(enableLiveReload) forKey:kRCTEnableLiveReloadKey]; +} + +- (void)setJsLocation:(NSString *)jsLocation +{ + [self updateDefaults:jsLocation forKey:kRCTJsLocationKey]; +} + +- (void)setEnableMinification:(BOOL)enableMinification +{ + [self updateDefaults:@(enableMinification) forKey:kRCTEnableMinificationKey]; +} + ++ (instancetype)sharedSettings +{ + static RCTBundleURLProvider *sharedInstance; + static dispatch_once_t once_token; + dispatch_once(&once_token, ^{ + sharedInstance = [RCTBundleURLProvider new]; + }); + return sharedInstance; +} +@end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index ed4eaa243..c43d45fe5 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; }; 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; + 68EFE4EE1CF6EB3900A1DE13 /* RCTBundleURLProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; 83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */; }; @@ -272,6 +273,8 @@ 58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAsyncLocalStorage.h; sourceTree = ""; }; 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = ""; }; 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; + 68EFE4EC1CF6EB3000A1DE13 /* RCTBundleURLProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBundleURLProvider.h; sourceTree = ""; }; + 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBundleURLProvider.m; sourceTree = ""; }; 6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootViewInternal.h; sourceTree = ""; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; @@ -528,6 +531,8 @@ 83CBBA491A601E3B00E9B192 /* Base */ = { isa = PBXGroup; children = ( + 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */, + 68EFE4EC1CF6EB3000A1DE13 /* RCTBundleURLProvider.h */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */, 14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */, @@ -746,6 +751,7 @@ 131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */, 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */, 191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */, + 68EFE4EE1CF6EB3900A1DE13 /* RCTBundleURLProvider.m in Sources */, 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */,