xref: /expo/ios/Exponent/ExpoKit/ExpoKit.m (revision 033ea1fc)
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