1#import "EXApiUtil.h" 2#import "EXBuildConstants.h" 3#import "EXEnvironment.h" 4#import "EXErrorRecoveryManager.h" 5#import "EXUserNotificationManager.h" 6#import "EXKernel.h" 7#import "EXAppLoader.h" 8#import "EXKernelLinkingManager.h" 9#import "EXKernelServiceRegistry.h" 10#import "EXKernelUtil.h" 11#import "EXLog.h" 12#import "ExpoKit.h" 13#import "EXReactAppManager.h" 14#import "EXReactAppManager+Private.h" 15#import "EXVersionManager.h" 16#import "EXVersions.h" 17#import "EXAppViewController.h" 18#import <ExpoModulesCore/EXModuleRegistryProvider.h> 19#import <EXConstants/EXConstantsService.h> 20#import <EXSplashScreen/EXSplashScreenService.h> 21 22#import <React/RCTBridge.h> 23#import <React/RCTCxxBridgeDelegate.h> 24#import <React/JSCExecutorFactory.h> 25#import <React/RCTRootView.h> 26 27@implementation RCTSource (EXReactAppManager) 28 29- (instancetype)initWithURL:(nonnull NSURL *)url data:(nonnull NSData *)data 30{ 31 if (self = [super init]) { 32 // Use KVO since RN publicly declares these properties as readonly and privately defines the 33 // ivars 34 [self setValue:url forKey:@"url"]; 35 [self setValue:data forKey:@"data"]; 36 [self setValue:@(data.length) forKey:@"length"]; 37 [self setValue:@(RCTSourceFilesChangedCountNotBuiltByBundler) forKey:@"filesChangedCount"]; 38 } 39 return self; 40} 41 42@end 43 44@interface EXReactAppManager () <RCTBridgeDelegate, RCTCxxBridgeDelegate> 45 46@property (nonatomic, strong) UIView * __nullable reactRootView; 47@property (nonatomic, copy) RCTSourceLoadBlock loadCallback; 48@property (nonatomic, strong) NSDictionary *initialProps; 49@property (nonatomic, strong) NSTimer *viewTestTimer; 50 51@end 52 53@implementation EXReactAppManager 54 55- (instancetype)initWithAppRecord:(EXKernelAppRecord *)record initialProps:(NSDictionary *)initialProps 56{ 57 if (self = [super init]) { 58 _appRecord = record; 59 _initialProps = initialProps; 60 _isHeadless = NO; 61 _exceptionHandler = [[EXReactAppExceptionHandler alloc] initWithAppRecord:_appRecord]; 62 } 63 return self; 64} 65 66- (void)setAppRecord:(EXKernelAppRecord *)appRecord 67{ 68 _appRecord = appRecord; 69 _exceptionHandler = [[EXReactAppExceptionHandler alloc] initWithAppRecord:appRecord]; 70} 71 72- (EXReactAppManagerStatus)status 73{ 74 if (!_appRecord) { 75 return kEXReactAppManagerStatusError; 76 } 77 if (_loadCallback) { 78 // we have a RCTBridge load callback so we're ready to receive load events 79 return kEXReactAppManagerStatusBridgeLoading; 80 } 81 if (_isBridgeRunning) { 82 return kEXReactAppManagerStatusRunning; 83 } 84 return kEXReactAppManagerStatusNew; 85} 86 87- (UIView *)rootView 88{ 89 return _reactRootView; 90} 91 92- (void)rebuildBridge 93{ 94 EXAssertMainThread(); 95 NSAssert((_delegate != nil), @"Cannot init react app without EXReactAppManagerDelegate"); 96 97 [self _invalidateAndClearDelegate:NO]; 98 [self computeVersionSymbolPrefix]; 99 100 // Assert early so we can catch the error before instantiating the bridge, otherwise we would be passing a 101 // nullish scope key to the scoped modules. 102 // Alternatively we could skip instantiating the scoped modules but then singletons like the one used in 103 // expo-updates would be loaded as bare modules. In the case of expo-updates, this would throw a fatal error 104 // because Expo.plist is not available in the Expo Go app. 105 NSAssert(_appRecord.scopeKey, @"Experience scope key should be nonnull when getting initial properties for root view. This can occur when the manifest JSON, loaded from the server, is missing keys."); 106 107 108 if ([self isReadyToLoad]) { 109 Class versionManagerClass = [self versionedClassFromString:@"EXVersionManager"]; 110 Class bridgeClass = [self versionedClassFromString:@"RCTBridge"]; 111 Class rootViewClass = [self versionedClassFromString:@"RCTRootView"]; 112 113 _versionManager = [[versionManagerClass alloc] initWithParams:[self extraParams] 114 manifest:_appRecord.appLoader.manifest 115 fatalHandler:handleFatalReactError 116 logFunction:[self logFunction] 117 logThreshold:[self logLevel]]; 118 _reactBridge = [[bridgeClass alloc] initWithDelegate:self launchOptions:[self launchOptionsForBridge]]; 119 120 if (!_isHeadless) { 121 // We don't want to run the whole JS app if app launches in the background, 122 // so we're omitting creation of RCTRootView that triggers runApplication and sets up React view hierarchy. 123 _reactRootView = [[rootViewClass alloc] initWithBridge:_reactBridge 124 moduleName:[self applicationKeyForRootView] 125 initialProperties:[self initialPropertiesForRootView]]; 126 } 127 128 [self setupWebSocketControls]; 129 [_delegate reactAppManagerIsReadyForLoad:self]; 130 131 NSAssert([_reactBridge isLoading], @"React bridge should be loading once initialized"); 132 [_versionManager bridgeWillStartLoading:_reactBridge]; 133 } 134} 135 136- (NSDictionary *)extraParams 137{ 138 // we allow the vanilla RN dev menu in some circumstances. 139 BOOL isStandardDevMenuAllowed = [EXEnvironment sharedEnvironment].isDetached; 140 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:@{ 141 @"manifest": _appRecord.appLoader.manifest.rawManifestJSON, 142 @"constants": @{ 143 @"linkingUri": RCTNullIfNil([EXKernelLinkingManager linkingUriForExperienceUri:_appRecord.appLoader.manifestUrl useLegacy:[self _compareVersionTo:27] == NSOrderedAscending]), 144 @"experienceUrl": RCTNullIfNil(_appRecord.appLoader.manifestUrl? _appRecord.appLoader.manifestUrl.absoluteString: nil), 145 @"expoRuntimeVersion": [EXBuildConstants sharedInstance].expoRuntimeVersion, 146 @"manifest": _appRecord.appLoader.manifest.rawManifestJSON, 147 @"executionEnvironment": [self _executionEnvironment], 148 @"appOwnership": [self _appOwnership], 149 @"isHeadless": @(_isHeadless), 150 @"supportedExpoSdks": [EXVersions sharedInstance].versions[@"sdkVersions"], 151 }, 152 @"exceptionsManagerDelegate": _exceptionHandler, 153 @"initialUri": RCTNullIfNil([EXKernelLinkingManager initialUriWithManifestUrl:_appRecord.appLoader.manifestUrl]), 154 @"isDeveloper": @([self enablesDeveloperTools]), 155 @"isStandardDevMenuAllowed": @(isStandardDevMenuAllowed), 156 @"testEnvironment": @([EXEnvironment sharedEnvironment].testEnvironment), 157 @"services": [EXKernel sharedInstance].serviceRegistry.allServices, 158 @"singletonModules": [EXModuleRegistryProvider singletonModules], 159 @"moduleRegistryDelegateClass": RCTNullIfNil([self moduleRegistryDelegateClass]), 160 }]; 161 if ([@"expo" isEqualToString:[self _appOwnership]]) { 162 [params addEntriesFromDictionary:@{ 163 @"fileSystemDirectories": @{ 164 @"documentDirectory": [self scopedDocumentDirectory], 165 @"cachesDirectory": [self scopedCachesDirectory] 166 } 167 }]; 168 } 169 return params; 170} 171 172- (void)invalidate 173{ 174 [self _invalidateAndClearDelegate:YES]; 175} 176 177- (void)_invalidateAndClearDelegate:(BOOL)clearDelegate 178{ 179 [self _stopObservingBridgeNotifications]; 180 if (_viewTestTimer) { 181 [_viewTestTimer invalidate]; 182 _viewTestTimer = nil; 183 } 184 if (_versionManager) { 185 [_versionManager invalidate]; 186 _versionManager = nil; 187 } 188 if (_reactRootView) { 189 [_reactRootView removeFromSuperview]; 190 _reactRootView = nil; 191 } 192 if (_reactBridge) { 193 [_reactBridge invalidate]; 194 _reactBridge = nil; 195 if (_delegate) { 196 [_delegate reactAppManagerDidInvalidate:self]; 197 if (clearDelegate) { 198 _delegate = nil; 199 } 200 } 201 } 202 _isBridgeRunning = NO; 203 [self _invalidateVersionState]; 204} 205 206- (void)computeVersionSymbolPrefix 207{ 208 // TODO: ben: kernel checks detached versions here 209 _validatedVersion = [[EXVersions sharedInstance] availableSdkVersionForManifest:_appRecord.appLoader.manifest]; 210 _versionSymbolPrefix = [[EXVersions sharedInstance] symbolPrefixForSdkVersion:self.validatedVersion isKernel:NO]; 211} 212 213- (void)_invalidateVersionState 214{ 215 _versionSymbolPrefix = @""; 216 _validatedVersion = nil; 217} 218 219- (Class)versionedClassFromString: (NSString *)classString 220{ 221 return NSClassFromString([self versionedString:classString]); 222} 223 224- (NSString *)versionedString: (NSString *)string 225{ 226 return [EXVersions versionedString:string withPrefix:_versionSymbolPrefix]; 227} 228 229- (NSString *)escapedResourceName:(NSString *)string 230{ 231 NSString *charactersToEscape = @"!*'();:@&=+$,/?%#[]"; 232 NSCharacterSet *allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:charactersToEscape] invertedSet]; 233 return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; 234} 235 236- (BOOL)isReadyToLoad 237{ 238 if (_appRecord) { 239 return (_appRecord.appLoader.status == kEXAppLoaderStatusHasManifest || _appRecord.appLoader.status == kEXAppLoaderStatusHasManifestAndBundle); 240 } 241 return NO; 242} 243 244- (NSURL *)bundleUrl 245{ 246 return [EXApiUtil bundleUrlFromManifest:_appRecord.appLoader.manifest]; 247} 248 249#pragma mark - EXAppFetcherDataSource 250 251- (NSString *)bundleResourceNameForAppFetcher:(EXAppFetcher *)appFetcher withManifest:(nonnull EXManifestsManifest *)manifest 252{ 253 if ([EXEnvironment sharedEnvironment].isDetached) { 254 NSLog(@"Standalone bundle remote url is %@", [EXEnvironment sharedEnvironment].standaloneManifestUrl); 255 return kEXEmbeddedBundleResourceName; 256 } else { 257 return manifest.legacyId; 258 } 259} 260 261- (BOOL)appFetcherShouldInvalidateBundleCache:(EXAppFetcher *)appFetcher 262{ 263 return NO; 264} 265 266#pragma mark - RCTBridgeDelegate 267 268- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 269{ 270 return [self bundleUrl]; 271} 272 273- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback 274{ 275 // clear any potentially old loading state 276 if (_appRecord.scopeKey) { 277 [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager setError:nil forScopeKey:_appRecord.scopeKey]; 278 } 279 [self _stopObservingBridgeNotifications]; 280 [self _startObservingBridgeNotificationsForBridge:bridge]; 281 282 if ([self enablesDeveloperTools]) { 283 if ([_appRecord.appLoader supportsBundleReload]) { 284 [_appRecord.appLoader forceBundleReload]; 285 } else { 286 NSAssert(_appRecord.scopeKey, @"EXKernelAppRecord.scopeKey should be nonnull if we have a manifest with developer tools enabled"); 287 [[EXKernel sharedInstance] reloadAppWithScopeKey:_appRecord.scopeKey]; 288 } 289 } 290 291 _loadCallback = loadCallback; 292 if (_appRecord.appLoader.status == kEXAppLoaderStatusHasManifestAndBundle) { 293 // finish loading immediately (app loader won't call this since it's already done) 294 [self appLoaderFinished]; 295 } else { 296 // wait for something else to call `appLoaderFinished` or `appLoaderFailed` later. 297 } 298} 299 300- (NSArray *)extraModulesForBridge:(RCTBridge *)bridge 301{ 302 return [self.versionManager extraModulesForBridge:bridge]; 303} 304 305- (void)appLoaderFinished 306{ 307 NSData *data = _appRecord.appLoader.bundle; 308 if (_loadCallback) { 309 _loadCallback(nil, [[RCTSource alloc] initWithURL:[self bundleUrl] data:data]); 310 _loadCallback = nil; 311 } 312} 313 314- (void)appLoaderFailedWithError:(NSError *)error 315{ 316 // RN is going to call RCTFatal() on this error, so keep a reference to it for later 317 // so we can distinguish this non-fatal error from actual fatal cases. 318 if (_appRecord.scopeKey) { 319 [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager setError:error forScopeKey:_appRecord.scopeKey]; 320 } 321 322 // react won't post this for us 323 [[NSNotificationCenter defaultCenter] postNotificationName:[self versionedString:RCTJavaScriptDidFailToLoadNotification] object:error]; 324 325 if (_loadCallback) { 326 _loadCallback(error, nil); 327 _loadCallback = nil; 328 } 329} 330 331#pragma mark - JavaScript loading 332 333- (void)_startObservingBridgeNotificationsForBridge:(RCTBridge *)bridge 334{ 335 NSAssert(bridge, @"Must subscribe to loading notifs for a non-null bridge"); 336 337 [[NSNotificationCenter defaultCenter] addObserver:self 338 selector:@selector(_handleJavaScriptStartLoadingEvent:) 339 name:[self versionedString:RCTJavaScriptWillStartLoadingNotification] 340 object:bridge]; 341 [[NSNotificationCenter defaultCenter] addObserver:self 342 selector:@selector(_handleJavaScriptLoadEvent:) 343 name:[self versionedString:RCTJavaScriptDidLoadNotification] 344 object:bridge]; 345 [[NSNotificationCenter defaultCenter] addObserver:self 346 selector:@selector(_handleJavaScriptLoadEvent:) 347 name:[self versionedString:RCTJavaScriptDidFailToLoadNotification] 348 object:bridge]; 349 [[NSNotificationCenter defaultCenter] addObserver:self 350 selector:@selector(_handleReactContentEvent:) 351 name:[self versionedString:RCTContentDidAppearNotification] 352 object:nil]; 353 [[NSNotificationCenter defaultCenter] addObserver:self 354 selector:@selector(_handleBridgeEvent:) 355 name:[self versionedString:RCTBridgeWillReloadNotification] 356 object:bridge]; 357} 358 359- (void)_stopObservingBridgeNotifications 360{ 361 [[NSNotificationCenter defaultCenter] removeObserver:self name:[self versionedString:RCTJavaScriptWillStartLoadingNotification] object:_reactBridge]; 362 [[NSNotificationCenter defaultCenter] removeObserver:self name:[self versionedString:RCTJavaScriptDidLoadNotification] object:_reactBridge]; 363 [[NSNotificationCenter defaultCenter] removeObserver:self name:[self versionedString:RCTJavaScriptDidFailToLoadNotification] object:_reactBridge]; 364 [[NSNotificationCenter defaultCenter] removeObserver:self name:[self versionedString:RCTContentDidAppearNotification] object:_reactBridge]; 365 [[NSNotificationCenter defaultCenter] removeObserver:self name:[self versionedString:RCTBridgeWillReloadNotification] object:_reactBridge]; 366} 367 368- (void)_handleJavaScriptStartLoadingEvent:(NSNotification *)notification 369{ 370 __weak __typeof(self) weakSelf = self; 371 dispatch_async(dispatch_get_main_queue(), ^{ 372 __strong __typeof(self) strongSelf = weakSelf; 373 if (strongSelf) { 374 [strongSelf.delegate reactAppManagerStartedLoadingJavaScript:strongSelf]; 375 } 376 }); 377} 378 379- (void)_handleJavaScriptLoadEvent:(NSNotification *)notification 380{ 381 if ([notification.name isEqualToString:[self versionedString:RCTJavaScriptDidLoadNotification]]) { 382 _isBridgeRunning = YES; 383 _hasBridgeEverLoaded = YES; 384 [_versionManager bridgeFinishedLoading:_reactBridge]; 385 386 // TODO: temporary solution for hiding LoadingProgressWindow 387 if (_appRecord.viewController) { 388 [_appRecord.viewController hideLoadingProgressWindow]; 389 } 390 } else if ([notification.name isEqualToString:[self versionedString:RCTJavaScriptDidFailToLoadNotification]]) { 391 NSError *error = (notification.userInfo) ? notification.userInfo[@"error"] : nil; 392 if (_appRecord.scopeKey) { 393 [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager setError:error forScopeKey:_appRecord.scopeKey]; 394 } 395 396 EX_WEAKIFY(self); 397 dispatch_async(dispatch_get_main_queue(), ^{ 398 EX_ENSURE_STRONGIFY(self); 399 [self.delegate reactAppManager:self failedToLoadJavaScriptWithError:error]; 400 }); 401 } 402} 403 404# pragma mark app loading & splash screen 405 406- (void)_handleReactContentEvent:(NSNotification *)notification 407{ 408 if ([notification.name isEqualToString:[self versionedString:RCTContentDidAppearNotification]] 409 && notification.object == self.reactRootView) { 410 EX_WEAKIFY(self); 411 dispatch_async(dispatch_get_main_queue(), ^{ 412 EX_ENSURE_STRONGIFY(self); 413 [self.delegate reactAppManagerAppContentDidAppear:self]; 414 [self _appLoadingFinished]; 415 }); 416 } 417} 418 419- (void)_handleBridgeEvent:(NSNotification *)notification 420{ 421 if ([notification.name isEqualToString:[self versionedString:RCTBridgeWillReloadNotification]]) { 422 EX_WEAKIFY(self); 423 dispatch_async(dispatch_get_main_queue(), ^{ 424 EX_ENSURE_STRONGIFY(self); 425 [self.delegate reactAppManagerAppContentWillReload:self]; 426 }); 427 } 428} 429 430- (void)_appLoadingFinished 431{ 432 EX_WEAKIFY(self); 433 dispatch_async(dispatch_get_main_queue(), ^{ 434 EX_ENSURE_STRONGIFY(self); 435 if (self.appRecord.scopeKey) { 436 [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager experienceFinishedLoadingWithScopeKey:self.appRecord.scopeKey]; 437 } 438 [self.delegate reactAppManagerFinishedLoadingJavaScript:self]; 439 }); 440} 441 442#pragma mark - dev tools 443 444- (RCTLogFunction)logFunction 445{ 446 return (([self enablesDeveloperTools]) ? EXDeveloperRCTLogFunction : EXDefaultRCTLogFunction); 447} 448 449- (RCTLogLevel)logLevel 450{ 451 return ([self enablesDeveloperTools]) ? RCTLogLevelInfo : RCTLogLevelWarning; 452} 453 454- (BOOL)enablesDeveloperTools 455{ 456 EXManifestsManifest *manifest = _appRecord.appLoader.manifest; 457 if (manifest) { 458 return manifest.isUsingDeveloperTool; 459 } 460 return false; 461} 462 463- (BOOL)requiresValidManifests 464{ 465 return YES; 466} 467 468- (void)showDevMenu 469{ 470 if ([self enablesDeveloperTools]) { 471 dispatch_async(dispatch_get_main_queue(), ^{ 472 [self.versionManager showDevMenuForBridge:self.reactBridge]; 473 }); 474 } 475} 476 477- (void)reloadBridge 478{ 479 if ([self enablesDeveloperTools]) { 480 [self.reactBridge reload]; 481 } 482} 483 484- (void)disableRemoteDebugging 485{ 486 if ([self enablesDeveloperTools]) { 487 [self.versionManager disableRemoteDebuggingForBridge:self.reactBridge]; 488 } 489} 490 491- (void)toggleRemoteDebugging 492{ 493 if ([self enablesDeveloperTools]) { 494 [self.versionManager toggleRemoteDebuggingForBridge:self.reactBridge]; 495 } 496} 497 498- (void)togglePerformanceMonitor 499{ 500 if ([self enablesDeveloperTools]) { 501 [self.versionManager togglePerformanceMonitorForBridge:self.reactBridge]; 502 } 503} 504 505- (void)toggleElementInspector 506{ 507 if ([self enablesDeveloperTools]) { 508 [self.versionManager toggleElementInspectorForBridge:self.reactBridge]; 509 } 510} 511 512- (void)toggleDevMenu 513{ 514 if ([EXEnvironment sharedEnvironment].isDetached) { 515 [[EXKernel sharedInstance].visibleApp.appManager showDevMenu]; 516 } else { 517 [[EXKernel sharedInstance] switchTasks]; 518 } 519} 520 521- (void)setupWebSocketControls 522{ 523 if ([self enablesDeveloperTools]) { 524 if ([_versionManager respondsToSelector:@selector(addWebSocketNotificationHandler:queue:forMethod:)]) { 525 __weak __typeof(self) weakSelf = self; 526 527 // Attach listeners to the bundler's dev server web socket connection. 528 // This enables tools to automatically reload the client remotely (i.e. in expo-cli). 529 530 // Enable a lot of tools under the same command namespace 531 [_versionManager addWebSocketNotificationHandler:^(id params) { 532 if (params != [NSNull null] && (NSDictionary *)params) { 533 NSDictionary *_params = (NSDictionary *)params; 534 if (_params[@"name"] != nil && (NSString *)_params[@"name"]) { 535 NSString *name = _params[@"name"]; 536 if ([name isEqualToString:@"reload"]) { 537 [[EXKernel sharedInstance] reloadVisibleApp]; 538 } else if ([name isEqualToString:@"toggleDevMenu"]) { 539 [weakSelf toggleDevMenu]; 540 } else if ([name isEqualToString:@"toggleRemoteDebugging"]) { 541 [weakSelf toggleRemoteDebugging]; 542 } else if ([name isEqualToString:@"toggleElementInspector"]) { 543 [weakSelf toggleElementInspector]; 544 } else if ([name isEqualToString:@"togglePerformanceMonitor"]) { 545 [weakSelf togglePerformanceMonitor]; 546 } 547 } 548 } 549 } 550 queue:dispatch_get_main_queue() 551 forMethod:@"sendDevCommand"]; 552 553 // These (reload and devMenu) are here to match RN dev tooling. 554 555 // Reload the app on "reload" 556 [_versionManager addWebSocketNotificationHandler:^(id params) { 557 [[EXKernel sharedInstance] reloadVisibleApp]; 558 } 559 queue:dispatch_get_main_queue() 560 forMethod:@"reload"]; 561 562 // Open the dev menu on "devMenu" 563 [_versionManager addWebSocketNotificationHandler:^(id params) { 564 [weakSelf toggleDevMenu]; 565 } 566 queue:dispatch_get_main_queue() 567 forMethod:@"devMenu"]; 568 } 569 } 570} 571 572- (NSDictionary<NSString *, NSString *> *)devMenuItems 573{ 574 return [self.versionManager devMenuItemsForBridge:self.reactBridge]; 575} 576 577- (void)selectDevMenuItemWithKey:(NSString *)key 578{ 579 dispatch_async(dispatch_get_main_queue(), ^{ 580 [self.versionManager selectDevMenuItemWithKey:key onBridge:self.reactBridge]; 581 }); 582} 583 584#pragma mark - RN configuration 585 586- (NSComparisonResult)_compareVersionTo:(NSUInteger)version 587{ 588 // Unversioned projects are always considered to be on the latest version 589 if (!_validatedVersion || _validatedVersion.length == 0 || [_validatedVersion isEqualToString:@"UNVERSIONED"]) { 590 return NSOrderedDescending; 591 } 592 593 NSUInteger projectVersionNumber = _validatedVersion.integerValue; 594 if (projectVersionNumber == version) { 595 return NSOrderedSame; 596 } 597 return (projectVersionNumber < version) ? NSOrderedAscending : NSOrderedDescending; 598} 599 600- (NSDictionary *)launchOptionsForBridge 601{ 602 if ([EXEnvironment sharedEnvironment].isDetached) { 603 // pass the native app's launch options to standalone bridge. 604 return [ExpoKit sharedInstance].launchOptions; 605 } 606 return @{}; 607} 608 609- (Class)moduleRegistryDelegateClass 610{ 611 if ([EXEnvironment sharedEnvironment].isDetached) { 612 return [ExpoKit sharedInstance].moduleRegistryDelegateClass; 613 } 614 return nil; 615} 616 617- (NSString *)applicationKeyForRootView 618{ 619 EXManifestsManifest *manifest = _appRecord.appLoader.manifest; 620 if (manifest && manifest.appKey) { 621 return manifest.appKey; 622 } 623 624 NSURL *bundleUrl = [self bundleUrl]; 625 if (bundleUrl) { 626 NSURLComponents *components = [NSURLComponents componentsWithURL:bundleUrl resolvingAgainstBaseURL:YES]; 627 NSArray<NSURLQueryItem *> *queryItems = components.queryItems; 628 for (NSURLQueryItem *item in queryItems) { 629 if ([item.name isEqualToString:@"app"]) { 630 return item.value; 631 } 632 } 633 } 634 635 return @"main"; 636} 637 638- (NSDictionary * _Nullable)initialPropertiesForRootView 639{ 640 NSMutableDictionary *props = [NSMutableDictionary dictionary]; 641 NSMutableDictionary *expProps = [NSMutableDictionary dictionary]; 642 643 NSAssert(_appRecord.scopeKey, @"Experience scope key should be nonnull when getting initial properties for root view"); 644 645 NSDictionary *errorRecoveryProps = [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager developerInfoForScopeKey:_appRecord.scopeKey]; 646 if ([[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager scopeKeyIsRecoveringFromError:_appRecord.scopeKey]) { 647 [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager increaseAutoReloadBuffer]; 648 if (errorRecoveryProps) { 649 expProps[@"errorRecovery"] = errorRecoveryProps; 650 } 651 } 652 653 expProps[@"shell"] = @(_appRecord == [EXKernel sharedInstance].appRegistry.standaloneAppRecord); 654 expProps[@"appOwnership"] = [self _appOwnership]; 655 if (_initialProps) { 656 [expProps addEntriesFromDictionary:_initialProps]; 657 } 658 EXPendingNotification *initialNotification = [[EXKernel sharedInstance].serviceRegistry.notificationsManager initialNotification]; 659 if (initialNotification) { 660 expProps[@"notification"] = initialNotification.properties; 661 } 662 663 NSString *manifestString = nil; 664 EXManifestsManifest *manifest = _appRecord.appLoader.manifest; 665 if (manifest && [NSJSONSerialization isValidJSONObject:manifest.rawManifestJSON]) { 666 NSError *error; 667 NSData *jsonData = [NSJSONSerialization dataWithJSONObject:manifest.rawManifestJSON options:0 error:&error]; 668 if (jsonData) { 669 manifestString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 670 } else { 671 DDLogWarn(@"Failed to serialize JSON manifest: %@", error); 672 } 673 } 674 675 expProps[@"manifestString"] = manifestString; 676 if (_appRecord.appLoader.manifestUrl) { 677 expProps[@"initialUri"] = [_appRecord.appLoader.manifestUrl absoluteString]; 678 } 679 props[@"exp"] = expProps; 680 return props; 681} 682 683- (NSString *)_appOwnership 684{ 685 if (_appRecord == [EXKernel sharedInstance].appRegistry.standaloneAppRecord) { 686 return @"standalone"; 687 } 688 return @"expo"; 689} 690 691- (NSString *)_executionEnvironment 692{ 693 if ([EXEnvironment sharedEnvironment].isDetached) { 694 return EXConstantsExecutionEnvironmentStandalone; 695 } else { 696 return EXConstantsExecutionEnvironmentStoreClient; 697 } 698} 699 700- (NSString *)scopedDocumentDirectory 701{ 702 NSString *escapedScopeKey = [self escapedResourceName:_appRecord.scopeKey]; 703 NSString *mainDocumentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; 704 NSString *exponentDocumentDirectory = [mainDocumentDirectory stringByAppendingPathComponent:@"ExponentExperienceData"]; 705 return [[exponentDocumentDirectory stringByAppendingPathComponent:escapedScopeKey] stringByStandardizingPath]; 706} 707 708- (NSString *)scopedCachesDirectory 709{ 710 NSString *escapedScopeKey = [self escapedResourceName:_appRecord.scopeKey]; 711 NSString *mainCachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; 712 NSString *exponentCachesDirectory = [mainCachesDirectory stringByAppendingPathComponent:@"ExponentExperienceData"]; 713 return [[exponentCachesDirectory stringByAppendingPathComponent:escapedScopeKey] stringByStandardizingPath]; 714} 715 716- (void *)jsExecutorFactoryForBridge:(id)bridge 717{ 718 return [_versionManager versionedJsExecutorFactoryForBridge:bridge]; 719} 720 721@end 722