1b987b50fSBen Roth// Copyright 2015-present 650 Industries. All rights reserved. 2b987b50fSBen Roth 39a7f140aSEric Samelson#import "EXEnvironment.h" 4b987b50fSBen Roth#import "EXHomeModule.h" 58a20a963SEric Samelson#import "EXSession.h" 6b987b50fSBen Roth#import "EXUnversioned.h" 772d142a5SStanisław Chmiela#import "EXClientReleaseType.h" 824a0cefbSTomasz Sapeta#import "EXKernelDevKeyCommands.h" 9*05aa4e6cSTomasz Sapeta 10*05aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 11f67462bcSTomasz Sapeta#import "EXDevMenuManager.h" 12*05aa4e6cSTomasz Sapeta#endif 13b987b50fSBen Roth 14b987b50fSBen Roth#import <React/RCTEventDispatcher.h> 15b987b50fSBen Roth 16b987b50fSBen Roth@interface EXHomeModule () 17b987b50fSBen Roth 18b987b50fSBen Roth@property (nonatomic, assign) BOOL hasListeners; 19b987b50fSBen Roth@property (nonatomic, strong) NSMutableDictionary *eventSuccessBlocks; 20b987b50fSBen Roth@property (nonatomic, strong) NSMutableDictionary *eventFailureBlocks; 21b987b50fSBen Roth@property (nonatomic, strong) NSArray * _Nonnull sdkVersions; 22b987b50fSBen Roth@property (nonatomic, weak) id<EXHomeModuleDelegate> delegate; 23b987b50fSBen Roth 24b987b50fSBen Roth@end 25b987b50fSBen Roth 26b987b50fSBen Roth@implementation EXHomeModule 27b987b50fSBen Roth 28b987b50fSBen Roth+ (NSString *)moduleName { return @"ExponentKernel"; } 29b987b50fSBen Roth 30b987b50fSBen Roth- (instancetype)initWithExperienceId:(NSString *)experienceId kernelServiceDelegate:(id)kernelServiceInstance params:(NSDictionary *)params 31b987b50fSBen Roth{ 32b987b50fSBen Roth if (self = [super initWithExperienceId:experienceId kernelServiceDelegate:kernelServiceInstance params:params]) { 33b987b50fSBen Roth _eventSuccessBlocks = [NSMutableDictionary dictionary]; 34b987b50fSBen Roth _eventFailureBlocks = [NSMutableDictionary dictionary]; 35c350fdbbSTomasz Sapeta _sdkVersions = params[@"constants"][@"supportedExpoSdks"]; 36b987b50fSBen Roth _delegate = kernelServiceInstance; 3724a0cefbSTomasz Sapeta 3824a0cefbSTomasz Sapeta // Register keyboard commands like Cmd+D for the simulator. 3924a0cefbSTomasz Sapeta [[EXKernelDevKeyCommands sharedInstance] registerDevCommands]; 40b987b50fSBen Roth } 41b987b50fSBen Roth return self; 42b987b50fSBen Roth} 43b987b50fSBen Roth 44b987b50fSBen Roth+ (BOOL)requiresMainQueueSetup 45b987b50fSBen Roth{ 46b987b50fSBen Roth return NO; 47b987b50fSBen Roth} 48b987b50fSBen Roth 49b987b50fSBen Roth- (NSDictionary *)constantsToExport 50b987b50fSBen Roth{ 51ec45106eSQuinlan Jung return @{ @"sdkVersions": _sdkVersions, 5272d142a5SStanisław Chmiela @"IOSClientReleaseType": [EXClientReleaseType clientReleaseType] }; 53b987b50fSBen Roth} 54b987b50fSBen Roth 55b987b50fSBen Roth#pragma mark - RCTEventEmitter methods 56b987b50fSBen Roth 57b987b50fSBen Roth- (NSArray<NSString *> *)supportedEvents 58b987b50fSBen Roth{ 59b987b50fSBen Roth return @[]; 60b987b50fSBen Roth} 61b987b50fSBen Roth 62b987b50fSBen Roth/** 63b987b50fSBen Roth * Override this method to avoid the [self supportedEvents] validation 64b987b50fSBen Roth */ 65b987b50fSBen Roth- (void)sendEventWithName:(NSString *)eventName body:(id)body 66b987b50fSBen Roth{ 67b987b50fSBen Roth // Note that this could be a versioned bridge! 68b987b50fSBen Roth [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" 69b987b50fSBen Roth args:body ? @[eventName, body] : @[eventName]]; 70b987b50fSBen Roth} 71b987b50fSBen Roth 72b987b50fSBen Roth#pragma mark - 73b987b50fSBen Roth 74b987b50fSBen Roth- (void)dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^)(NSDictionary *))success onFailure:(void (^)(NSString *))failure 75b987b50fSBen Roth{ 76b987b50fSBen Roth NSString *qualifiedEventName = [NSString stringWithFormat:@"ExponentKernel.%@", eventName]; 77b987b50fSBen Roth NSMutableDictionary *qualifiedEventBody = (eventBody) ? [eventBody mutableCopy] : [NSMutableDictionary dictionary]; 78b987b50fSBen Roth 79b987b50fSBen Roth if (success && failure) { 80b987b50fSBen Roth NSString *eventId = [[NSUUID UUID] UUIDString]; 81b987b50fSBen Roth [_eventSuccessBlocks setObject:success forKey:eventId]; 82b987b50fSBen Roth [_eventFailureBlocks setObject:failure forKey:eventId]; 83b987b50fSBen Roth [qualifiedEventBody setObject:eventId forKey:@"eventId"]; 84b987b50fSBen Roth } 85b987b50fSBen Roth 86b987b50fSBen Roth [self sendEventWithName:qualifiedEventName body:qualifiedEventBody]; 87b987b50fSBen Roth} 88b987b50fSBen Roth 89b987b50fSBen Roth/** 9024a0cefbSTomasz Sapeta * Requests JavaScript side to start closing the dev menu (start the animation or so). 9124a0cefbSTomasz Sapeta * Fully closes the dev menu once it receives a response from that event. 9224a0cefbSTomasz Sapeta */ 9324a0cefbSTomasz Sapeta- (void)requestToCloseDevMenu 9424a0cefbSTomasz Sapeta{ 95*05aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 96f67462bcSTomasz Sapeta void (^callback)(id) = ^(id arg){ 97f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] closeWithoutAnimation]; 9824a0cefbSTomasz Sapeta }; 99f67462bcSTomasz Sapeta [self dispatchJSEvent:@"requestToCloseDevMenu" body:nil onSuccess:callback onFailure:callback]; 100*05aa4e6cSTomasz Sapeta#endif 10124a0cefbSTomasz Sapeta} 10224a0cefbSTomasz Sapeta 10324a0cefbSTomasz Sapeta/** 104b987b50fSBen Roth * Duplicates Linking.openURL but does not validate that this is an exponent URL; 105b987b50fSBen Roth * in other words, we just take your word for it and never hand it off to iOS. 106b987b50fSBen Roth * Used by the home screen URL bar. 107b987b50fSBen Roth */ 108b987b50fSBen RothRCT_EXPORT_METHOD(openURL:(NSURL *)URL 109b987b50fSBen Roth resolve:(RCTPromiseResolveBlock)resolve 110b987b50fSBen Roth reject:(__unused RCTPromiseRejectBlock)reject) 111b987b50fSBen Roth{ 112b987b50fSBen Roth if (URL) { 113b987b50fSBen Roth [_delegate homeModule:self didOpenUrl:URL.absoluteString]; 114b987b50fSBen Roth resolve(@YES); 115b987b50fSBen Roth } else { 116b987b50fSBen Roth NSError *err = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot open a nil url" }]; 117b987b50fSBen Roth reject(@"E_INVALID_URL", err.localizedDescription, err); 118b987b50fSBen Roth } 119b987b50fSBen Roth} 120b987b50fSBen Roth 12124a0cefbSTomasz Sapeta/** 12224a0cefbSTomasz Sapeta * Returns boolean value determining whether the current app supports developer tools. 12324a0cefbSTomasz Sapeta */ 12424a0cefbSTomasz SapetaRCT_REMAP_METHOD(doesCurrentTaskEnableDevtoolsAsync, 125b987b50fSBen Roth doesCurrentTaskEnableDevtoolsWithResolver:(RCTPromiseResolveBlock)resolve 126b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 127b987b50fSBen Roth{ 128b987b50fSBen Roth if (_delegate) { 129b987b50fSBen Roth resolve(@([_delegate homeModuleShouldEnableDevtools:self])); 130b987b50fSBen Roth } else { 131b987b50fSBen Roth // don't reject, just disable devtools 132b987b50fSBen Roth resolve(@NO); 133b987b50fSBen Roth } 134b987b50fSBen Roth} 135b987b50fSBen Roth 13624a0cefbSTomasz Sapeta/** 13724a0cefbSTomasz Sapeta * Gets a dictionary of dev menu options available in the currently shown experience, 13824a0cefbSTomasz Sapeta * If the experience doesn't support developer tools just returns an empty response. 13924a0cefbSTomasz Sapeta */ 14024a0cefbSTomasz SapetaRCT_REMAP_METHOD(getDevMenuItemsToShowAsync, 141b987b50fSBen Roth getDevMenuItemsToShowWithResolver:(RCTPromiseResolveBlock)resolve 142b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 143b987b50fSBen Roth{ 144b987b50fSBen Roth if (_delegate && [_delegate homeModuleShouldEnableDevtools:self]) { 145b987b50fSBen Roth resolve([_delegate devMenuItemsForHomeModule:self]); 146b987b50fSBen Roth } else { 147b987b50fSBen Roth // don't reject, just show no devtools 148b987b50fSBen Roth resolve(@{}); 149b987b50fSBen Roth } 150b987b50fSBen Roth} 151b987b50fSBen Roth 15224a0cefbSTomasz Sapeta/** 15324a0cefbSTomasz Sapeta * Function called every time the dev menu option is selected. 15424a0cefbSTomasz Sapeta */ 15524a0cefbSTomasz SapetaRCT_EXPORT_METHOD(selectDevMenuItemWithKeyAsync:(NSString *)key) 156b987b50fSBen Roth{ 157b987b50fSBen Roth if (_delegate) { 158b987b50fSBen Roth [_delegate homeModule:self didSelectDevMenuItemWithKey:key]; 159b987b50fSBen Roth } 160b987b50fSBen Roth} 161b987b50fSBen Roth 16224a0cefbSTomasz Sapeta/** 16324a0cefbSTomasz Sapeta * Reloads currently shown app with the manifest. 16424a0cefbSTomasz Sapeta */ 16524a0cefbSTomasz SapetaRCT_EXPORT_METHOD(reloadAppAsync) 166b987b50fSBen Roth{ 167b987b50fSBen Roth if (_delegate) { 168b987b50fSBen Roth [_delegate homeModuleDidSelectRefresh:self]; 169b987b50fSBen Roth } 170b987b50fSBen Roth} 171b987b50fSBen Roth 17224a0cefbSTomasz Sapeta/** 17324a0cefbSTomasz Sapeta * Immediately closes the dev menu if it's visible. 17424a0cefbSTomasz Sapeta * Note: It skips the animation that would have been applied by the JS side. 17524a0cefbSTomasz Sapeta */ 17624a0cefbSTomasz SapetaRCT_EXPORT_METHOD(closeDevMenuAsync) 177b987b50fSBen Roth{ 178*05aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 179f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] closeWithoutAnimation]; 180*05aa4e6cSTomasz Sapeta#endif 181b987b50fSBen Roth} 182b987b50fSBen Roth 18324a0cefbSTomasz Sapeta/** 18424a0cefbSTomasz Sapeta * Goes back to the home app. 18524a0cefbSTomasz Sapeta */ 18624a0cefbSTomasz SapetaRCT_EXPORT_METHOD(goToHomeAsync) 187b987b50fSBen Roth{ 188b987b50fSBen Roth if (_delegate) { 189b987b50fSBen Roth [_delegate homeModuleDidSelectGoToHome:self]; 190b987b50fSBen Roth } 191b987b50fSBen Roth} 192b987b50fSBen Roth 19324a0cefbSTomasz Sapeta/** 19424a0cefbSTomasz Sapeta * Opens QR scanner to open another app by scanning its QR code. 19524a0cefbSTomasz Sapeta */ 1962fcba380SBen RothRCT_EXPORT_METHOD(selectQRReader) 1972fcba380SBen Roth{ 1982fcba380SBen Roth if (_delegate) { 1992fcba380SBen Roth [_delegate homeModuleDidSelectQRReader:self]; 2002fcba380SBen Roth } 2012fcba380SBen Roth} 2022fcba380SBen Roth 203f67462bcSTomasz SapetaRCT_REMAP_METHOD(getDevMenuSettingsAsync, 204f67462bcSTomasz Sapeta getDevMenuSettingsAsync:(RCTPromiseResolveBlock)resolve 205f67462bcSTomasz Sapeta rejecter:(RCTPromiseRejectBlock)reject) 206f67462bcSTomasz Sapeta{ 207*05aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 208f67462bcSTomasz Sapeta EXDevMenuManager *manager = [EXDevMenuManager sharedInstance]; 209f67462bcSTomasz Sapeta 210f67462bcSTomasz Sapeta resolve(@{ 211f67462bcSTomasz Sapeta @"motionGestureEnabled": @(manager.interceptMotionGesture), 212f67462bcSTomasz Sapeta @"touchGestureEnabled": @(manager.interceptTouchGesture), 213f67462bcSTomasz Sapeta }); 214*05aa4e6cSTomasz Sapeta#else 215*05aa4e6cSTomasz Sapeta resolve(@{}); 216*05aa4e6cSTomasz Sapeta#endif 217f67462bcSTomasz Sapeta} 218f67462bcSTomasz Sapeta 219f67462bcSTomasz SapetaRCT_REMAP_METHOD(setDevMenuSettingAsync, 220f67462bcSTomasz Sapeta setDevMenuSetting:(NSString *)key 221f67462bcSTomasz Sapeta withValue:(id)value 222f67462bcSTomasz Sapeta resolver:(RCTPromiseResolveBlock)resolve 223f67462bcSTomasz Sapeta rejecter:(RCTPromiseRejectBlock)reject) 224f67462bcSTomasz Sapeta{ 225*05aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 226f67462bcSTomasz Sapeta EXDevMenuManager *manager = [EXDevMenuManager sharedInstance]; 227f67462bcSTomasz Sapeta 228f67462bcSTomasz Sapeta if ([key isEqualToString:@"motionGestureEnabled"]) { 229f67462bcSTomasz Sapeta manager.interceptMotionGesture = [value boolValue]; 230f67462bcSTomasz Sapeta } else if ([key isEqualToString:@"touchGestureEnabled"]) { 231f67462bcSTomasz Sapeta manager.interceptTouchGesture = [value boolValue]; 232f67462bcSTomasz Sapeta } else { 233f67462bcSTomasz Sapeta return reject(@"ERR_DEV_MENU_SETTING_NOT_EXISTS", @"Specified dev menu setting doesn't exist.", nil); 234f67462bcSTomasz Sapeta } 235*05aa4e6cSTomasz Sapeta#endif 236f67462bcSTomasz Sapeta resolve(nil); 237f67462bcSTomasz Sapeta} 238f67462bcSTomasz Sapeta 2398a20a963SEric SamelsonRCT_REMAP_METHOD(getSessionAsync, 2408a20a963SEric Samelson getSessionAsync:(RCTPromiseResolveBlock)resolve 2418a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2429a7f140aSEric Samelson{ 2438a20a963SEric Samelson NSDictionary *session = [[EXSession sharedInstance] session]; 2448a20a963SEric Samelson resolve(session); 2459a7f140aSEric Samelson} 2469a7f140aSEric Samelson 2478a20a963SEric SamelsonRCT_REMAP_METHOD(setSessionAsync, 2488a20a963SEric Samelson setSessionAsync:(NSDictionary *)session 2498a20a963SEric Samelson resolver:(RCTPromiseResolveBlock)resolve 2508a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2519a7f140aSEric Samelson{ 2528a20a963SEric Samelson NSError *error; 2538a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] saveSessionToKeychain:session error:&error]; 2548a20a963SEric Samelson if (success) { 2558a20a963SEric Samelson resolve(nil); 2568a20a963SEric Samelson } else { 2578a20a963SEric Samelson reject(@"ERR_SESSION_NOT_SAVED", @"Could not save session", error); 2588a20a963SEric Samelson } 2598a20a963SEric Samelson} 2608a20a963SEric Samelson 2618a20a963SEric SamelsonRCT_REMAP_METHOD(removeSessionAsync, 2628a20a963SEric Samelson removeSessionAsync:(RCTPromiseResolveBlock)resolve 2638a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2648a20a963SEric Samelson{ 2658a20a963SEric Samelson NSError *error; 2668a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] deleteSessionFromKeychainWithError:&error]; 2678a20a963SEric Samelson if (success) { 2688a20a963SEric Samelson resolve(nil); 2698a20a963SEric Samelson } else { 2708a20a963SEric Samelson reject(@"ERR_SESSION_NOT_REMOVED", @"Could not remove session", error); 2718a20a963SEric Samelson } 2729a7f140aSEric Samelson} 2739a7f140aSEric Samelson 27424a0cefbSTomasz Sapeta/** 27524a0cefbSTomasz Sapeta * Checks whether the dev menu onboarding is already finished. 27624a0cefbSTomasz Sapeta * Onboarding is a screen that shows the dev menu to the user that opens any experience for the first time. 27724a0cefbSTomasz Sapeta*/ 27824a0cefbSTomasz SapetaRCT_REMAP_METHOD(getIsOnboardingFinishedAsync, 27924a0cefbSTomasz Sapeta getIsOnboardingFinishedWithResolver:(RCTPromiseResolveBlock)resolve 28066169681SBen Roth rejecter:(RCTPromiseRejectBlock)reject) 281b987b50fSBen Roth{ 28266169681SBen Roth if (_delegate) { 28366169681SBen Roth BOOL isFinished = [_delegate homeModuleShouldFinishNux:self]; 28466169681SBen Roth resolve(@(isFinished)); 28566169681SBen Roth } else { 28666169681SBen Roth resolve(@(NO)); 28766169681SBen Roth } 28866169681SBen Roth} 28966169681SBen Roth 29024a0cefbSTomasz Sapeta/** 29124a0cefbSTomasz Sapeta * Sets appropriate setting in user defaults that user's onboarding has finished. 29224a0cefbSTomasz Sapeta */ 29324a0cefbSTomasz SapetaRCT_REMAP_METHOD(setIsOnboardingFinishedAsync, 29424a0cefbSTomasz Sapeta setIsOnboardingFinished:(BOOL)isOnboardingFinished) 29566169681SBen Roth{ 29666169681SBen Roth if (_delegate) { 29724a0cefbSTomasz Sapeta [_delegate homeModule:self didFinishNux:isOnboardingFinished]; 29866169681SBen Roth } 299b987b50fSBen Roth} 300b987b50fSBen Roth 30124a0cefbSTomasz Sapeta/** 30224a0cefbSTomasz Sapeta * Called when the native event has succeeded on the JS side. 30324a0cefbSTomasz Sapeta */ 304b987b50fSBen RothRCT_REMAP_METHOD(onEventSuccess, 305b987b50fSBen Roth eventId:(NSString *)eventId 306b987b50fSBen Roth body:(NSDictionary *)body) 307b987b50fSBen Roth{ 308b987b50fSBen Roth void (^success)(NSDictionary *) = [_eventSuccessBlocks objectForKey:eventId]; 309b987b50fSBen Roth if (success) { 310b987b50fSBen Roth success(body); 311b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 312b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 313b987b50fSBen Roth } 314b987b50fSBen Roth} 315b987b50fSBen Roth 31624a0cefbSTomasz Sapeta/** 31724a0cefbSTomasz Sapeta * Called when the native event has failed on the JS side. 31824a0cefbSTomasz Sapeta */ 319b987b50fSBen RothRCT_REMAP_METHOD(onEventFailure, 320b987b50fSBen Roth eventId:(NSString *)eventId 321b987b50fSBen Roth message:(NSString *)message) 322b987b50fSBen Roth{ 323b987b50fSBen Roth void (^failure)(NSString *) = [_eventFailureBlocks objectForKey:eventId]; 324b987b50fSBen Roth if (failure) { 325b987b50fSBen Roth failure(message); 326b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 327b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 328b987b50fSBen Roth } 329b987b50fSBen Roth} 330b987b50fSBen Roth 331b987b50fSBen Roth@end 332