1*0fbf6a33SBen Roth// Copyright 2015-present 650 Industries. All rights reserved. 2*0fbf6a33SBen Roth 3*0fbf6a33SBen Roth#import "EXAnalytics.h" 4*0fbf6a33SBen Roth#import "EXAppState.h" 5*0fbf6a33SBen Roth#import "EXKernelDevMenuViewController.h" 6*0fbf6a33SBen Roth#import "EXFrame.h" 7*0fbf6a33SBen Roth#import "EXFrameReactAppManager.h" 8*0fbf6a33SBen Roth#import "EXKernel.h" 9*0fbf6a33SBen Roth#import "EXKernelBridgeRecord.h" 10*0fbf6a33SBen Roth#import "EXKernelDevMotionHandler.h" 11*0fbf6a33SBen Roth#import "EXKernelDevKeyCommands.h" 12*0fbf6a33SBen Roth#import "EXKernelModule.h" 13*0fbf6a33SBen Roth#import "EXLinkingManager.h" 14*0fbf6a33SBen Roth#import "EXManifestResource.h" 15*0fbf6a33SBen Roth#import "EXShellManager.h" 16*0fbf6a33SBen Roth#import "EXVersions.h" 17*0fbf6a33SBen Roth#import "EXViewController.h" 18*0fbf6a33SBen Roth 19*0fbf6a33SBen Roth#import <React/RCTBridge+Private.h> 20*0fbf6a33SBen Roth#import <React/RCTEventDispatcher.h> 21*0fbf6a33SBen Roth#import <React/RCTModuleData.h> 22*0fbf6a33SBen Roth#import <React/RCTUtils.h> 23*0fbf6a33SBen Roth 24*0fbf6a33SBen RothNS_ASSUME_NONNULL_BEGIN 25*0fbf6a33SBen Roth 26*0fbf6a33SBen RothNSString *kEXKernelErrorDomain = @"EXKernelErrorDomain"; 27*0fbf6a33SBen RothNSNotificationName kEXKernelOpenUrlNotification = @"EXKernelOpenUrlNotification"; 28*0fbf6a33SBen RothNSNotificationName kEXKernelRefreshForegroundTaskNotification = @"EXKernelRefreshForegroundTaskNotification"; 29*0fbf6a33SBen RothNSNotificationName kEXKernelGetPushTokenNotification = @"EXKernelGetPushTokenNotification"; 30*0fbf6a33SBen RothNSNotificationName kEXKernelJSIsLoadedNotification = @"EXKernelJSIsLoadedNotification"; 31*0fbf6a33SBen RothNSString *kEXKernelShouldForegroundTaskEvent = @"foregroundTask"; 32*0fbf6a33SBen RothNSString * const kEXDeviceInstallUUIDKey = @"EXDeviceInstallUUIDKey"; 33*0fbf6a33SBen RothNSString * const kEXKernelClearJSCacheUserDefaultsKey = @"EXKernelClearJSCacheUserDefaultsKey"; 34*0fbf6a33SBen RothNSString * const EXKernelDisableNuxDefaultsKey = @"EXKernelDisableNuxDefaultsKey"; 35*0fbf6a33SBen Roth 36*0fbf6a33SBen Roth@interface EXKernel () 37*0fbf6a33SBen Roth 38*0fbf6a33SBen Roth@property (nonatomic, weak) EXViewController *vcExponentRoot; 39*0fbf6a33SBen Roth 40*0fbf6a33SBen Roth@end 41*0fbf6a33SBen Roth 42*0fbf6a33SBen Roth@implementation EXKernel 43*0fbf6a33SBen Roth 44*0fbf6a33SBen Roth+ (instancetype)sharedInstance 45*0fbf6a33SBen Roth{ 46*0fbf6a33SBen Roth static EXKernel *theKernel; 47*0fbf6a33SBen Roth static dispatch_once_t once; 48*0fbf6a33SBen Roth dispatch_once(&once, ^{ 49*0fbf6a33SBen Roth if (!theKernel) { 50*0fbf6a33SBen Roth theKernel = [[EXKernel alloc] init]; 51*0fbf6a33SBen Roth } 52*0fbf6a33SBen Roth }); 53*0fbf6a33SBen Roth return theKernel; 54*0fbf6a33SBen Roth} 55*0fbf6a33SBen Roth 56*0fbf6a33SBen Roth+ (BOOL)isDevKernel 57*0fbf6a33SBen Roth{ 58*0fbf6a33SBen Roth // if we're in detached state (i.e. ExponentView) then never expect local kernel 59*0fbf6a33SBen Roth BOOL isDetachedKernel = ([[EXVersions sharedInstance].versions objectForKey:@"detachedNativeVersions"] != nil); 60*0fbf6a33SBen Roth if (isDetachedKernel) { 61*0fbf6a33SBen Roth return NO; 62*0fbf6a33SBen Roth } 63*0fbf6a33SBen Roth 64*0fbf6a33SBen Roth // otherwise, expect local kernel when we are attached to xcode 65*0fbf6a33SBen Roth#if DEBUG 66*0fbf6a33SBen Roth return YES; 67*0fbf6a33SBen Roth#endif 68*0fbf6a33SBen Roth return NO; 69*0fbf6a33SBen Roth} 70*0fbf6a33SBen Roth 71*0fbf6a33SBen Roth- (instancetype)init 72*0fbf6a33SBen Roth{ 73*0fbf6a33SBen Roth if (self = [super init]) { 74*0fbf6a33SBen Roth _bridgeRegistry = [[EXKernelBridgeRegistry alloc] init]; 75*0fbf6a33SBen Roth [EXKernelDevMotionHandler sharedInstance]; 76*0fbf6a33SBen Roth [EXKernelDevKeyCommands sharedInstance]; 77*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self 78*0fbf6a33SBen Roth selector:@selector(_routeUrl:) 79*0fbf6a33SBen Roth name:kEXKernelOpenUrlNotification 80*0fbf6a33SBen Roth object:nil]; 81*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self 82*0fbf6a33SBen Roth selector:@selector(_refreshForegroundTask:) 83*0fbf6a33SBen Roth name:kEXKernelRefreshForegroundTaskNotification 84*0fbf6a33SBen Roth object:nil]; 85*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_onKernelJSLoaded) name:kEXKernelJSIsLoadedNotification object:nil]; 86*0fbf6a33SBen Roth for (NSString *name in @[UIApplicationDidBecomeActiveNotification, 87*0fbf6a33SBen Roth UIApplicationDidEnterBackgroundNotification, 88*0fbf6a33SBen Roth UIApplicationDidFinishLaunchingNotification, 89*0fbf6a33SBen Roth UIApplicationWillResignActiveNotification, 90*0fbf6a33SBen Roth UIApplicationWillEnterForegroundNotification]) { 91*0fbf6a33SBen Roth 92*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] addObserver:self 93*0fbf6a33SBen Roth selector:@selector(_handleAppStateDidChange:) 94*0fbf6a33SBen Roth name:name 95*0fbf6a33SBen Roth object:nil]; 96*0fbf6a33SBen Roth } 97*0fbf6a33SBen Roth NSLog(@"Expo iOS Client Version %@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"EXClientVersion"]); 98*0fbf6a33SBen Roth } 99*0fbf6a33SBen Roth return self; 100*0fbf6a33SBen Roth} 101*0fbf6a33SBen Roth 102*0fbf6a33SBen Roth- (void)dealloc 103*0fbf6a33SBen Roth{ 104*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] removeObserver:self]; 105*0fbf6a33SBen Roth} 106*0fbf6a33SBen Roth 107*0fbf6a33SBen Roth- (void)sendNotification:(NSDictionary *)notifBody 108*0fbf6a33SBen Roth toExperienceWithId:(NSString *)destinationExperienceId 109*0fbf6a33SBen Roth fromBackground:(BOOL)isFromBackground 110*0fbf6a33SBen Roth isRemote:(BOOL)isRemote 111*0fbf6a33SBen Roth{ 112*0fbf6a33SBen Roth EXReactAppManager *destinationAppManager = _bridgeRegistry.kernelAppManager; 113*0fbf6a33SBen Roth for (id bridge in [_bridgeRegistry bridgeEnumerator]) { 114*0fbf6a33SBen Roth EXKernelBridgeRecord *bridgeRecord = [_bridgeRegistry recordForBridge:bridge]; 115*0fbf6a33SBen Roth if (bridgeRecord.experienceId && [bridgeRecord.experienceId isEqualToString:destinationExperienceId]) { 116*0fbf6a33SBen Roth destinationAppManager = bridgeRecord.appManager; 117*0fbf6a33SBen Roth break; 118*0fbf6a33SBen Roth } 119*0fbf6a33SBen Roth } 120*0fbf6a33SBen Roth // if the notification came from the background, in most but not all cases, this means the user acted on an iOS notification 121*0fbf6a33SBen Roth // and caused the app to launch. 122*0fbf6a33SBen Roth // From SO: 123*0fbf6a33SBen Roth // > Note that "App opened from Notification" will be a false positive if the notification is sent while the user is on a different 124*0fbf6a33SBen Roth // > screen (for example, if they pull down the status bar and then receive a notification from your app). 125*0fbf6a33SBen Roth NSDictionary *bodyWithOrigin = @{ 126*0fbf6a33SBen Roth @"origin": (isFromBackground) ? @"selected" : @"received", 127*0fbf6a33SBen Roth @"remote": @(isRemote), 128*0fbf6a33SBen Roth @"data": notifBody, 129*0fbf6a33SBen Roth }; 130*0fbf6a33SBen Roth if (destinationAppManager) { 131*0fbf6a33SBen Roth if (destinationAppManager == _bridgeRegistry.kernelAppManager) { 132*0fbf6a33SBen Roth // send both the body and the experience id, so we can open a new experience from the kernel 133*0fbf6a33SBen Roth [self _dispatchJSEvent:@"Exponent.notification" 134*0fbf6a33SBen Roth body:@{ 135*0fbf6a33SBen Roth @"body": bodyWithOrigin, 136*0fbf6a33SBen Roth @"experienceId": destinationExperienceId, 137*0fbf6a33SBen Roth } 138*0fbf6a33SBen Roth onAppManager:_bridgeRegistry.kernelAppManager]; 139*0fbf6a33SBen Roth } else { 140*0fbf6a33SBen Roth // send the body to the already-open experience 141*0fbf6a33SBen Roth [self _dispatchJSEvent:@"Exponent.notification" body:bodyWithOrigin onAppManager:destinationAppManager]; 142*0fbf6a33SBen Roth [self _moveAppManagerToForeground:destinationAppManager]; 143*0fbf6a33SBen Roth } 144*0fbf6a33SBen Roth } 145*0fbf6a33SBen Roth} 146*0fbf6a33SBen Roth 147*0fbf6a33SBen Roth- (void)registerRootExponentViewController:(EXViewController *)exponentViewController 148*0fbf6a33SBen Roth{ 149*0fbf6a33SBen Roth _vcExponentRoot = exponentViewController; 150*0fbf6a33SBen Roth} 151*0fbf6a33SBen Roth 152*0fbf6a33SBen Roth- (EXViewController *)rootViewController 153*0fbf6a33SBen Roth{ 154*0fbf6a33SBen Roth return _vcExponentRoot; 155*0fbf6a33SBen Roth} 156*0fbf6a33SBen Roth 157*0fbf6a33SBen Roth- (UIInterfaceOrientationMask)supportedInterfaceOrientationsForForegroundTask 158*0fbf6a33SBen Roth{ 159*0fbf6a33SBen Roth if (_bridgeRegistry.lastKnownForegroundBridge) { 160*0fbf6a33SBen Roth if (_bridgeRegistry.lastKnownForegroundBridge != _bridgeRegistry.kernelAppManager.reactBridge) { 161*0fbf6a33SBen Roth EXKernelBridgeRecord *foregroundBridgeRecord = [_bridgeRegistry recordForBridge:_bridgeRegistry.lastKnownForegroundBridge]; 162*0fbf6a33SBen Roth if (foregroundBridgeRecord.appManager.frame) { 163*0fbf6a33SBen Roth return [foregroundBridgeRecord.appManager.frame supportedInterfaceOrientations]; 164*0fbf6a33SBen Roth } 165*0fbf6a33SBen Roth } 166*0fbf6a33SBen Roth } 167*0fbf6a33SBen Roth // kernel or unknown bridge: lock to portrait 168*0fbf6a33SBen Roth return UIInterfaceOrientationMaskPortrait; 169*0fbf6a33SBen Roth} 170*0fbf6a33SBen Roth 171*0fbf6a33SBen Roth- (void)_onKernelJSLoaded 172*0fbf6a33SBen Roth{ 173*0fbf6a33SBen Roth // used by appetize: optionally disable nux 174*0fbf6a33SBen Roth BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:EXKernelDisableNuxDefaultsKey]; 175*0fbf6a33SBen Roth if (disableNuxDefaultsValue) { 176*0fbf6a33SBen Roth [self dispatchKernelJSEvent:@"resetNuxState" body:@{ @"isNuxCompleted": @YES } onSuccess:nil onFailure:nil]; 177*0fbf6a33SBen Roth [[NSUserDefaults standardUserDefaults] removeObjectForKey:EXKernelDisableNuxDefaultsKey]; 178*0fbf6a33SBen Roth } 179*0fbf6a33SBen Roth} 180*0fbf6a33SBen Roth 181*0fbf6a33SBen Roth#pragma mark - Linking 182*0fbf6a33SBen Roth 183*0fbf6a33SBen Roth- (void)_refreshForegroundTask:(NSNotification *)notif 184*0fbf6a33SBen Roth{ 185*0fbf6a33SBen Roth id notifBridge = notif.userInfo[@"bridge"]; 186*0fbf6a33SBen Roth if ([notifBridge respondsToSelector:@selector(parentBridge)]) { 187*0fbf6a33SBen Roth notifBridge = [notifBridge parentBridge]; 188*0fbf6a33SBen Roth } 189*0fbf6a33SBen Roth if (notifBridge == _bridgeRegistry.kernelAppManager.reactBridge) { 190*0fbf6a33SBen Roth DDLogError(@"Can't use ExponentUtil.reload() on the kernel bridge. Use RN dev tools to reload the bundle."); 191*0fbf6a33SBen Roth return; 192*0fbf6a33SBen Roth } 193*0fbf6a33SBen Roth if (notifBridge == _bridgeRegistry.lastKnownForegroundBridge) { 194*0fbf6a33SBen Roth // only the foreground task is allowed to force a reload 195*0fbf6a33SBen Roth [self dispatchKernelJSEvent:@"refresh" body:@{} onSuccess:nil onFailure:nil]; 196*0fbf6a33SBen Roth } 197*0fbf6a33SBen Roth} 198*0fbf6a33SBen Roth 199*0fbf6a33SBen Roth/** 200*0fbf6a33SBen Roth * Expected notif.userInfo keys: 201*0fbf6a33SBen Roth * url - the url (NSString) to route 202*0fbf6a33SBen Roth * bridge - (optional) if this event came from a bridge, a pointer to that bridge 203*0fbf6a33SBen Roth */ 204*0fbf6a33SBen Roth- (void)_routeUrl: (NSNotification *)notif 205*0fbf6a33SBen Roth{ 206*0fbf6a33SBen Roth NSURL *notifUri = [NSURL URLWithString:notif.userInfo[@"url"]]; 207*0fbf6a33SBen Roth if (!notifUri) { 208*0fbf6a33SBen Roth DDLogInfo(@"Tried to route invalid url: %@", notif.userInfo[@"url"]); 209*0fbf6a33SBen Roth return; 210*0fbf6a33SBen Roth } 211*0fbf6a33SBen Roth NSString *urlToRoute = [[self class] _uriTransformedForRouting:notifUri].absoluteString; 212*0fbf6a33SBen Roth 213*0fbf6a33SBen Roth // kernel bridge is our default handler for this url 214*0fbf6a33SBen Roth // because it can open a new bridge if we don't already have one. 215*0fbf6a33SBen Roth EXReactAppManager *destinationAppManager = _bridgeRegistry.kernelAppManager; 216*0fbf6a33SBen Roth 217*0fbf6a33SBen Roth for (id bridge in [_bridgeRegistry bridgeEnumerator]) { 218*0fbf6a33SBen Roth EXKernelBridgeRecord *bridgeRecord = [_bridgeRegistry recordForBridge:bridge]; 219*0fbf6a33SBen Roth if ([urlToRoute hasPrefix:[[self class] linkingUriForExperienceUri:bridgeRecord.appManager.frame.initialUri]]) { 220*0fbf6a33SBen Roth // this is a link into a bridge we already have running. 221*0fbf6a33SBen Roth // use this bridge as the link's destination instead of the kernel. 222*0fbf6a33SBen Roth destinationAppManager = bridgeRecord.appManager; 223*0fbf6a33SBen Roth break; 224*0fbf6a33SBen Roth } 225*0fbf6a33SBen Roth } 226*0fbf6a33SBen Roth 227*0fbf6a33SBen Roth if (destinationAppManager) { 228*0fbf6a33SBen Roth // fire a Linking url event on this (possibly versioned) bridge 229*0fbf6a33SBen Roth id linkingModule = [self _nativeModuleForAppManager:destinationAppManager named:@"LinkingManager"]; 230*0fbf6a33SBen Roth if (!linkingModule) { 231*0fbf6a33SBen Roth DDLogError(@"Could not find the Linking module to open URL (%@)", urlToRoute); 232*0fbf6a33SBen Roth } else if ([linkingModule respondsToSelector:@selector(dispatchOpenUrlEvent:)]) { 233*0fbf6a33SBen Roth [linkingModule dispatchOpenUrlEvent:[NSURL URLWithString:urlToRoute]]; 234*0fbf6a33SBen Roth } else { 235*0fbf6a33SBen Roth DDLogError(@"Linking module doesn't support the API we use to open URL (%@)", urlToRoute); 236*0fbf6a33SBen Roth } 237*0fbf6a33SBen Roth [self _moveAppManagerToForeground:destinationAppManager]; 238*0fbf6a33SBen Roth } 239*0fbf6a33SBen Roth} 240*0fbf6a33SBen Roth 241*0fbf6a33SBen Roth+ (NSString *)linkingUriForExperienceUri:(NSURL *)uri 242*0fbf6a33SBen Roth{ 243*0fbf6a33SBen Roth uri = [self _uriTransformedForRouting:uri]; 244*0fbf6a33SBen Roth NSURLComponents *components = [NSURLComponents componentsWithURL:uri resolvingAgainstBaseURL:YES]; 245*0fbf6a33SBen Roth 246*0fbf6a33SBen Roth // if the provided uri is the shell app manifest uri, 247*0fbf6a33SBen Roth // this should have been transformed into customscheme://+deep-link 248*0fbf6a33SBen Roth // and then all we do here is strip off the deep-link part, leaving +. 249*0fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && [[EXShellManager sharedInstance] isShellUrlScheme:components.scheme]) { 250*0fbf6a33SBen Roth return [NSString stringWithFormat:@"%@://", components.scheme]; 251*0fbf6a33SBen Roth } 252*0fbf6a33SBen Roth 253*0fbf6a33SBen Roth NSMutableString* path = [NSMutableString stringWithString:components.path]; 254*0fbf6a33SBen Roth 255*0fbf6a33SBen Roth // if the uri already contains a deep link, strip everything specific to that 256*0fbf6a33SBen Roth NSRange deepLinkRange = [path rangeOfString:@"+"]; 257*0fbf6a33SBen Roth if (deepLinkRange.length > 0) { 258*0fbf6a33SBen Roth path = [[path substringToIndex:deepLinkRange.location] mutableCopy]; 259*0fbf6a33SBen Roth } 260*0fbf6a33SBen Roth 261*0fbf6a33SBen Roth if (path.length == 0 || [path characterAtIndex:path.length - 1] != '/') { 262*0fbf6a33SBen Roth [path appendString:@"/"]; 263*0fbf6a33SBen Roth } 264*0fbf6a33SBen Roth [path appendString:@"+"]; 265*0fbf6a33SBen Roth components.path = path; 266*0fbf6a33SBen Roth 267*0fbf6a33SBen Roth components.query = nil; 268*0fbf6a33SBen Roth 269*0fbf6a33SBen Roth return [components string]; 270*0fbf6a33SBen Roth} 271*0fbf6a33SBen Roth 272*0fbf6a33SBen Roth+ (NSURL *)_uriTransformedForRouting: (NSURL *)uri 273*0fbf6a33SBen Roth{ 274*0fbf6a33SBen Roth if (!uri) { 275*0fbf6a33SBen Roth return nil; 276*0fbf6a33SBen Roth } 277*0fbf6a33SBen Roth 278*0fbf6a33SBen Roth NSURL *normalizedUri = [self _uriNormalizedForRouting:uri]; 279*0fbf6a33SBen Roth 280*0fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && [EXShellManager sharedInstance].hasUrlScheme) { 281*0fbf6a33SBen Roth // if the provided uri is the shell app manifest uri, 282*0fbf6a33SBen Roth // transform this into customscheme://deep-link 283*0fbf6a33SBen Roth if ([self _isShellManifestUrl:normalizedUri]) { 284*0fbf6a33SBen Roth NSString *uriString = normalizedUri.absoluteString; 285*0fbf6a33SBen Roth NSRange deepLinkRange = [uriString rangeOfString:@"+"]; 286*0fbf6a33SBen Roth NSString *deepLink = @""; 287*0fbf6a33SBen Roth if (deepLinkRange.length > 0) { 288*0fbf6a33SBen Roth deepLink = [uriString substringFromIndex:deepLinkRange.location + 1]; 289*0fbf6a33SBen Roth } 290*0fbf6a33SBen Roth NSString *result = [NSString stringWithFormat:@"%@://%@", [EXShellManager sharedInstance].urlScheme, deepLink]; 291*0fbf6a33SBen Roth return [NSURL URLWithString:result]; 292*0fbf6a33SBen Roth } 293*0fbf6a33SBen Roth } 294*0fbf6a33SBen Roth return normalizedUri; 295*0fbf6a33SBen Roth} 296*0fbf6a33SBen Roth 297*0fbf6a33SBen Roth+ (NSURL *)_uriNormalizedForRouting: (NSURL *)uri 298*0fbf6a33SBen Roth{ 299*0fbf6a33SBen Roth NSURLComponents *components = [NSURLComponents componentsWithURL:uri resolvingAgainstBaseURL:YES]; 300*0fbf6a33SBen Roth 301*0fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && [[EXShellManager sharedInstance] isShellUrlScheme:components.scheme]) { 302*0fbf6a33SBen Roth // if we're a shell and this uri had the shell scheme, leave it alone. 303*0fbf6a33SBen Roth } else { 304*0fbf6a33SBen Roth if ([components.scheme isEqualToString:@"https"]) { 305*0fbf6a33SBen Roth components.scheme = @"exps"; 306*0fbf6a33SBen Roth } else { 307*0fbf6a33SBen Roth components.scheme = @"exp"; 308*0fbf6a33SBen Roth } 309*0fbf6a33SBen Roth } 310*0fbf6a33SBen Roth 311*0fbf6a33SBen Roth if ([components.scheme isEqualToString:@"exp"] && [components.port integerValue] == 80) { 312*0fbf6a33SBen Roth components.port = nil; 313*0fbf6a33SBen Roth } else if ([components.scheme isEqualToString:@"exps"] && [components.port integerValue] == 443) { 314*0fbf6a33SBen Roth components.port = nil; 315*0fbf6a33SBen Roth } 316*0fbf6a33SBen Roth 317*0fbf6a33SBen Roth return [components URL]; 318*0fbf6a33SBen Roth} 319*0fbf6a33SBen Roth 320*0fbf6a33SBen Roth+ (BOOL)_isShellManifestUrl: (NSURL *)normalizedUri 321*0fbf6a33SBen Roth{ 322*0fbf6a33SBen Roth NSString *uriString = normalizedUri.absoluteString; 323*0fbf6a33SBen Roth for (NSString *shellManifestUrl in [EXShellManager sharedInstance].allManifestUrls) { 324*0fbf6a33SBen Roth NSURL *normalizedShellManifestURL = [self _uriNormalizedForRouting:[NSURL URLWithString:shellManifestUrl]]; 325*0fbf6a33SBen Roth if ([normalizedShellManifestURL.absoluteString isEqualToString:uriString]) { 326*0fbf6a33SBen Roth return YES; 327*0fbf6a33SBen Roth } 328*0fbf6a33SBen Roth } 329*0fbf6a33SBen Roth return NO; 330*0fbf6a33SBen Roth} 331*0fbf6a33SBen Roth 332*0fbf6a33SBen Roth+ (BOOL)application:(UIApplication *)application 333*0fbf6a33SBen Roth openURL:(NSURL *)URL 334*0fbf6a33SBen Roth sourceApplication:(NSString *)sourceApplication 335*0fbf6a33SBen Roth annotation:(id)annotation 336*0fbf6a33SBen Roth{ 337*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:kEXKernelOpenUrlNotification 338*0fbf6a33SBen Roth object:nil 339*0fbf6a33SBen Roth userInfo:@{ 340*0fbf6a33SBen Roth @"url": URL.absoluteString 341*0fbf6a33SBen Roth }]; 342*0fbf6a33SBen Roth return YES; 343*0fbf6a33SBen Roth} 344*0fbf6a33SBen Roth 345*0fbf6a33SBen Roth+ (BOOL)application:(UIApplication *)application 346*0fbf6a33SBen RothcontinueUserActivity:(NSUserActivity *)userActivity 347*0fbf6a33SBen Roth restorationHandler:(void (^)(NSArray *))restorationHandler 348*0fbf6a33SBen Roth{ 349*0fbf6a33SBen Roth if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { 350*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:kEXKernelOpenUrlNotification 351*0fbf6a33SBen Roth object:nil 352*0fbf6a33SBen Roth userInfo:@{ 353*0fbf6a33SBen Roth @"url": userActivity.webpageURL.absoluteString 354*0fbf6a33SBen Roth }]; 355*0fbf6a33SBen Roth } 356*0fbf6a33SBen Roth return YES; 357*0fbf6a33SBen Roth} 358*0fbf6a33SBen Roth 359*0fbf6a33SBen Roth+ (NSURL *)initialUrlFromLaunchOptions:(NSDictionary *)launchOptions 360*0fbf6a33SBen Roth{ 361*0fbf6a33SBen Roth NSURL *initialUrl; 362*0fbf6a33SBen Roth 363*0fbf6a33SBen Roth if (launchOptions) { 364*0fbf6a33SBen Roth if (launchOptions[UIApplicationLaunchOptionsURLKey]) { 365*0fbf6a33SBen Roth initialUrl = launchOptions[UIApplicationLaunchOptionsURLKey]; 366*0fbf6a33SBen Roth } else if (launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]) { 367*0fbf6a33SBen Roth NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]; 368*0fbf6a33SBen Roth 369*0fbf6a33SBen Roth if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) { 370*0fbf6a33SBen Roth initialUrl = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL; 371*0fbf6a33SBen Roth } 372*0fbf6a33SBen Roth } 373*0fbf6a33SBen Roth } 374*0fbf6a33SBen Roth 375*0fbf6a33SBen Roth return initialUrl; 376*0fbf6a33SBen Roth} 377*0fbf6a33SBen Roth 378*0fbf6a33SBen Roth+ (NSString *)deviceInstallUUID 379*0fbf6a33SBen Roth{ 380*0fbf6a33SBen Roth NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:kEXDeviceInstallUUIDKey]; 381*0fbf6a33SBen Roth if (!uuid) { 382*0fbf6a33SBen Roth uuid = [[NSUUID UUID] UUIDString]; 383*0fbf6a33SBen Roth [[NSUserDefaults standardUserDefaults] setObject:uuid forKey:kEXDeviceInstallUUIDKey]; 384*0fbf6a33SBen Roth [[NSUserDefaults standardUserDefaults] synchronize]; 385*0fbf6a33SBen Roth } 386*0fbf6a33SBen Roth return uuid; 387*0fbf6a33SBen Roth} 388*0fbf6a33SBen Roth 389*0fbf6a33SBen Roth#pragma mark - Bridge stuff 390*0fbf6a33SBen Roth 391*0fbf6a33SBen Roth- (void)dispatchKernelJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^_Nullable)(NSDictionary * _Nullable))success onFailure:(void (^_Nullable)(NSString * _Nullable))failure 392*0fbf6a33SBen Roth{ 393*0fbf6a33SBen Roth EXKernelModule *kernelModule = [self _nativeModuleForAppManager:_bridgeRegistry.kernelAppManager named:@"ExponentKernel"]; 394*0fbf6a33SBen Roth if (kernelModule) { 395*0fbf6a33SBen Roth [kernelModule dispatchJSEvent:eventName body:eventBody onSuccess:success onFailure:failure]; 396*0fbf6a33SBen Roth } 397*0fbf6a33SBen Roth} 398*0fbf6a33SBen Roth 399*0fbf6a33SBen Roth- (void)_dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onAppManager:(EXReactAppManager *)appManager 400*0fbf6a33SBen Roth{ 401*0fbf6a33SBen Roth [appManager.reactBridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" 402*0fbf6a33SBen Roth args:eventBody ? @[eventName, eventBody] : @[eventName]]; 403*0fbf6a33SBen Roth} 404*0fbf6a33SBen Roth 405*0fbf6a33SBen Roth- (id)_nativeModuleForAppManager:(EXReactAppManager *)appManager named:(NSString *)moduleName 406*0fbf6a33SBen Roth{ 407*0fbf6a33SBen Roth id destinationBridge = appManager.reactBridge; 408*0fbf6a33SBen Roth 409*0fbf6a33SBen Roth if ([destinationBridge respondsToSelector:@selector(batchedBridge)]) { 410*0fbf6a33SBen Roth id batchedBridge = [destinationBridge batchedBridge]; 411*0fbf6a33SBen Roth id moduleData = [batchedBridge moduleDataForName:moduleName]; 412*0fbf6a33SBen Roth 413*0fbf6a33SBen Roth // React Native before SDK 11 didn't strip the "RCT" prefix from module names 414*0fbf6a33SBen Roth if (!moduleData && ![moduleName hasPrefix:@"RCT"]) { 415*0fbf6a33SBen Roth moduleData = [batchedBridge moduleDataForName:[@"RCT" stringByAppendingString:moduleName]]; 416*0fbf6a33SBen Roth } 417*0fbf6a33SBen Roth 418*0fbf6a33SBen Roth if (moduleData) { 419*0fbf6a33SBen Roth return [moduleData instance]; 420*0fbf6a33SBen Roth } 421*0fbf6a33SBen Roth } else { 422*0fbf6a33SBen Roth DDLogError(@"Bridge does not support the API we use to get its underlying batched bridge"); 423*0fbf6a33SBen Roth } 424*0fbf6a33SBen Roth return nil; 425*0fbf6a33SBen Roth} 426*0fbf6a33SBen Roth 427*0fbf6a33SBen Roth- (void)_moveAppManagerToForeground: (EXReactAppManager *)appManager 428*0fbf6a33SBen Roth{ 429*0fbf6a33SBen Roth if (appManager != _bridgeRegistry.kernelAppManager) { 430*0fbf6a33SBen Roth EXFrameReactAppManager *frameAppManager = (EXFrameReactAppManager *)appManager; 431*0fbf6a33SBen Roth // kernel JS needs to bring the relevant frame/bridge to visibility. 432*0fbf6a33SBen Roth NSURL *frameUrlToForeground = frameAppManager.frame.initialUri; 433*0fbf6a33SBen Roth [self dispatchKernelJSEvent:kEXKernelShouldForegroundTaskEvent body:@{ @"taskUrl":frameUrlToForeground.absoluteString } onSuccess:nil onFailure:nil]; 434*0fbf6a33SBen Roth } 435*0fbf6a33SBen Roth} 436*0fbf6a33SBen Roth 437*0fbf6a33SBen Roth/** 438*0fbf6a33SBen Roth * If the bridge has a batchedBridge or parentBridge selector, posts the notification on that object as well. 439*0fbf6a33SBen Roth */ 440*0fbf6a33SBen Roth- (void)_postNotificationName: (NSNotificationName)name onAbstractBridge: (id)bridge 441*0fbf6a33SBen Roth{ 442*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:name object:bridge]; 443*0fbf6a33SBen Roth if ([bridge respondsToSelector:@selector(batchedBridge)]) { 444*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:name object:[bridge batchedBridge]]; 445*0fbf6a33SBen Roth } else if ([bridge respondsToSelector:@selector(parentBridge)]) { 446*0fbf6a33SBen Roth [[NSNotificationCenter defaultCenter] postNotificationName:name object:[bridge parentBridge]]; 447*0fbf6a33SBen Roth } 448*0fbf6a33SBen Roth} 449*0fbf6a33SBen Roth 450*0fbf6a33SBen Roth#pragma mark - EXKernelModuleDelegate 451*0fbf6a33SBen Roth 452*0fbf6a33SBen Roth- (BOOL)kernelModuleShouldEnableDevtools:(__unused EXKernelModule *)module 453*0fbf6a33SBen Roth{ 454*0fbf6a33SBen Roth return ( 455*0fbf6a33SBen Roth (!EX_ENABLE_LEGACY_MENU_BEHAVIOR) && 456*0fbf6a33SBen Roth _bridgeRegistry.lastKnownForegroundAppManager != _bridgeRegistry.kernelAppManager && 457*0fbf6a33SBen Roth [_bridgeRegistry.lastKnownForegroundAppManager areDevtoolsEnabled] 458*0fbf6a33SBen Roth ); 459*0fbf6a33SBen Roth} 460*0fbf6a33SBen Roth 461*0fbf6a33SBen Roth- (NSDictionary<NSString *, NSString *> *)devMenuItemsForKernelModule:(EXKernelModule *)module 462*0fbf6a33SBen Roth{ 463*0fbf6a33SBen Roth return [_bridgeRegistry.lastKnownForegroundAppManager devMenuItems]; 464*0fbf6a33SBen Roth} 465*0fbf6a33SBen Roth 466*0fbf6a33SBen Roth- (void)kernelModule:(EXKernelModule *)module didSelectDevMenuItemWithKey:(NSString *)key 467*0fbf6a33SBen Roth{ 468*0fbf6a33SBen Roth [_bridgeRegistry.lastKnownForegroundAppManager selectDevMenuItemWithKey:key]; 469*0fbf6a33SBen Roth} 470*0fbf6a33SBen Roth 471*0fbf6a33SBen Roth- (void)kernelModuleDidSelectKernelDevMenu:(__unused EXKernelModule *)module 472*0fbf6a33SBen Roth{ 473*0fbf6a33SBen Roth EXKernelDevMenuViewController *vcDevMenu = [[EXKernelDevMenuViewController alloc] init]; 474*0fbf6a33SBen Roth if (_vcExponentRoot) { 475*0fbf6a33SBen Roth [_vcExponentRoot presentViewController:vcDevMenu animated:YES completion:nil]; 476*0fbf6a33SBen Roth } 477*0fbf6a33SBen Roth} 478*0fbf6a33SBen Roth 479*0fbf6a33SBen Roth- (void)kernelModule:(__unused EXKernelModule *)module taskDidForegroundWithType:(NSInteger)type params:(NSDictionary *)params 480*0fbf6a33SBen Roth{ 481*0fbf6a33SBen Roth EXKernelRoute routetype = (EXKernelRoute)type; 482*0fbf6a33SBen Roth [[EXAnalytics sharedInstance] logForegroundEventForRoute:routetype fromJS:YES]; 483*0fbf6a33SBen Roth 484*0fbf6a33SBen Roth if (params) { 485*0fbf6a33SBen Roth NSString *urlToForeground = params[@"url"]; 486*0fbf6a33SBen Roth NSString *urlToBackground = params[@"urlToBackground"]; 487*0fbf6a33SBen Roth EXReactAppManager *appManagerToForeground = nil; 488*0fbf6a33SBen Roth EXReactAppManager *appManagerToBackground = nil; 489*0fbf6a33SBen Roth 490*0fbf6a33SBen Roth if (routetype == kEXKernelRouteHome) { 491*0fbf6a33SBen Roth appManagerToForeground = _bridgeRegistry.kernelAppManager; 492*0fbf6a33SBen Roth } 493*0fbf6a33SBen Roth if (routetype == kEXKernelRouteBrowser && !urlToBackground) { 494*0fbf6a33SBen Roth appManagerToBackground = _bridgeRegistry.kernelAppManager; 495*0fbf6a33SBen Roth } 496*0fbf6a33SBen Roth 497*0fbf6a33SBen Roth for (id bridge in [_bridgeRegistry bridgeEnumerator]) { 498*0fbf6a33SBen Roth EXKernelBridgeRecord *bridgeRecord = [_bridgeRegistry recordForBridge:bridge]; 499*0fbf6a33SBen Roth if (urlToForeground && [bridgeRecord.appManager.frame.initialUri.absoluteString isEqualToString:urlToForeground]) { 500*0fbf6a33SBen Roth appManagerToForeground = bridgeRecord.appManager; 501*0fbf6a33SBen Roth } else if (urlToBackground && [bridgeRecord.appManager.frame.initialUri.absoluteString isEqualToString:urlToBackground]) { 502*0fbf6a33SBen Roth appManagerToBackground = bridgeRecord.appManager; 503*0fbf6a33SBen Roth } 504*0fbf6a33SBen Roth } 505*0fbf6a33SBen Roth 506*0fbf6a33SBen Roth if (appManagerToBackground) { 507*0fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidBackgroundNotification onAbstractBridge:appManagerToBackground.reactBridge]; 508*0fbf6a33SBen Roth id appStateModule = [self _nativeModuleForAppManager:appManagerToBackground named:@"AppState"]; 509*0fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(setState:)]) { 510*0fbf6a33SBen Roth [appStateModule setState:@"background"]; 511*0fbf6a33SBen Roth } 512*0fbf6a33SBen Roth } 513*0fbf6a33SBen Roth if (appManagerToForeground) { 514*0fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidForegroundNotification onAbstractBridge:appManagerToForeground.reactBridge]; 515*0fbf6a33SBen Roth id appStateModule = [self _nativeModuleForAppManager:appManagerToForeground named:@"AppState"]; 516*0fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(setState:)]) { 517*0fbf6a33SBen Roth [appStateModule setState:@"active"]; 518*0fbf6a33SBen Roth } 519*0fbf6a33SBen Roth _bridgeRegistry.lastKnownForegroundBridge = appManagerToForeground.reactBridge; 520*0fbf6a33SBen Roth } else { 521*0fbf6a33SBen Roth _bridgeRegistry.lastKnownForegroundBridge = nil; 522*0fbf6a33SBen Roth } 523*0fbf6a33SBen Roth 524*0fbf6a33SBen Roth } 525*0fbf6a33SBen Roth} 526*0fbf6a33SBen Roth 527*0fbf6a33SBen Roth- (void)kernelModule:(EXKernelModule *)module didRequestManifestWithUrl:(NSURL *)url originalUrl:(NSURL *)originalUrl success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure 528*0fbf6a33SBen Roth{ 529*0fbf6a33SBen Roth if (!([url.scheme isEqualToString:@"http"] || [url.scheme isEqualToString:@"https"])) { 530*0fbf6a33SBen Roth NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; 531*0fbf6a33SBen Roth components.scheme = @"http"; 532*0fbf6a33SBen Roth url = [components URL]; 533*0fbf6a33SBen Roth } 534*0fbf6a33SBen Roth EXCachedResourceBehavior cacheBehavior = kEXCachedResourceFallBackToCache; 535*0fbf6a33SBen Roth if ([url.host isEqualToString:@"localhost"]) { 536*0fbf6a33SBen Roth // we can't pre-detect if this person is using a developer tool, but using localhost is a pretty solid indicator. 537*0fbf6a33SBen Roth cacheBehavior = kEXCachedResourceNoCache; 538*0fbf6a33SBen Roth } 539*0fbf6a33SBen Roth EXManifestResource *manifestResource = [[EXManifestResource alloc] initWithManifestUrl:url originalUrl:originalUrl]; 540*0fbf6a33SBen Roth [manifestResource loadResourceWithBehavior:cacheBehavior successBlock:^(NSData * _Nonnull data) { 541*0fbf6a33SBen Roth NSString *manifestString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 542*0fbf6a33SBen Roth success(manifestString); 543*0fbf6a33SBen Roth } errorBlock:^(NSError * _Nonnull error) { 544*0fbf6a33SBen Roth#if DEBUG 545*0fbf6a33SBen Roth if ([EXShellManager sharedInstance].isShell && error && error.code == 404) { 546*0fbf6a33SBen Roth NSString *message = error.localizedDescription; 547*0fbf6a33SBen Roth message = [NSString stringWithFormat:@"Make sure you are serving your project from XDE or exp (%@)", message]; 548*0fbf6a33SBen Roth error = [NSError errorWithDomain:error.domain code:error.code userInfo:@{ NSLocalizedDescriptionKey: message }]; 549*0fbf6a33SBen Roth } 550*0fbf6a33SBen Roth#endif 551*0fbf6a33SBen Roth failure(error); 552*0fbf6a33SBen Roth }]; 553*0fbf6a33SBen Roth} 554*0fbf6a33SBen Roth 555*0fbf6a33SBen Roth#pragma mark - App State 556*0fbf6a33SBen Roth 557*0fbf6a33SBen Roth- (void)_handleAppStateDidChange:(NSNotification *)notification 558*0fbf6a33SBen Roth{ 559*0fbf6a33SBen Roth NSString *newState; 560*0fbf6a33SBen Roth 561*0fbf6a33SBen Roth if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) { 562*0fbf6a33SBen Roth newState = @"inactive"; 563*0fbf6a33SBen Roth } else if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) { 564*0fbf6a33SBen Roth newState = @"background"; 565*0fbf6a33SBen Roth } else { 566*0fbf6a33SBen Roth switch (RCTSharedApplication().applicationState) { 567*0fbf6a33SBen Roth case UIApplicationStateActive: 568*0fbf6a33SBen Roth newState = @"active"; 569*0fbf6a33SBen Roth break; 570*0fbf6a33SBen Roth case UIApplicationStateBackground: { 571*0fbf6a33SBen Roth newState = @"background"; 572*0fbf6a33SBen Roth break; 573*0fbf6a33SBen Roth } 574*0fbf6a33SBen Roth default: { 575*0fbf6a33SBen Roth newState = @"unknown"; 576*0fbf6a33SBen Roth break; 577*0fbf6a33SBen Roth } 578*0fbf6a33SBen Roth } 579*0fbf6a33SBen Roth } 580*0fbf6a33SBen Roth 581*0fbf6a33SBen Roth if (_bridgeRegistry.lastKnownForegroundBridge) { 582*0fbf6a33SBen Roth EXKernelBridgeRecord *foregroundRecord = [_bridgeRegistry recordForBridge:_bridgeRegistry.lastKnownForegroundBridge]; 583*0fbf6a33SBen Roth id appStateModule = [self _nativeModuleForAppManager:foregroundRecord.appManager named:@"AppState"]; 584*0fbf6a33SBen Roth NSString *lastKnownState; 585*0fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(lastKnownState)]) { 586*0fbf6a33SBen Roth lastKnownState = [appStateModule lastKnownState]; 587*0fbf6a33SBen Roth } 588*0fbf6a33SBen Roth if ([appStateModule respondsToSelector:@selector(setState:)]) { 589*0fbf6a33SBen Roth [appStateModule setState:newState]; 590*0fbf6a33SBen Roth } 591*0fbf6a33SBen Roth if (!lastKnownState || ![newState isEqualToString:lastKnownState]) { 592*0fbf6a33SBen Roth if ([newState isEqualToString:@"active"]) { 593*0fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidForegroundNotification onAbstractBridge:_bridgeRegistry.lastKnownForegroundBridge]; 594*0fbf6a33SBen Roth } else if ([newState isEqualToString:@"background"]) { 595*0fbf6a33SBen Roth [self _postNotificationName:kEXKernelBridgeDidBackgroundNotification onAbstractBridge:_bridgeRegistry.lastKnownForegroundBridge]; 596*0fbf6a33SBen Roth } 597*0fbf6a33SBen Roth } 598*0fbf6a33SBen Roth } 599*0fbf6a33SBen Roth} 600*0fbf6a33SBen Roth 601*0fbf6a33SBen Roth@end 602*0fbf6a33SBen Roth 603*0fbf6a33SBen RothNS_ASSUME_NONNULL_END 604