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