10fbf6a33SBen Roth// Copyright 2015-present 650 Industries. All rights reserved. 20fbf6a33SBen Roth 30fbf6a33SBen Roth#import "EXAnalytics.h" 40fbf6a33SBen Roth#import "EXAppState.h" 50fbf6a33SBen Roth#import "EXKernelDevMenuViewController.h" 60fbf6a33SBen Roth#import "EXFrame.h" 70fbf6a33SBen Roth#import "EXFrameReactAppManager.h" 80fbf6a33SBen Roth#import "EXKernel.h" 90fbf6a33SBen Roth#import "EXKernelBridgeRecord.h" 100fbf6a33SBen Roth#import "EXKernelDevMotionHandler.h" 110fbf6a33SBen Roth#import "EXKernelDevKeyCommands.h" 120fbf6a33SBen Roth#import "EXKernelModule.h" 130fbf6a33SBen Roth#import "EXLinkingManager.h" 140fbf6a33SBen Roth#import "EXManifestResource.h" 150fbf6a33SBen Roth#import "EXShellManager.h" 160fbf6a33SBen Roth#import "EXVersions.h" 170fbf6a33SBen Roth#import "EXViewController.h" 180fbf6a33SBen Roth 190fbf6a33SBen Roth#import <React/RCTBridge+Private.h> 200fbf6a33SBen Roth#import <React/RCTEventDispatcher.h> 210fbf6a33SBen Roth#import <React/RCTModuleData.h> 220fbf6a33SBen Roth#import <React/RCTUtils.h> 230fbf6a33SBen Roth 240fbf6a33SBen RothNS_ASSUME_NONNULL_BEGIN 250fbf6a33SBen Roth 260fbf6a33SBen RothNSString *kEXKernelErrorDomain = @"EXKernelErrorDomain"; 270fbf6a33SBen RothNSNotificationName kEXKernelOpenUrlNotification = @"EXKernelOpenUrlNotification"; 280fbf6a33SBen RothNSNotificationName kEXKernelRefreshForegroundTaskNotification = @"EXKernelRefreshForegroundTaskNotification"; 290fbf6a33SBen RothNSNotificationName kEXKernelGetPushTokenNotification = @"EXKernelGetPushTokenNotification"; 300fbf6a33SBen RothNSNotificationName kEXKernelJSIsLoadedNotification = @"EXKernelJSIsLoadedNotification"; 310fbf6a33SBen RothNSString *kEXKernelShouldForegroundTaskEvent = @"foregroundTask"; 320fbf6a33SBen RothNSString * const kEXDeviceInstallUUIDKey = @"EXDeviceInstallUUIDKey"; 330fbf6a33SBen RothNSString * const kEXKernelClearJSCacheUserDefaultsKey = @"EXKernelClearJSCacheUserDefaultsKey"; 340fbf6a33SBen RothNSString * const EXKernelDisableNuxDefaultsKey = @"EXKernelDisableNuxDefaultsKey"; 350fbf6a33SBen Roth 360fbf6a33SBen Roth@interface EXKernel () 370fbf6a33SBen Roth 380fbf6a33SBen Roth@property (nonatomic, weak) EXViewController *vcExponentRoot; 390fbf6a33SBen Roth 400fbf6a33SBen Roth@end 410fbf6a33SBen Roth 420fbf6a33SBen Roth@implementation EXKernel 430fbf6a33SBen Roth 440fbf6a33SBen Roth+ (instancetype)sharedInstance 450fbf6a33SBen Roth{ 460fbf6a33SBen Roth static EXKernel *theKernel; 470fbf6a33SBen Roth static dispatch_once_t once; 480fbf6a33SBen Roth dispatch_once(&once, ^{ 490fbf6a33SBen Roth if (!theKernel) { 500fbf6a33SBen Roth theKernel = [[EXKernel alloc] init]; 510fbf6a33SBen Roth } 520fbf6a33SBen Roth }); 530fbf6a33SBen Roth return theKernel; 540fbf6a33SBen Roth} 550fbf6a33SBen Roth 560fbf6a33SBen Roth+ (BOOL)isDevKernel 570fbf6a33SBen Roth{ 580fbf6a33SBen Roth // if we're in detached state (i.e. ExponentView) then never expect local kernel 590fbf6a33SBen Roth BOOL isDetachedKernel = ([[EXVersions sharedInstance].versions objectForKey:@"detachedNativeVersions"] != nil); 600fbf6a33SBen Roth if (isDetachedKernel) { 610fbf6a33SBen Roth return NO; 620fbf6a33SBen Roth } 630fbf6a33SBen Roth 640fbf6a33SBen Roth // otherwise, expect local kernel when we are attached to xcode 650fbf6a33SBen Roth#if DEBUG 660fbf6a33SBen Roth return YES; 670fbf6a33SBen Roth#endif 680fbf6a33SBen Roth return NO; 690fbf6a33SBen Roth} 700fbf6a33SBen Roth 710fbf6a33SBen Roth- (instancetype)init 720fbf6a33SBen Roth{ 730fbf6a33SBen Roth if (self = [super init]) { 740fbf6a33SBen Roth _bridgeRegistry = [[EXKernelBridgeRegistry alloc] init]; 751899209aSBen Roth _recoveryManager = [[EXErrorRecoveryManager alloc] init]; 760fbf6a33SBen Roth [EXKernelDevMotionHandler sharedInstance]; 770fbf6a33SBen Roth [EXKernelDevKeyCommands sharedInstance]; 780fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self 790fbf6a33SBen Roth selector:@selector(_routeUrl:) 800fbf6a33SBen Roth name:kEXKernelOpenUrlNotification 810fbf6a33SBen Roth object:nil]; 820fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self 830fbf6a33SBen Roth selector:@selector(_refreshForegroundTask:) 840fbf6a33SBen Roth name:kEXKernelRefreshForegroundTaskNotification 850fbf6a33SBen Roth object:nil]; 860fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_onKernelJSLoaded) name:kEXKernelJSIsLoadedNotification object:nil]; 870fbf6a33SBen Roth for (NSString *name in @[UIApplicationDidBecomeActiveNotification, 880fbf6a33SBen Roth UIApplicationDidEnterBackgroundNotification, 890fbf6a33SBen Roth UIApplicationDidFinishLaunchingNotification, 900fbf6a33SBen Roth UIApplicationWillResignActiveNotification, 910fbf6a33SBen Roth UIApplicationWillEnterForegroundNotification]) { 920fbf6a33SBen Roth 930fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self 940fbf6a33SBen Roth selector:@selector(_handleAppStateDidChange:) 950fbf6a33SBen Roth name:name 960fbf6a33SBen Roth object:nil]; 970fbf6a33SBen Roth } 980fbf6a33SBen Roth NSLog(@"Expo iOS Client Version %@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"EXClientVersion"]); 990fbf6a33SBen Roth } 1000fbf6a33SBen Roth return self; 1010fbf6a33SBen Roth} 1020fbf6a33SBen Roth 1030fbf6a33SBen Roth- (void)dealloc 1040fbf6a33SBen Roth{ 1050fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] removeObserver:self]; 1060fbf6a33SBen Roth} 1070fbf6a33SBen Roth 1080fbf6a33SBen Roth- (void)sendNotification:(NSDictionary *)notifBody 1090fbf6a33SBen Roth toExperienceWithId:(NSString *)destinationExperienceId 1100fbf6a33SBen Roth fromBackground:(BOOL)isFromBackground 1110fbf6a33SBen Roth isRemote:(BOOL)isRemote 1120fbf6a33SBen Roth{ 1130fbf6a33SBen Roth EXReactAppManager *destinationAppManager = _bridgeRegistry.kernelAppManager; 1140fbf6a33SBen Roth for (id bridge in [_bridgeRegistry bridgeEnumerator]) { 1150fbf6a33SBen Roth EXKernelBridgeRecord *bridgeRecord = [_bridgeRegistry recordForBridge:bridge]; 1160fbf6a33SBen Roth if (bridgeRecord.experienceId && [bridgeRecord.experienceId isEqualToString:destinationExperienceId]) { 1170fbf6a33SBen Roth destinationAppManager = bridgeRecord.appManager; 1180fbf6a33SBen Roth break; 1190fbf6a33SBen Roth } 1200fbf6a33SBen Roth } 1210fbf6a33SBen Roth // if the notification came from the background, in most but not all cases, this means the user acted on an iOS notification 1220fbf6a33SBen Roth // and caused the app to launch. 1230fbf6a33SBen Roth // From SO: 1240fbf6a33SBen Roth // > Note that "App opened from Notification" will be a false positive if the notification is sent while the user is on a different 1250fbf6a33SBen Roth // > screen (for example, if they pull down the status bar and then receive a notification from your app). 1260fbf6a33SBen Roth NSDictionary *bodyWithOrigin = @{ 1270fbf6a33SBen Roth @"origin": (isFromBackground) ? @"selected" : @"received", 1280fbf6a33SBen Roth @"remote": @(isRemote), 1290fbf6a33SBen Roth @"data": notifBody, 1300fbf6a33SBen Roth }; 1310fbf6a33SBen Roth if (destinationAppManager) { 1320fbf6a33SBen Roth if (destinationAppManager == _bridgeRegistry.kernelAppManager) { 1330fbf6a33SBen Roth // send both the body and the experience id, so we can open a new experience from the kernel 1340fbf6a33SBen Roth [self _dispatchJSEvent:@"Exponent.notification" 1350fbf6a33SBen Roth body:@{ 1360fbf6a33SBen Roth @"body": bodyWithOrigin, 1370fbf6a33SBen Roth @"experienceId": destinationExperienceId, 1380fbf6a33SBen Roth } 1390fbf6a33SBen Roth onAppManager:_bridgeRegistry.kernelAppManager]; 1400fbf6a33SBen Roth } else { 1410fbf6a33SBen Roth // send the body to the already-open experience 1420fbf6a33SBen Roth [self _dispatchJSEvent:@"Exponent.notification" body:bodyWithOrigin onAppManager:destinationAppManager]; 1430fbf6a33SBen Roth [self _moveAppManagerToForeground:destinationAppManager]; 1440fbf6a33SBen Roth } 1450fbf6a33SBen Roth } 1460fbf6a33SBen Roth} 1470fbf6a33SBen Roth 1480fbf6a33SBen Roth- (void)registerRootExponentViewController:(EXViewController *)exponentViewController 1490fbf6a33SBen Roth{ 1500fbf6a33SBen Roth _vcExponentRoot = exponentViewController; 1510fbf6a33SBen Roth} 1520fbf6a33SBen Roth 1530fbf6a33SBen Roth- (EXViewController *)rootViewController 1540fbf6a33SBen Roth{ 1550fbf6a33SBen Roth return _vcExponentRoot; 1560fbf6a33SBen Roth} 1570fbf6a33SBen Roth 1580fbf6a33SBen Roth- (UIInterfaceOrientationMask)supportedInterfaceOrientationsForForegroundTask 1590fbf6a33SBen Roth{ 1600fbf6a33SBen Roth if (_bridgeRegistry.lastKnownForegroundBridge) { 1610fbf6a33SBen Roth if (_bridgeRegistry.lastKnownForegroundBridge != _bridgeRegistry.kernelAppManager.reactBridge) { 1620fbf6a33SBen Roth EXKernelBridgeRecord *foregroundBridgeRecord = [_bridgeRegistry recordForBridge:_bridgeRegistry.lastKnownForegroundBridge]; 1630fbf6a33SBen Roth if (foregroundBridgeRecord.appManager.frame) { 1640fbf6a33SBen Roth return [foregroundBridgeRecord.appManager.frame supportedInterfaceOrientations]; 1650fbf6a33SBen Roth } 1660fbf6a33SBen Roth } 1670fbf6a33SBen Roth } 1680fbf6a33SBen Roth // kernel or unknown bridge: lock to portrait 1690fbf6a33SBen Roth return UIInterfaceOrientationMaskPortrait; 1700fbf6a33SBen Roth} 1710fbf6a33SBen Roth 1720fbf6a33SBen Roth- (void)_onKernelJSLoaded 1730fbf6a33SBen Roth{ 1740fbf6a33SBen Roth // used by appetize: optionally disable nux 1750fbf6a33SBen Roth BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:EXKernelDisableNuxDefaultsKey]; 1760fbf6a33SBen Roth if (disableNuxDefaultsValue) { 1770fbf6a33SBen Roth [self dispatchKernelJSEvent:@"resetNuxState" body:@{ @"isNuxCompleted": @YES } onSuccess:nil onFailure:nil]; 1780fbf6a33SBen Roth [[NSUserDefaults standardUserDefaults] removeObjectForKey:EXKernelDisableNuxDefaultsKey]; 1790fbf6a33SBen Roth } 1800fbf6a33SBen Roth} 1810fbf6a33SBen Roth 1820fbf6a33SBen Roth#pragma mark - Linking 1830fbf6a33SBen Roth 1840fbf6a33SBen Roth- (void)_refreshForegroundTask:(NSNotification *)notif 1850fbf6a33SBen Roth{ 1860fbf6a33SBen Roth id notifBridge = notif.userInfo[@"bridge"]; 1870fbf6a33SBen Roth if ([notifBridge respondsToSelector:@selector(parentBridge)]) { 1880fbf6a33SBen Roth notifBridge = [notifBridge parentBridge]; 1890fbf6a33SBen Roth } 1900fbf6a33SBen Roth if (notifBridge == _bridgeRegistry.kernelAppManager.reactBridge) { 1910fbf6a33SBen Roth DDLogError(@"Can't use ExponentUtil.reload() on the kernel bridge. Use RN dev tools to reload the bundle."); 1920fbf6a33SBen Roth return; 1930fbf6a33SBen Roth } 1940fbf6a33SBen Roth if (notifBridge == _bridgeRegistry.lastKnownForegroundBridge) { 1950fbf6a33SBen Roth // only the foreground task is allowed to force a reload 1960fbf6a33SBen Roth [self dispatchKernelJSEvent:@"refresh" body:@{} onSuccess:nil onFailure:nil]; 1970fbf6a33SBen Roth } 1980fbf6a33SBen Roth} 1990fbf6a33SBen Roth 2000fbf6a33SBen Roth/** 2010fbf6a33SBen Roth * Expected notif.userInfo keys: 2020fbf6a33SBen Roth * url - the url (NSString) to route 2030fbf6a33SBen Roth * bridge - (optional) if this event came from a bridge, a pointer to that bridge 2040fbf6a33SBen Roth */ 2050fbf6a33SBen Roth- (void)_routeUrl: (NSNotification *)notif 2060fbf6a33SBen Roth{ 2070fbf6a33SBen Roth NSURL *notifUri = [NSURL URLWithString:notif.userInfo[@"url"]]; 2080fbf6a33SBen Roth if (!notifUri) { 2090fbf6a33SBen Roth DDLogInfo(@"Tried to route invalid url: %@", notif.userInfo[@"url"]); 2100fbf6a33SBen Roth return; 2110fbf6a33SBen Roth } 2120fbf6a33SBen Roth NSString *urlToRoute = [[self class] _uriTransformedForRouting:notifUri].absoluteString; 2130fbf6a33SBen Roth 2140fbf6a33SBen Roth // kernel bridge is our default handler for this url 2150fbf6a33SBen Roth // because it can open a new bridge if we don't already have one. 2160fbf6a33SBen Roth EXReactAppManager *destinationAppManager = _bridgeRegistry.kernelAppManager; 2170fbf6a33SBen Roth 2180fbf6a33SBen Roth for (id bridge in [_bridgeRegistry bridgeEnumerator]) { 2190fbf6a33SBen Roth EXKernelBridgeRecord *bridgeRecord = [_bridgeRegistry recordForBridge:bridge]; 2200fbf6a33SBen Roth if ([urlToRoute hasPrefix:[[self class] linkingUriForExperienceUri:bridgeRecord.appManager.frame.initialUri]]) { 2210fbf6a33SBen Roth // this is a link into a bridge we already have running. 2220fbf6a33SBen Roth // use this bridge as the link's destination instead of the kernel. 2230fbf6a33SBen Roth destinationAppManager = bridgeRecord.appManager; 2240fbf6a33SBen Roth break; 2250fbf6a33SBen Roth } 2260fbf6a33SBen Roth } 2270fbf6a33SBen Roth 2280fbf6a33SBen Roth if (destinationAppManager) { 2290fbf6a33SBen Roth // fire a Linking url event on this (possibly versioned) bridge 2300fbf6a33SBen Roth id linkingModule = [self _nativeModuleForAppManager:destinationAppManager named:@"LinkingManager"]; 2310fbf6a33SBen Roth if (!linkingModule) { 2320fbf6a33SBen Roth DDLogError(@"Could not find the Linking module to open URL (%@)", urlToRoute); 2330fbf6a33SBen Roth } else if ([linkingModule respondsToSelector:@selector(dispatchOpenUrlEvent:)]) { 2340fbf6a33SBen Roth [linkingModule dispatchOpenUrlEvent:[NSURL URLWithString:urlToRoute]]; 2350fbf6a33SBen Roth } else { 2360fbf6a33SBen Roth DDLogError(@"Linking module doesn't support the API we use to open URL (%@)", urlToRoute); 2370fbf6a33SBen Roth } 2380fbf6a33SBen Roth [self _moveAppManagerToForeground:destinationAppManager]; 2390fbf6a33SBen Roth } 2400fbf6a33SBen Roth} 2410fbf6a33SBen Roth 2420fbf6a33SBen Roth+ (NSString *)linkingUriForExperienceUri:(NSURL *)uri 2430fbf6a33SBen Roth{ 2440fbf6a33SBen Roth uri = [self _uriTransformedForRouting:uri]; 2450fbf6a33SBen Roth NSURLComponents *components = [NSURLComponents componentsWithURL:uri resolvingAgainstBaseURL:YES]; 2460fbf6a33SBen Roth 2470fbf6a33SBen Roth // if the provided uri is the shell app manifest uri, 2480fbf6a33SBen Roth // this should have been transformed into customscheme://+deep-link 2490fbf6a33SBen Roth // and then all we do here is strip off the deep-link part, leaving +. 2500fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && [[EXShellManager sharedInstance] isShellUrlScheme:components.scheme]) { 2510fbf6a33SBen Roth return [NSString stringWithFormat:@"%@://", components.scheme]; 2520fbf6a33SBen Roth } 2530fbf6a33SBen Roth 2540fbf6a33SBen Roth NSMutableString* path = [NSMutableString stringWithString:components.path]; 2550fbf6a33SBen Roth 2560fbf6a33SBen Roth // if the uri already contains a deep link, strip everything specific to that 2570fbf6a33SBen Roth NSRange deepLinkRange = [path rangeOfString:@"+"]; 2580fbf6a33SBen Roth if (deepLinkRange.length > 0) { 2590fbf6a33SBen Roth path = [[path substringToIndex:deepLinkRange.location] mutableCopy]; 2600fbf6a33SBen Roth } 2610fbf6a33SBen Roth 2620fbf6a33SBen Roth if (path.length == 0 || [path characterAtIndex:path.length - 1] != '/') { 2630fbf6a33SBen Roth [path appendString:@"/"]; 2640fbf6a33SBen Roth } 2650fbf6a33SBen Roth [path appendString:@"+"]; 2660fbf6a33SBen Roth components.path = path; 2670fbf6a33SBen Roth 2680fbf6a33SBen Roth components.query = nil; 2690fbf6a33SBen Roth 2700fbf6a33SBen Roth return [components string]; 2710fbf6a33SBen Roth} 2720fbf6a33SBen Roth 2730fbf6a33SBen Roth+ (NSURL *)_uriTransformedForRouting: (NSURL *)uri 2740fbf6a33SBen Roth{ 2750fbf6a33SBen Roth if (!uri) { 2760fbf6a33SBen Roth return nil; 2770fbf6a33SBen Roth } 2780fbf6a33SBen Roth 2790fbf6a33SBen Roth NSURL *normalizedUri = [self _uriNormalizedForRouting:uri]; 2800fbf6a33SBen Roth 2810fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && [EXShellManager sharedInstance].hasUrlScheme) { 2820fbf6a33SBen Roth // if the provided uri is the shell app manifest uri, 2830fbf6a33SBen Roth // transform this into customscheme://deep-link 2840fbf6a33SBen Roth if ([self _isShellManifestUrl:normalizedUri]) { 2850fbf6a33SBen Roth NSString *uriString = normalizedUri.absoluteString; 2860fbf6a33SBen Roth NSRange deepLinkRange = [uriString rangeOfString:@"+"]; 2870fbf6a33SBen Roth NSString *deepLink = @""; 2880fbf6a33SBen Roth if (deepLinkRange.length > 0) { 2890fbf6a33SBen Roth deepLink = [uriString substringFromIndex:deepLinkRange.location + 1]; 2900fbf6a33SBen Roth } 2910fbf6a33SBen Roth NSString *result = [NSString stringWithFormat:@"%@://%@", [EXShellManager sharedInstance].urlScheme, deepLink]; 2920fbf6a33SBen Roth return [NSURL URLWithString:result]; 2930fbf6a33SBen Roth } 2940fbf6a33SBen Roth } 2950fbf6a33SBen Roth return normalizedUri; 2960fbf6a33SBen Roth} 2970fbf6a33SBen Roth 2980fbf6a33SBen Roth+ (NSURL *)_uriNormalizedForRouting: (NSURL *)uri 2990fbf6a33SBen Roth{ 3000fbf6a33SBen Roth NSURLComponents *components = [NSURLComponents componentsWithURL:uri resolvingAgainstBaseURL:YES]; 3010fbf6a33SBen Roth 3020fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && [[EXShellManager sharedInstance] isShellUrlScheme:components.scheme]) { 3030fbf6a33SBen Roth // if we're a shell and this uri had the shell scheme, leave it alone. 3040fbf6a33SBen Roth } else { 3050fbf6a33SBen Roth if ([components.scheme isEqualToString:@"https"]) { 3060fbf6a33SBen Roth components.scheme = @"exps"; 3070fbf6a33SBen Roth } else { 3080fbf6a33SBen Roth components.scheme = @"exp"; 3090fbf6a33SBen Roth } 3100fbf6a33SBen Roth } 3110fbf6a33SBen Roth 3120fbf6a33SBen Roth if ([components.scheme isEqualToString:@"exp"] && [components.port integerValue] == 80) { 3130fbf6a33SBen Roth components.port = nil; 3140fbf6a33SBen Roth } else if ([components.scheme isEqualToString:@"exps"] && [components.port integerValue] == 443) { 3150fbf6a33SBen Roth components.port = nil; 3160fbf6a33SBen Roth } 3170fbf6a33SBen Roth 3180fbf6a33SBen Roth return [components URL]; 3190fbf6a33SBen Roth} 3200fbf6a33SBen Roth 3210fbf6a33SBen Roth+ (BOOL)_isShellManifestUrl: (NSURL *)normalizedUri 3220fbf6a33SBen Roth{ 3230fbf6a33SBen Roth NSString *uriString = normalizedUri.absoluteString; 3240fbf6a33SBen Roth for (NSString *shellManifestUrl in [EXShellManager sharedInstance].allManifestUrls) { 3250fbf6a33SBen Roth NSURL *normalizedShellManifestURL = [self _uriNormalizedForRouting:[NSURL URLWithString:shellManifestUrl]]; 3260fbf6a33SBen Roth if ([normalizedShellManifestURL.absoluteString isEqualToString:uriString]) { 3270fbf6a33SBen Roth return YES; 3280fbf6a33SBen Roth } 3290fbf6a33SBen Roth } 3300fbf6a33SBen Roth return NO; 3310fbf6a33SBen Roth} 3320fbf6a33SBen Roth 3330fbf6a33SBen Roth+ (BOOL)application:(UIApplication *)application 3340fbf6a33SBen Roth openURL:(NSURL *)URL 3350fbf6a33SBen Roth sourceApplication:(NSString *)sourceApplication 3360fbf6a33SBen Roth annotation:(id)annotation 3370fbf6a33SBen Roth{ 3380fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:kEXKernelOpenUrlNotification 3390fbf6a33SBen Roth object:nil 3400fbf6a33SBen Roth userInfo:@{ 3410fbf6a33SBen Roth @"url": URL.absoluteString 3420fbf6a33SBen Roth }]; 3430fbf6a33SBen Roth return YES; 3440fbf6a33SBen Roth} 3450fbf6a33SBen Roth 3460fbf6a33SBen Roth+ (BOOL)application:(UIApplication *)application 3470fbf6a33SBen RothcontinueUserActivity:(NSUserActivity *)userActivity 3480fbf6a33SBen Roth restorationHandler:(void (^)(NSArray *))restorationHandler 3490fbf6a33SBen Roth{ 3500fbf6a33SBen Roth if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { 3510fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:kEXKernelOpenUrlNotification 3520fbf6a33SBen Roth object:nil 3530fbf6a33SBen Roth userInfo:@{ 3540fbf6a33SBen Roth @"url": userActivity.webpageURL.absoluteString 3550fbf6a33SBen Roth }]; 3560fbf6a33SBen Roth } 3570fbf6a33SBen Roth return YES; 3580fbf6a33SBen Roth} 3590fbf6a33SBen Roth 3600fbf6a33SBen Roth+ (NSURL *)initialUrlFromLaunchOptions:(NSDictionary *)launchOptions 3610fbf6a33SBen Roth{ 3620fbf6a33SBen Roth NSURL *initialUrl; 3630fbf6a33SBen Roth 3640fbf6a33SBen Roth if (launchOptions) { 3650fbf6a33SBen Roth if (launchOptions[UIApplicationLaunchOptionsURLKey]) { 3660fbf6a33SBen Roth initialUrl = launchOptions[UIApplicationLaunchOptionsURLKey]; 3670fbf6a33SBen Roth } else if (launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]) { 3680fbf6a33SBen Roth NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]; 3690fbf6a33SBen Roth 3700fbf6a33SBen Roth if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) { 3710fbf6a33SBen Roth initialUrl = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL; 3720fbf6a33SBen Roth } 3730fbf6a33SBen Roth } 3740fbf6a33SBen Roth } 3750fbf6a33SBen Roth 3760fbf6a33SBen Roth return initialUrl; 3770fbf6a33SBen Roth} 3780fbf6a33SBen Roth 3790fbf6a33SBen Roth+ (NSString *)deviceInstallUUID 3800fbf6a33SBen Roth{ 3810fbf6a33SBen Roth NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:kEXDeviceInstallUUIDKey]; 3820fbf6a33SBen Roth if (!uuid) { 3830fbf6a33SBen Roth uuid = [[NSUUID UUID] UUIDString]; 3840fbf6a33SBen Roth [[NSUserDefaults standardUserDefaults] setObject:uuid forKey:kEXDeviceInstallUUIDKey]; 3850fbf6a33SBen Roth [[NSUserDefaults standardUserDefaults] synchronize]; 3860fbf6a33SBen Roth } 3870fbf6a33SBen Roth return uuid; 3880fbf6a33SBen Roth} 3890fbf6a33SBen Roth 3900fbf6a33SBen Roth#pragma mark - Bridge stuff 3910fbf6a33SBen Roth 3920fbf6a33SBen Roth- (void)dispatchKernelJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^_Nullable)(NSDictionary * _Nullable))success onFailure:(void (^_Nullable)(NSString * _Nullable))failure 3930fbf6a33SBen Roth{ 3940fbf6a33SBen Roth EXKernelModule *kernelModule = [self _nativeModuleForAppManager:_bridgeRegistry.kernelAppManager named:@"ExponentKernel"]; 3950fbf6a33SBen Roth if (kernelModule) { 3960fbf6a33SBen Roth [kernelModule dispatchJSEvent:eventName body:eventBody onSuccess:success onFailure:failure]; 3970fbf6a33SBen Roth } 3980fbf6a33SBen Roth} 3990fbf6a33SBen Roth 4000fbf6a33SBen Roth- (void)_dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onAppManager:(EXReactAppManager *)appManager 4010fbf6a33SBen Roth{ 4020fbf6a33SBen Roth [appManager.reactBridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" 4030fbf6a33SBen Roth args:eventBody ? @[eventName, eventBody] : @[eventName]]; 4040fbf6a33SBen Roth} 4050fbf6a33SBen Roth 4060fbf6a33SBen Roth- (id)_nativeModuleForAppManager:(EXReactAppManager *)appManager named:(NSString *)moduleName 4070fbf6a33SBen Roth{ 4080fbf6a33SBen Roth id destinationBridge = appManager.reactBridge; 4090fbf6a33SBen Roth 4100fbf6a33SBen Roth if ([destinationBridge respondsToSelector:@selector(batchedBridge)]) { 4110fbf6a33SBen Roth id batchedBridge = [destinationBridge batchedBridge]; 4120fbf6a33SBen Roth id moduleData = [batchedBridge moduleDataForName:moduleName]; 4130fbf6a33SBen Roth 4140fbf6a33SBen Roth // React Native before SDK 11 didn't strip the "RCT" prefix from module names 4150fbf6a33SBen Roth if (!moduleData && ![moduleName hasPrefix:@"RCT"]) { 4160fbf6a33SBen Roth moduleData = [batchedBridge moduleDataForName:[@"RCT" stringByAppendingString:moduleName]]; 4170fbf6a33SBen Roth } 4180fbf6a33SBen Roth 4190fbf6a33SBen Roth if (moduleData) { 4200fbf6a33SBen Roth return [moduleData instance]; 4210fbf6a33SBen Roth } 4220fbf6a33SBen Roth } else { 4230fbf6a33SBen Roth DDLogError(@"Bridge does not support the API we use to get its underlying batched bridge"); 4240fbf6a33SBen Roth } 4250fbf6a33SBen Roth return nil; 4260fbf6a33SBen Roth} 4270fbf6a33SBen Roth 4280fbf6a33SBen Roth- (void)_moveAppManagerToForeground: (EXReactAppManager *)appManager 4290fbf6a33SBen Roth{ 4300fbf6a33SBen Roth if (appManager != _bridgeRegistry.kernelAppManager) { 4310fbf6a33SBen Roth EXFrameReactAppManager *frameAppManager = (EXFrameReactAppManager *)appManager; 4320fbf6a33SBen Roth // kernel JS needs to bring the relevant frame/bridge to visibility. 4330fbf6a33SBen Roth NSURL *frameUrlToForeground = frameAppManager.frame.initialUri; 4340fbf6a33SBen Roth [self dispatchKernelJSEvent:kEXKernelShouldForegroundTaskEvent body:@{ @"taskUrl":frameUrlToForeground.absoluteString } onSuccess:nil onFailure:nil]; 4350fbf6a33SBen Roth } 4360fbf6a33SBen Roth} 4370fbf6a33SBen Roth 4380fbf6a33SBen Roth/** 4390fbf6a33SBen Roth * If the bridge has a batchedBridge or parentBridge selector, posts the notification on that object as well. 4400fbf6a33SBen Roth */ 4410fbf6a33SBen Roth- (void)_postNotificationName: (NSNotificationName)name onAbstractBridge: (id)bridge 4420fbf6a33SBen Roth{ 4430fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:name object:bridge]; 4440fbf6a33SBen Roth if ([bridge respondsToSelector:@selector(batchedBridge)]) { 4450fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:name object:[bridge batchedBridge]]; 4460fbf6a33SBen Roth } else if ([bridge respondsToSelector:@selector(parentBridge)]) { 4470fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:name object:[bridge parentBridge]]; 4480fbf6a33SBen Roth } 4490fbf6a33SBen Roth} 4500fbf6a33SBen Roth 4510fbf6a33SBen Roth#pragma mark - EXKernelModuleDelegate 4520fbf6a33SBen Roth 453*f148175aSBen Roth- (BOOL)kernelModuleShouldEnableLegacyMenuBehavior:(EXKernelModule *)module 454*f148175aSBen Roth{ 455*f148175aSBen Roth return [EXKernelDevKeyCommands sharedInstance].isLegacyMenuBehaviorEnabled; 456*f148175aSBen Roth} 457*f148175aSBen Roth 458*f148175aSBen Roth- (void)kernelModule:(EXKernelModule *)module didSelectEnableLegacyMenuBehavior:(BOOL)isEnabled 459*f148175aSBen Roth{ 460*f148175aSBen Roth [EXKernelDevKeyCommands sharedInstance].isLegacyMenuBehaviorEnabled = isEnabled; 461*f148175aSBen Roth} 462*f148175aSBen Roth 4630fbf6a33SBen Roth- (BOOL)kernelModuleShouldEnableDevtools:(__unused EXKernelModule *)module 4640fbf6a33SBen Roth{ 4650fbf6a33SBen Roth return ( 466*f148175aSBen Roth (![EXKernelDevKeyCommands sharedInstance].isLegacyMenuBehaviorEnabled) && 4670fbf6a33SBen Roth _bridgeRegistry.lastKnownForegroundAppManager != _bridgeRegistry.kernelAppManager && 4680fbf6a33SBen Roth [_bridgeRegistry.lastKnownForegroundAppManager areDevtoolsEnabled] 4690fbf6a33SBen Roth ); 4700fbf6a33SBen Roth} 4710fbf6a33SBen Roth 4720fbf6a33SBen Roth- (NSDictionary<NSString *, NSString *> *)devMenuItemsForKernelModule:(EXKernelModule *)module 4730fbf6a33SBen Roth{ 4740fbf6a33SBen Roth return [_bridgeRegistry.lastKnownForegroundAppManager devMenuItems]; 4750fbf6a33SBen Roth} 4760fbf6a33SBen Roth 4770fbf6a33SBen Roth- (void)kernelModule:(EXKernelModule *)module didSelectDevMenuItemWithKey:(NSString *)key 4780fbf6a33SBen Roth{ 4790fbf6a33SBen Roth [_bridgeRegistry.lastKnownForegroundAppManager selectDevMenuItemWithKey:key]; 4800fbf6a33SBen Roth} 4810fbf6a33SBen Roth 4820fbf6a33SBen Roth- (void)kernelModuleDidSelectKernelDevMenu:(__unused EXKernelModule *)module 4830fbf6a33SBen Roth{ 4840fbf6a33SBen Roth EXKernelDevMenuViewController *vcDevMenu = [[EXKernelDevMenuViewController alloc] init]; 4850fbf6a33SBen Roth if (_vcExponentRoot) { 4860fbf6a33SBen Roth [_vcExponentRoot presentViewController:vcDevMenu animated:YES completion:nil]; 4870fbf6a33SBen Roth } 4880fbf6a33SBen Roth} 4890fbf6a33SBen Roth 4908e3c2ccdSBen Roth- (BOOL)kernelModuleShouldAutoReloadCurrentTask:(EXKernelModule *)module 4918e3c2ccdSBen Roth{ 4928e3c2ccdSBen Roth NSString *foregroundTaskExperienceId = _bridgeRegistry.lastKnownForegroundAppManager.experienceId; 4938e3c2ccdSBen Roth return [_recoveryManager experienceIdShouldReloadOnError:foregroundTaskExperienceId]; 4948e3c2ccdSBen Roth} 4958e3c2ccdSBen Roth 4960fbf6a33SBen Roth- (void)kernelModule:(__unused EXKernelModule *)module taskDidForegroundWithType:(NSInteger)type params:(NSDictionary *)params 4970fbf6a33SBen Roth{ 4980fbf6a33SBen Roth EXKernelRoute routetype = (EXKernelRoute)type; 4990fbf6a33SBen Roth [[EXAnalytics sharedInstance] logForegroundEventForRoute:routetype fromJS:YES]; 5000fbf6a33SBen Roth 5010fbf6a33SBen Roth if (params) { 5020fbf6a33SBen Roth NSString *urlToForeground = params[@"url"]; 5030fbf6a33SBen Roth NSString *urlToBackground = params[@"urlToBackground"]; 5040fbf6a33SBen Roth EXReactAppManager *appManagerToForeground = nil; 5050fbf6a33SBen Roth EXReactAppManager *appManagerToBackground = nil; 5060fbf6a33SBen Roth 5070fbf6a33SBen Roth if (routetype == kEXKernelRouteHome) { 5080fbf6a33SBen Roth appManagerToForeground = _bridgeRegistry.kernelAppManager; 5090fbf6a33SBen Roth } 5100fbf6a33SBen Roth if (routetype == kEXKernelRouteBrowser && !urlToBackground) { 5110fbf6a33SBen Roth appManagerToBackground = _bridgeRegistry.kernelAppManager; 5120fbf6a33SBen Roth } 5130fbf6a33SBen Roth 5140fbf6a33SBen Roth for (id bridge in [_bridgeRegistry bridgeEnumerator]) { 5150fbf6a33SBen Roth EXKernelBridgeRecord *bridgeRecord = [_bridgeRegistry recordForBridge:bridge]; 5160fbf6a33SBen Roth if (urlToForeground && [bridgeRecord.appManager.frame.initialUri.absoluteString isEqualToString:urlToForeground]) { 5170fbf6a33SBen Roth appManagerToForeground = bridgeRecord.appManager; 5180fbf6a33SBen Roth } else if (urlToBackground && [bridgeRecord.appManager.frame.initialUri.absoluteString isEqualToString:urlToBackground]) { 5190fbf6a33SBen Roth appManagerToBackground = bridgeRecord.appManager; 5200fbf6a33SBen Roth } 5210fbf6a33SBen Roth } 5220fbf6a33SBen Roth 5230fbf6a33SBen Roth if (appManagerToBackground) { 5240fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidBackgroundNotification onAbstractBridge:appManagerToBackground.reactBridge]; 5250fbf6a33SBen Roth id appStateModule = [self _nativeModuleForAppManager:appManagerToBackground named:@"AppState"]; 5260fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(setState:)]) { 5270fbf6a33SBen Roth [appStateModule setState:@"background"]; 5280fbf6a33SBen Roth } 5290fbf6a33SBen Roth } 5300fbf6a33SBen Roth if (appManagerToForeground) { 5310fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidForegroundNotification onAbstractBridge:appManagerToForeground.reactBridge]; 5320fbf6a33SBen Roth id appStateModule = [self _nativeModuleForAppManager:appManagerToForeground named:@"AppState"]; 5330fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(setState:)]) { 5340fbf6a33SBen Roth [appStateModule setState:@"active"]; 5350fbf6a33SBen Roth } 5360fbf6a33SBen Roth _bridgeRegistry.lastKnownForegroundBridge = appManagerToForeground.reactBridge; 5370fbf6a33SBen Roth } else { 5380fbf6a33SBen Roth _bridgeRegistry.lastKnownForegroundBridge = nil; 5390fbf6a33SBen Roth } 5400fbf6a33SBen Roth 5410fbf6a33SBen Roth } 5420fbf6a33SBen Roth} 5430fbf6a33SBen Roth 5440fbf6a33SBen Roth- (void)kernelModule:(EXKernelModule *)module didRequestManifestWithUrl:(NSURL *)url originalUrl:(NSURL *)originalUrl success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure 5450fbf6a33SBen Roth{ 5460fbf6a33SBen Roth if (!([url.scheme isEqualToString:@"http"] || [url.scheme isEqualToString:@"https"])) { 5470fbf6a33SBen Roth NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; 5480fbf6a33SBen Roth components.scheme = @"http"; 5490fbf6a33SBen Roth url = [components URL]; 5500fbf6a33SBen Roth } 5510fbf6a33SBen Roth EXCachedResourceBehavior cacheBehavior = kEXCachedResourceFallBackToCache; 5520fbf6a33SBen Roth if ([url.host isEqualToString:@"localhost"]) { 5530fbf6a33SBen Roth // we can't pre-detect if this person is using a developer tool, but using localhost is a pretty solid indicator. 5540fbf6a33SBen Roth cacheBehavior = kEXCachedResourceNoCache; 5550fbf6a33SBen Roth } 5560fbf6a33SBen Roth EXManifestResource *manifestResource = [[EXManifestResource alloc] initWithManifestUrl:url originalUrl:originalUrl]; 5570fbf6a33SBen Roth [manifestResource loadResourceWithBehavior:cacheBehavior successBlock:^(NSData * _Nonnull data) { 5580fbf6a33SBen Roth NSString *manifestString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 5590fbf6a33SBen Roth success(manifestString); 5600fbf6a33SBen Roth } errorBlock:^(NSError * _Nonnull error) { 5610fbf6a33SBen Roth#if DEBUG 5620fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && error && error.code == 404) { 5630fbf6a33SBen Roth NSString *message = error.localizedDescription; 5640fbf6a33SBen Roth message = [NSString stringWithFormat:@"Make sure you are serving your project from XDE or exp (%@)", message]; 5650fbf6a33SBen Roth error = [NSError errorWithDomain:error.domain code:error.code userInfo:@{ NSLocalizedDescriptionKey: message }]; 5660fbf6a33SBen Roth } 5670fbf6a33SBen Roth#endif 5680fbf6a33SBen Roth failure(error); 5690fbf6a33SBen Roth }]; 5700fbf6a33SBen Roth} 5710fbf6a33SBen Roth 5720fbf6a33SBen Roth#pragma mark - App State 5730fbf6a33SBen Roth 5740fbf6a33SBen Roth- (void)_handleAppStateDidChange:(NSNotification *)notification 5750fbf6a33SBen Roth{ 5760fbf6a33SBen Roth NSString *newState; 5770fbf6a33SBen Roth 5780fbf6a33SBen Roth if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) { 5790fbf6a33SBen Roth newState = @"inactive"; 5800fbf6a33SBen Roth } else if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) { 5810fbf6a33SBen Roth newState = @"background"; 5820fbf6a33SBen Roth } else { 5830fbf6a33SBen Roth switch (RCTSharedApplication().applicationState) { 5840fbf6a33SBen Roth case UIApplicationStateActive: 5850fbf6a33SBen Roth newState = @"active"; 5860fbf6a33SBen Roth break; 5870fbf6a33SBen Roth case UIApplicationStateBackground: { 5880fbf6a33SBen Roth newState = @"background"; 5890fbf6a33SBen Roth break; 5900fbf6a33SBen Roth } 5910fbf6a33SBen Roth default: { 5920fbf6a33SBen Roth newState = @"unknown"; 5930fbf6a33SBen Roth break; 5940fbf6a33SBen Roth } 5950fbf6a33SBen Roth } 5960fbf6a33SBen Roth } 5970fbf6a33SBen Roth 5980fbf6a33SBen Roth if (_bridgeRegistry.lastKnownForegroundBridge) { 5990fbf6a33SBen Roth EXKernelBridgeRecord *foregroundRecord = [_bridgeRegistry recordForBridge:_bridgeRegistry.lastKnownForegroundBridge]; 6000fbf6a33SBen Roth id appStateModule = [self _nativeModuleForAppManager:foregroundRecord.appManager named:@"AppState"]; 6010fbf6a33SBen Roth NSString *lastKnownState; 6020fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(lastKnownState)]) { 6030fbf6a33SBen Roth lastKnownState = [appStateModule lastKnownState]; 6040fbf6a33SBen Roth } 6050fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(setState:)]) { 6060fbf6a33SBen Roth [appStateModule setState:newState]; 6070fbf6a33SBen Roth } 6080fbf6a33SBen Roth if (!lastKnownState || ![newState isEqualToString:lastKnownState]) { 6090fbf6a33SBen Roth if ([newState isEqualToString:@"active"]) { 6100fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidForegroundNotification onAbstractBridge:_bridgeRegistry.lastKnownForegroundBridge]; 6110fbf6a33SBen Roth } else if ([newState isEqualToString:@"background"]) { 6120fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidBackgroundNotification onAbstractBridge:_bridgeRegistry.lastKnownForegroundBridge]; 6130fbf6a33SBen Roth } 6140fbf6a33SBen Roth } 6150fbf6a33SBen Roth } 6160fbf6a33SBen Roth} 6170fbf6a33SBen Roth 6180fbf6a33SBen Roth@end 6190fbf6a33SBen Roth 6200fbf6a33SBen RothNS_ASSUME_NONNULL_END 621