1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "ExpoKit.h" 4#import "EXViewController.h" 5#import "EXBuildConstants.h" 6#import "EXEnvironment.h" 7#import "EXKernel.h" 8#import "EXKernelUtil.h" 9#import "EXKernelLinkingManager.h" 10#import "EXReactAppExceptionHandler.h" 11#import "EXRemoteNotificationManager.h" 12 13#if __has_include(<EXNotifications/EXNotificationCenterDelegate.h>) 14#import <EXNotifications/EXNotificationCenterDelegate.h> 15#endif 16 17#import <ExpoModulesCore/EXModuleRegistryProvider.h> 18 19#import <GoogleMaps/GoogleMaps.h> 20 21NSString * const EXAppDidRegisterForRemoteNotificationsNotification = @"kEXAppDidRegisterForRemoteNotificationsNotification"; 22NSString * const EXAppDidRegisterUserNotificationSettingsNotification = @"kEXAppDidRegisterUserNotificationSettingsNotification"; 23 24@interface ExpoKit () 25{ 26 Class _rootViewControllerClass; 27} 28 29@property (nonatomic, nullable, strong) EXViewController *rootViewController; 30@property (nonatomic, strong) NSDictionary *launchOptions; 31 32@end 33 34@implementation ExpoKit 35 36+ (nonnull instancetype)sharedInstance 37{ 38 static ExpoKit *theExpoKit = nil; 39 static dispatch_once_t once; 40 dispatch_once(&once, ^{ 41 if (!theExpoKit) { 42 theExpoKit = [[ExpoKit alloc] init]; 43 } 44 }); 45 return theExpoKit; 46} 47 48- (instancetype)init 49{ 50 if (self = [super init]) { 51 _rootViewControllerClass = [EXViewController class]; 52 [self _initDefaultKeys]; 53 } 54 return self; 55} 56 57- (void)dealloc 58{ 59 [[NSNotificationCenter defaultCenter] removeObserver:self]; 60} 61 62- (void)registerRootViewControllerClass:(Class)rootViewControllerClass 63{ 64 NSAssert([rootViewControllerClass isSubclassOfClass:[EXViewController class]], @"ExpoKit root view controller class must subclass EXViewController."); 65 _rootViewControllerClass = rootViewControllerClass; 66} 67 68- (EXViewController *)rootViewController 69{ 70 if (!_rootViewController) { 71 _rootViewController = [[_rootViewControllerClass alloc] init]; 72 _rootViewController.delegate = [EXKernel sharedInstance]; 73 } 74 return _rootViewController; 75} 76 77- (UIViewController *)currentViewController 78{ 79 EXViewController *rootViewController = [self rootViewController]; 80 UIViewController *controller = [rootViewController contentViewController]; 81 while (controller.presentedViewController != nil) { 82 controller = controller.presentedViewController; 83 } 84 return controller; 85} 86 87- (void)prepareWithLaunchOptions:(nullable NSDictionary *)launchOptions 88{ 89 [DDLog addLogger:[DDOSLogger sharedInstance]]; 90 RCTSetFatalHandler(handleFatalReactError); 91 92 NSString *standaloneGMSKey = [[NSBundle mainBundle].infoDictionary objectForKey:@"GMSApiKey"]; 93 if (standaloneGMSKey && standaloneGMSKey.length) { 94 [GMSServices provideAPIKey:standaloneGMSKey]; 95 } else { 96 if (_applicationKeys[@"GOOGLE_MAPS_IOS_API_KEY"]) {// we may define this as empty 97 if ([_applicationKeys[@"GOOGLE_MAPS_IOS_API_KEY"] length]) { 98 [GMSServices provideAPIKey:_applicationKeys[@"GOOGLE_MAPS_IOS_API_KEY"]]; 99 } 100 } 101 } 102 103 _launchOptions = launchOptions; 104} 105 106#pragma mark - misc AppDelegate hooks 107 108- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 109{ 110#if __has_include(<EXNotifications/EXNotificationCenterDelegate.h>) 111 if (![UNUserNotificationCenter currentNotificationCenter].delegate) { 112 EXLogWarn(@"UNUserNotificationCenter delegates should be set by EXNotificationCenterDelegate."); 113 } 114 115 // Register EXUserNotificationManager as a delegate of EXNotificationCenterDelegate 116 id<EXNotificationCenterDelegate> notificationCenterDelegate = (id<EXNotificationCenterDelegate>) [EXModuleRegistryProvider getSingletonModuleForClass:[EXNotificationCenterDelegate class]]; 117 [notificationCenterDelegate addDelegate:(id<EXNotificationsDelegate>)[EXKernel sharedInstance].serviceRegistry.notificationsManager]; 118#else 119 [[UNUserNotificationCenter currentNotificationCenter] setDelegate:(id<UNUserNotificationCenterDelegate>) [EXKernel sharedInstance].serviceRegistry.notificationsManager]; 120 // This is safe to call; if the app doesn't have permission to display user-facing notifications 121 // then registering for a push token is a no-op 122 [[EXKernel sharedInstance].serviceRegistry.remoteNotificationManager registerForRemoteNotifications]; 123#endif 124 125 return YES; 126} 127 128 129#pragma mark - APNS hooks 130 131- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token 132{ 133 [[EXKernel sharedInstance].serviceRegistry.remoteNotificationManager registerAPNSToken:token registrationError:nil]; 134 [[NSNotificationCenter defaultCenter] postNotificationName:EXAppDidRegisterForRemoteNotificationsNotification object:nil userInfo:@{ @"token": token }]; 135} 136 137- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err 138{ 139 DDLogWarn(@"Failed to register for remote notifs: %@", err); 140 [[EXKernel sharedInstance].serviceRegistry.remoteNotificationManager registerAPNSToken:nil registrationError:err]; 141 142 // Post this even in the failure case -- up to subscribers to subsequently read the system permission state 143 [[NSNotificationCenter defaultCenter] postNotificationName:EXAppDidRegisterForRemoteNotificationsNotification object:nil]; 144} 145 146- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 147fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { 148 149 // Here background task execution should go. 150 151 completionHandler(UIBackgroundFetchResultNoData); 152} 153 154#pragma mark - deep linking hooks 155 156- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation 157{ 158 return [EXKernelLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; 159} 160 161- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler 162{ 163 if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { 164 NSURL *webpageURL = userActivity.webpageURL; 165 if ([EXEnvironment sharedEnvironment].isDetached) { 166 return [EXKernelLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 167 } else { 168 NSString *path = [webpageURL path]; 169 170 // Filter out URLs that don't match experience URLs since the AASA pattern's grammar is not as 171 // expressive as we'd like and matches profile URLs too 172 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^/@[a-z0-9_-]+/.+$" 173 options:NSRegularExpressionCaseInsensitive 174 error:nil]; 175 NSUInteger matchCount = [regex numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]; 176 177 if (matchCount > 0) { 178 [EXKernelLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 179 return YES; 180 } else { 181 if (![path isEqualToString:@"/expo-go"]) { 182 [application openURL:webpageURL options:@{} completionHandler:nil]; 183 return YES; 184 } 185 } 186 } 187 } 188 189 return NO; 190} 191 192#pragma mark - internal 193 194- (void)_initDefaultKeys 195{ 196 // these are provided in the expo/expo open source repo as defaults; they can all be overridden by setting 197 // the `applicationKeys` property on ExpoKit. 198 if ([EXBuildConstants sharedInstance].defaultApiKeys) { 199 self.applicationKeys = [EXBuildConstants sharedInstance].defaultApiKeys; 200 } 201} 202 203@end 204