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*f67462bcSTomasz Sapeta#import "EXDevMenuManager.h" 10b987b50fSBen Roth 11b987b50fSBen Roth#import <React/RCTEventDispatcher.h> 12b987b50fSBen Roth 13b987b50fSBen Roth@interface EXHomeModule () 14b987b50fSBen Roth 15b987b50fSBen Roth@property (nonatomic, assign) BOOL hasListeners; 16b987b50fSBen Roth@property (nonatomic, strong) NSMutableDictionary *eventSuccessBlocks; 17b987b50fSBen Roth@property (nonatomic, strong) NSMutableDictionary *eventFailureBlocks; 18b987b50fSBen Roth@property (nonatomic, strong) NSArray * _Nonnull sdkVersions; 19b987b50fSBen Roth@property (nonatomic, weak) id<EXHomeModuleDelegate> delegate; 20b987b50fSBen Roth 21b987b50fSBen Roth@end 22b987b50fSBen Roth 23b987b50fSBen Roth@implementation EXHomeModule 24b987b50fSBen Roth 25b987b50fSBen Roth+ (NSString *)moduleName { return @"ExponentKernel"; } 26b987b50fSBen Roth 27b987b50fSBen Roth- (instancetype)initWithExperienceId:(NSString *)experienceId kernelServiceDelegate:(id)kernelServiceInstance params:(NSDictionary *)params 28b987b50fSBen Roth{ 29b987b50fSBen Roth if (self = [super initWithExperienceId:experienceId kernelServiceDelegate:kernelServiceInstance params:params]) { 30b987b50fSBen Roth _eventSuccessBlocks = [NSMutableDictionary dictionary]; 31b987b50fSBen Roth _eventFailureBlocks = [NSMutableDictionary dictionary]; 32c350fdbbSTomasz Sapeta _sdkVersions = params[@"constants"][@"supportedExpoSdks"]; 33b987b50fSBen Roth _delegate = kernelServiceInstance; 3424a0cefbSTomasz Sapeta 3524a0cefbSTomasz Sapeta // Register keyboard commands like Cmd+D for the simulator. 3624a0cefbSTomasz Sapeta [[EXKernelDevKeyCommands sharedInstance] registerDevCommands]; 37b987b50fSBen Roth } 38b987b50fSBen Roth return self; 39b987b50fSBen Roth} 40b987b50fSBen Roth 41b987b50fSBen Roth+ (BOOL)requiresMainQueueSetup 42b987b50fSBen Roth{ 43b987b50fSBen Roth return NO; 44b987b50fSBen Roth} 45b987b50fSBen Roth 46b987b50fSBen Roth- (NSDictionary *)constantsToExport 47b987b50fSBen Roth{ 48ec45106eSQuinlan Jung return @{ @"sdkVersions": _sdkVersions, 4972d142a5SStanisław Chmiela @"IOSClientReleaseType": [EXClientReleaseType clientReleaseType] }; 50b987b50fSBen Roth} 51b987b50fSBen Roth 52b987b50fSBen Roth#pragma mark - RCTEventEmitter methods 53b987b50fSBen Roth 54b987b50fSBen Roth- (NSArray<NSString *> *)supportedEvents 55b987b50fSBen Roth{ 56b987b50fSBen Roth return @[]; 57b987b50fSBen Roth} 58b987b50fSBen Roth 59b987b50fSBen Roth/** 60b987b50fSBen Roth * Override this method to avoid the [self supportedEvents] validation 61b987b50fSBen Roth */ 62b987b50fSBen Roth- (void)sendEventWithName:(NSString *)eventName body:(id)body 63b987b50fSBen Roth{ 64b987b50fSBen Roth // Note that this could be a versioned bridge! 65b987b50fSBen Roth [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" 66b987b50fSBen Roth args:body ? @[eventName, body] : @[eventName]]; 67b987b50fSBen Roth} 68b987b50fSBen Roth 69b987b50fSBen Roth#pragma mark - 70b987b50fSBen Roth 71b987b50fSBen Roth- (void)dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^)(NSDictionary *))success onFailure:(void (^)(NSString *))failure 72b987b50fSBen Roth{ 73b987b50fSBen Roth NSString *qualifiedEventName = [NSString stringWithFormat:@"ExponentKernel.%@", eventName]; 74b987b50fSBen Roth NSMutableDictionary *qualifiedEventBody = (eventBody) ? [eventBody mutableCopy] : [NSMutableDictionary dictionary]; 75b987b50fSBen Roth 76b987b50fSBen Roth if (success && failure) { 77b987b50fSBen Roth NSString *eventId = [[NSUUID UUID] UUIDString]; 78b987b50fSBen Roth [_eventSuccessBlocks setObject:success forKey:eventId]; 79b987b50fSBen Roth [_eventFailureBlocks setObject:failure forKey:eventId]; 80b987b50fSBen Roth [qualifiedEventBody setObject:eventId forKey:@"eventId"]; 81b987b50fSBen Roth } 82b987b50fSBen Roth 83b987b50fSBen Roth [self sendEventWithName:qualifiedEventName body:qualifiedEventBody]; 84b987b50fSBen Roth} 85b987b50fSBen Roth 86b987b50fSBen Roth/** 8724a0cefbSTomasz Sapeta * Requests JavaScript side to start closing the dev menu (start the animation or so). 8824a0cefbSTomasz Sapeta * Fully closes the dev menu once it receives a response from that event. 8924a0cefbSTomasz Sapeta */ 9024a0cefbSTomasz Sapeta- (void)requestToCloseDevMenu 9124a0cefbSTomasz Sapeta{ 92*f67462bcSTomasz Sapeta void (^callback)(id) = ^(id arg){ 93*f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] closeWithoutAnimation]; 9424a0cefbSTomasz Sapeta }; 95*f67462bcSTomasz Sapeta [self dispatchJSEvent:@"requestToCloseDevMenu" body:nil onSuccess:callback onFailure:callback]; 9624a0cefbSTomasz Sapeta} 9724a0cefbSTomasz Sapeta 9824a0cefbSTomasz Sapeta/** 99b987b50fSBen Roth * Duplicates Linking.openURL but does not validate that this is an exponent URL; 100b987b50fSBen Roth * in other words, we just take your word for it and never hand it off to iOS. 101b987b50fSBen Roth * Used by the home screen URL bar. 102b987b50fSBen Roth */ 103b987b50fSBen RothRCT_EXPORT_METHOD(openURL:(NSURL *)URL 104b987b50fSBen Roth resolve:(RCTPromiseResolveBlock)resolve 105b987b50fSBen Roth reject:(__unused RCTPromiseRejectBlock)reject) 106b987b50fSBen Roth{ 107b987b50fSBen Roth if (URL) { 108b987b50fSBen Roth [_delegate homeModule:self didOpenUrl:URL.absoluteString]; 109b987b50fSBen Roth resolve(@YES); 110b987b50fSBen Roth } else { 111b987b50fSBen Roth NSError *err = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot open a nil url" }]; 112b987b50fSBen Roth reject(@"E_INVALID_URL", err.localizedDescription, err); 113b987b50fSBen Roth } 114b987b50fSBen Roth} 115b987b50fSBen Roth 11624a0cefbSTomasz Sapeta/** 11724a0cefbSTomasz Sapeta * Returns boolean value determining whether the current app supports developer tools. 11824a0cefbSTomasz Sapeta */ 11924a0cefbSTomasz SapetaRCT_REMAP_METHOD(doesCurrentTaskEnableDevtoolsAsync, 120b987b50fSBen Roth doesCurrentTaskEnableDevtoolsWithResolver:(RCTPromiseResolveBlock)resolve 121b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 122b987b50fSBen Roth{ 123b987b50fSBen Roth if (_delegate) { 124b987b50fSBen Roth resolve(@([_delegate homeModuleShouldEnableDevtools:self])); 125b987b50fSBen Roth } else { 126b987b50fSBen Roth // don't reject, just disable devtools 127b987b50fSBen Roth resolve(@NO); 128b987b50fSBen Roth } 129b987b50fSBen Roth} 130b987b50fSBen Roth 13124a0cefbSTomasz Sapeta/** 13224a0cefbSTomasz Sapeta * Gets a dictionary of dev menu options available in the currently shown experience, 13324a0cefbSTomasz Sapeta * If the experience doesn't support developer tools just returns an empty response. 13424a0cefbSTomasz Sapeta */ 13524a0cefbSTomasz SapetaRCT_REMAP_METHOD(getDevMenuItemsToShowAsync, 136b987b50fSBen Roth getDevMenuItemsToShowWithResolver:(RCTPromiseResolveBlock)resolve 137b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 138b987b50fSBen Roth{ 139b987b50fSBen Roth if (_delegate && [_delegate homeModuleShouldEnableDevtools:self]) { 140b987b50fSBen Roth resolve([_delegate devMenuItemsForHomeModule:self]); 141b987b50fSBen Roth } else { 142b987b50fSBen Roth // don't reject, just show no devtools 143b987b50fSBen Roth resolve(@{}); 144b987b50fSBen Roth } 145b987b50fSBen Roth} 146b987b50fSBen Roth 14724a0cefbSTomasz Sapeta/** 14824a0cefbSTomasz Sapeta * Function called every time the dev menu option is selected. 14924a0cefbSTomasz Sapeta */ 15024a0cefbSTomasz SapetaRCT_EXPORT_METHOD(selectDevMenuItemWithKeyAsync:(NSString *)key) 151b987b50fSBen Roth{ 152b987b50fSBen Roth if (_delegate) { 153b987b50fSBen Roth [_delegate homeModule:self didSelectDevMenuItemWithKey:key]; 154b987b50fSBen Roth } 155b987b50fSBen Roth} 156b987b50fSBen Roth 15724a0cefbSTomasz Sapeta/** 15824a0cefbSTomasz Sapeta * Reloads currently shown app with the manifest. 15924a0cefbSTomasz Sapeta */ 16024a0cefbSTomasz SapetaRCT_EXPORT_METHOD(reloadAppAsync) 161b987b50fSBen Roth{ 162b987b50fSBen Roth if (_delegate) { 163b987b50fSBen Roth [_delegate homeModuleDidSelectRefresh:self]; 164b987b50fSBen Roth } 165b987b50fSBen Roth} 166b987b50fSBen Roth 16724a0cefbSTomasz Sapeta/** 16824a0cefbSTomasz Sapeta * Immediately closes the dev menu if it's visible. 16924a0cefbSTomasz Sapeta * Note: It skips the animation that would have been applied by the JS side. 17024a0cefbSTomasz Sapeta */ 17124a0cefbSTomasz SapetaRCT_EXPORT_METHOD(closeDevMenuAsync) 172b987b50fSBen Roth{ 173*f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] closeWithoutAnimation]; 174b987b50fSBen Roth} 175b987b50fSBen Roth 17624a0cefbSTomasz Sapeta/** 17724a0cefbSTomasz Sapeta * Goes back to the home app. 17824a0cefbSTomasz Sapeta */ 17924a0cefbSTomasz SapetaRCT_EXPORT_METHOD(goToHomeAsync) 180b987b50fSBen Roth{ 181b987b50fSBen Roth if (_delegate) { 182b987b50fSBen Roth [_delegate homeModuleDidSelectGoToHome:self]; 183b987b50fSBen Roth } 184b987b50fSBen Roth} 185b987b50fSBen Roth 18624a0cefbSTomasz Sapeta/** 18724a0cefbSTomasz Sapeta * Opens QR scanner to open another app by scanning its QR code. 18824a0cefbSTomasz Sapeta */ 1892fcba380SBen RothRCT_EXPORT_METHOD(selectQRReader) 1902fcba380SBen Roth{ 1912fcba380SBen Roth if (_delegate) { 1922fcba380SBen Roth [_delegate homeModuleDidSelectQRReader:self]; 1932fcba380SBen Roth } 1942fcba380SBen Roth} 1952fcba380SBen Roth 196*f67462bcSTomasz SapetaRCT_REMAP_METHOD(getDevMenuSettingsAsync, 197*f67462bcSTomasz Sapeta getDevMenuSettingsAsync:(RCTPromiseResolveBlock)resolve 198*f67462bcSTomasz Sapeta rejecter:(RCTPromiseRejectBlock)reject) 199*f67462bcSTomasz Sapeta{ 200*f67462bcSTomasz Sapeta EXDevMenuManager *manager = [EXDevMenuManager sharedInstance]; 201*f67462bcSTomasz Sapeta 202*f67462bcSTomasz Sapeta resolve(@{ 203*f67462bcSTomasz Sapeta @"motionGestureEnabled": @(manager.interceptMotionGesture), 204*f67462bcSTomasz Sapeta @"touchGestureEnabled": @(manager.interceptTouchGesture), 205*f67462bcSTomasz Sapeta }); 206*f67462bcSTomasz Sapeta} 207*f67462bcSTomasz Sapeta 208*f67462bcSTomasz SapetaRCT_REMAP_METHOD(setDevMenuSettingAsync, 209*f67462bcSTomasz Sapeta setDevMenuSetting:(NSString *)key 210*f67462bcSTomasz Sapeta withValue:(id)value 211*f67462bcSTomasz Sapeta resolver:(RCTPromiseResolveBlock)resolve 212*f67462bcSTomasz Sapeta rejecter:(RCTPromiseRejectBlock)reject) 213*f67462bcSTomasz Sapeta{ 214*f67462bcSTomasz Sapeta EXDevMenuManager *manager = [EXDevMenuManager sharedInstance]; 215*f67462bcSTomasz Sapeta 216*f67462bcSTomasz Sapeta if ([key isEqualToString:@"motionGestureEnabled"]) { 217*f67462bcSTomasz Sapeta manager.interceptMotionGesture = [value boolValue]; 218*f67462bcSTomasz Sapeta } else if ([key isEqualToString:@"touchGestureEnabled"]) { 219*f67462bcSTomasz Sapeta manager.interceptTouchGesture = [value boolValue]; 220*f67462bcSTomasz Sapeta } else { 221*f67462bcSTomasz Sapeta return reject(@"ERR_DEV_MENU_SETTING_NOT_EXISTS", @"Specified dev menu setting doesn't exist.", nil); 222*f67462bcSTomasz Sapeta } 223*f67462bcSTomasz Sapeta resolve(nil); 224*f67462bcSTomasz Sapeta} 225*f67462bcSTomasz Sapeta 2268a20a963SEric SamelsonRCT_REMAP_METHOD(getSessionAsync, 2278a20a963SEric Samelson getSessionAsync:(RCTPromiseResolveBlock)resolve 2288a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2299a7f140aSEric Samelson{ 2308a20a963SEric Samelson NSDictionary *session = [[EXSession sharedInstance] session]; 2318a20a963SEric Samelson resolve(session); 2329a7f140aSEric Samelson} 2339a7f140aSEric Samelson 2348a20a963SEric SamelsonRCT_REMAP_METHOD(setSessionAsync, 2358a20a963SEric Samelson setSessionAsync:(NSDictionary *)session 2368a20a963SEric Samelson resolver:(RCTPromiseResolveBlock)resolve 2378a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2389a7f140aSEric Samelson{ 2398a20a963SEric Samelson NSError *error; 2408a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] saveSessionToKeychain:session error:&error]; 2418a20a963SEric Samelson if (success) { 2428a20a963SEric Samelson resolve(nil); 2438a20a963SEric Samelson } else { 2448a20a963SEric Samelson reject(@"ERR_SESSION_NOT_SAVED", @"Could not save session", error); 2458a20a963SEric Samelson } 2468a20a963SEric Samelson} 2478a20a963SEric Samelson 2488a20a963SEric SamelsonRCT_REMAP_METHOD(removeSessionAsync, 2498a20a963SEric Samelson removeSessionAsync:(RCTPromiseResolveBlock)resolve 2508a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2518a20a963SEric Samelson{ 2528a20a963SEric Samelson NSError *error; 2538a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] deleteSessionFromKeychainWithError:&error]; 2548a20a963SEric Samelson if (success) { 2558a20a963SEric Samelson resolve(nil); 2568a20a963SEric Samelson } else { 2578a20a963SEric Samelson reject(@"ERR_SESSION_NOT_REMOVED", @"Could not remove session", error); 2588a20a963SEric Samelson } 2599a7f140aSEric Samelson} 2609a7f140aSEric Samelson 26124a0cefbSTomasz Sapeta/** 26224a0cefbSTomasz Sapeta * Checks whether the dev menu onboarding is already finished. 26324a0cefbSTomasz Sapeta * Onboarding is a screen that shows the dev menu to the user that opens any experience for the first time. 26424a0cefbSTomasz Sapeta*/ 26524a0cefbSTomasz SapetaRCT_REMAP_METHOD(getIsOnboardingFinishedAsync, 26624a0cefbSTomasz Sapeta getIsOnboardingFinishedWithResolver:(RCTPromiseResolveBlock)resolve 26766169681SBen Roth rejecter:(RCTPromiseRejectBlock)reject) 268b987b50fSBen Roth{ 26966169681SBen Roth if (_delegate) { 27066169681SBen Roth BOOL isFinished = [_delegate homeModuleShouldFinishNux:self]; 27166169681SBen Roth resolve(@(isFinished)); 27266169681SBen Roth } else { 27366169681SBen Roth resolve(@(NO)); 27466169681SBen Roth } 27566169681SBen Roth} 27666169681SBen Roth 27724a0cefbSTomasz Sapeta/** 27824a0cefbSTomasz Sapeta * Sets appropriate setting in user defaults that user's onboarding has finished. 27924a0cefbSTomasz Sapeta */ 28024a0cefbSTomasz SapetaRCT_REMAP_METHOD(setIsOnboardingFinishedAsync, 28124a0cefbSTomasz Sapeta setIsOnboardingFinished:(BOOL)isOnboardingFinished) 28266169681SBen Roth{ 28366169681SBen Roth if (_delegate) { 28424a0cefbSTomasz Sapeta [_delegate homeModule:self didFinishNux:isOnboardingFinished]; 28566169681SBen Roth } 286b987b50fSBen Roth} 287b987b50fSBen Roth 28824a0cefbSTomasz Sapeta/** 28924a0cefbSTomasz Sapeta * Called when the native event has succeeded on the JS side. 29024a0cefbSTomasz Sapeta */ 291b987b50fSBen RothRCT_REMAP_METHOD(onEventSuccess, 292b987b50fSBen Roth eventId:(NSString *)eventId 293b987b50fSBen Roth body:(NSDictionary *)body) 294b987b50fSBen Roth{ 295b987b50fSBen Roth void (^success)(NSDictionary *) = [_eventSuccessBlocks objectForKey:eventId]; 296b987b50fSBen Roth if (success) { 297b987b50fSBen Roth success(body); 298b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 299b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 300b987b50fSBen Roth } 301b987b50fSBen Roth} 302b987b50fSBen Roth 30324a0cefbSTomasz Sapeta/** 30424a0cefbSTomasz Sapeta * Called when the native event has failed on the JS side. 30524a0cefbSTomasz Sapeta */ 306b987b50fSBen RothRCT_REMAP_METHOD(onEventFailure, 307b987b50fSBen Roth eventId:(NSString *)eventId 308b987b50fSBen Roth message:(NSString *)message) 309b987b50fSBen Roth{ 310b987b50fSBen Roth void (^failure)(NSString *) = [_eventFailureBlocks objectForKey:eventId]; 311b987b50fSBen Roth if (failure) { 312b987b50fSBen Roth failure(message); 313b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 314b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 315b987b50fSBen Roth } 316b987b50fSBen Roth} 317b987b50fSBen Roth 318b987b50fSBen Roth@end 319