RCTBundleURLProvider.m 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTBundleURLProvider.h"
  8. #import "RCTConvert.h"
  9. #import "RCTDefines.h"
  10. NSString *const RCTBundleURLProviderUpdatedNotification = @"RCTBundleURLProviderUpdatedNotification";
  11. const NSUInteger kRCTBundleURLProviderDefaultPort = RCT_METRO_PORT;
  12. static NSString *const kRCTJsLocationKey = @"RCT_jsLocation";
  13. // This option is no longer exposed in the dev menu UI.
  14. // It was renamed in D15958697 so it doesn't get stuck with no way to turn it off:
  15. static NSString *const kRCTEnableLiveReloadKey = @"RCT_enableLiveReload_LEGACY";
  16. static NSString *const kRCTEnableDevKey = @"RCT_enableDev";
  17. static NSString *const kRCTEnableMinificationKey = @"RCT_enableMinification";
  18. @implementation RCTBundleURLProvider
  19. - (instancetype)init
  20. {
  21. self = [super init];
  22. if (self) {
  23. [self setDefaults];
  24. }
  25. return self;
  26. }
  27. - (NSDictionary *)defaults
  28. {
  29. return @{
  30. kRCTEnableLiveReloadKey : @NO,
  31. kRCTEnableDevKey : @YES,
  32. kRCTEnableMinificationKey : @NO,
  33. };
  34. }
  35. - (void)settingsUpdated
  36. {
  37. [[NSNotificationCenter defaultCenter] postNotificationName:RCTBundleURLProviderUpdatedNotification object:self];
  38. }
  39. - (void)setDefaults
  40. {
  41. [[NSUserDefaults standardUserDefaults] registerDefaults:[self defaults]];
  42. }
  43. - (void)resetToDefaults
  44. {
  45. for (NSString *key in [[self defaults] allKeys]) {
  46. [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
  47. }
  48. [self setDefaults];
  49. [self settingsUpdated];
  50. }
  51. static NSURL *serverRootWithHostPort(NSString *hostPort)
  52. {
  53. if ([hostPort rangeOfString:@":"].location != NSNotFound) {
  54. return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", hostPort]];
  55. }
  56. return [NSURL
  57. URLWithString:[NSString
  58. stringWithFormat:@"http://%@:%lu/", hostPort, (unsigned long)kRCTBundleURLProviderDefaultPort]];
  59. }
  60. #if RCT_DEV
  61. - (BOOL)isPackagerRunning:(NSString *)host
  62. {
  63. NSURL *url = [serverRootWithHostPort(host) URLByAppendingPathComponent:@"status"];
  64. NSURLSession *session = [NSURLSession sharedSession];
  65. NSURLRequest *request = [NSURLRequest requestWithURL:url];
  66. __block NSURLResponse *response;
  67. __block NSData *data;
  68. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  69. [[session dataTaskWithRequest:request
  70. completionHandler:^(NSData *d, NSURLResponse *res, __unused NSError *err) {
  71. data = d;
  72. response = res;
  73. dispatch_semaphore_signal(semaphore);
  74. }] resume];
  75. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  76. NSString *status = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  77. return [status isEqualToString:@"packager-status:running"];
  78. }
  79. - (NSString *)guessPackagerHost
  80. {
  81. static NSString *ipGuess;
  82. static dispatch_once_t onceToken;
  83. dispatch_once(&onceToken, ^{
  84. NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"];
  85. ipGuess =
  86. [[NSString stringWithContentsOfFile:ipPath encoding:NSUTF8StringEncoding
  87. error:nil] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
  88. });
  89. NSString *host = ipGuess ?: @"localhost";
  90. if ([self isPackagerRunning:host]) {
  91. return host;
  92. }
  93. return nil;
  94. }
  95. #endif
  96. - (NSString *)packagerServerHost
  97. {
  98. NSString *location = [self jsLocation];
  99. if (location != nil) {
  100. return location;
  101. }
  102. #if RCT_DEV
  103. NSString *host = [self guessPackagerHost];
  104. if (host) {
  105. return host;
  106. }
  107. #endif
  108. return nil;
  109. }
  110. - (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackURLProvider:(NSURL * (^)(void))fallbackURLProvider
  111. {
  112. NSString *packagerServerHost = [self packagerServerHost];
  113. if (!packagerServerHost) {
  114. return fallbackURLProvider();
  115. } else {
  116. return [RCTBundleURLProvider jsBundleURLForBundleRoot:bundleRoot
  117. packagerHost:packagerServerHost
  118. enableDev:[self enableDev]
  119. enableMinification:[self enableMinification]];
  120. }
  121. }
  122. - (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
  123. fallbackResource:(NSString *)resourceName
  124. fallbackExtension:(NSString *)extension
  125. {
  126. return [self jsBundleURLForBundleRoot:bundleRoot
  127. fallbackURLProvider:^NSURL * {
  128. return [self jsBundleURLForFallbackResource:resourceName fallbackExtension:extension];
  129. }];
  130. }
  131. - (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackResource:(NSString *)resourceName
  132. {
  133. return [self jsBundleURLForBundleRoot:bundleRoot fallbackResource:resourceName fallbackExtension:nil];
  134. }
  135. - (NSURL *)jsBundleURLForFallbackResource:(NSString *)resourceName fallbackExtension:(NSString *)extension
  136. {
  137. resourceName = resourceName ?: @"main";
  138. extension = extension ?: @"jsbundle";
  139. return [[NSBundle mainBundle] URLForResource:resourceName withExtension:extension];
  140. }
  141. - (NSURL *)resourceURLForResourceRoot:(NSString *)root
  142. resourceName:(NSString *)name
  143. resourceExtension:(NSString *)extension
  144. offlineBundle:(NSBundle *)offlineBundle
  145. {
  146. NSString *packagerServerHost = [self packagerServerHost];
  147. if (!packagerServerHost) {
  148. // Serve offline bundle (local file)
  149. NSBundle *bundle = offlineBundle ?: [NSBundle mainBundle];
  150. return [bundle URLForResource:name withExtension:extension];
  151. }
  152. NSString *path = [NSString stringWithFormat:@"/%@/%@.%@", root, name, extension];
  153. return [[self class] resourceURLForResourcePath:path packagerHost:packagerServerHost query:nil];
  154. }
  155. + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
  156. packagerHost:(NSString *)packagerHost
  157. enableDev:(BOOL)enableDev
  158. enableMinification:(BOOL)enableMinification
  159. {
  160. NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
  161. // When we support only iOS 8 and above, use queryItems for a better API.
  162. NSString *query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@",
  163. enableDev ? @"true" : @"false",
  164. enableMinification ? @"true" : @"false"];
  165. return [[self class] resourceURLForResourcePath:path packagerHost:packagerHost query:query];
  166. }
  167. + (NSURL *)resourceURLForResourcePath:(NSString *)path packagerHost:(NSString *)packagerHost query:(NSString *)query
  168. {
  169. NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHostPort(packagerHost)
  170. resolvingAgainstBaseURL:NO];
  171. components.path = path;
  172. if (query != nil) {
  173. components.query = query;
  174. }
  175. return components.URL;
  176. }
  177. - (void)updateValue:(id)object forKey:(NSString *)key
  178. {
  179. [[NSUserDefaults standardUserDefaults] setObject:object forKey:key];
  180. [[NSUserDefaults standardUserDefaults] synchronize];
  181. [self settingsUpdated];
  182. }
  183. - (BOOL)enableDev
  184. {
  185. return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableDevKey];
  186. }
  187. - (BOOL)enableLiveReload
  188. {
  189. return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableLiveReloadKey];
  190. }
  191. - (BOOL)enableMinification
  192. {
  193. return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableMinificationKey];
  194. }
  195. - (NSString *)jsLocation
  196. {
  197. return [[NSUserDefaults standardUserDefaults] stringForKey:kRCTJsLocationKey];
  198. }
  199. - (void)setEnableDev:(BOOL)enableDev
  200. {
  201. [self updateValue:@(enableDev) forKey:kRCTEnableDevKey];
  202. }
  203. - (void)setEnableLiveReload:(BOOL)enableLiveReload
  204. {
  205. [self updateValue:@(enableLiveReload) forKey:kRCTEnableLiveReloadKey];
  206. }
  207. - (void)setJsLocation:(NSString *)jsLocation
  208. {
  209. [self updateValue:jsLocation forKey:kRCTJsLocationKey];
  210. }
  211. - (void)setEnableMinification:(BOOL)enableMinification
  212. {
  213. [self updateValue:@(enableMinification) forKey:kRCTEnableMinificationKey];
  214. }
  215. + (instancetype)sharedSettings
  216. {
  217. static RCTBundleURLProvider *sharedInstance;
  218. static dispatch_once_t once_token;
  219. dispatch_once(&once_token, ^{
  220. sharedInstance = [RCTBundleURLProvider new];
  221. });
  222. return sharedInstance;
  223. }
  224. @end