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" 905aa4e6cSTomasz Sapeta 1005aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 11f67462bcSTomasz Sapeta#import "EXDevMenuManager.h" 1205aa4e6cSTomasz 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 30*5db43c74SWill Schurman- (instancetype)initWithExperienceStableLegacyId:(NSString *)experienceStableLegacyId 31*5db43c74SWill Schurman scopeKey:(NSString *)scopeKey 32*5db43c74SWill Schurman easProjectId:(NSString *)easProjectId 33*5db43c74SWill Schurman kernelServiceDelegate:(id)kernelServiceInstance 34*5db43c74SWill Schurman params:(NSDictionary *)params { 35*5db43c74SWill Schurman if (self = [super initWithExperienceStableLegacyId:experienceStableLegacyId 36*5db43c74SWill Schurman scopeKey:scopeKey 37*5db43c74SWill Schurman easProjectId:easProjectId 38*5db43c74SWill Schurman kernelServiceDelegates:kernelServiceInstance 39*5db43c74SWill Schurman params:params]) { 40b987b50fSBen Roth _eventSuccessBlocks = [NSMutableDictionary dictionary]; 41b987b50fSBen Roth _eventFailureBlocks = [NSMutableDictionary dictionary]; 42c350fdbbSTomasz Sapeta _sdkVersions = params[@"constants"][@"supportedExpoSdks"]; 43b987b50fSBen Roth _delegate = kernelServiceInstance; 4424a0cefbSTomasz Sapeta 4524a0cefbSTomasz Sapeta // Register keyboard commands like Cmd+D for the simulator. 4624a0cefbSTomasz Sapeta [[EXKernelDevKeyCommands sharedInstance] registerDevCommands]; 47b987b50fSBen Roth } 48b987b50fSBen Roth return self; 49b987b50fSBen Roth} 50b987b50fSBen Roth 51b987b50fSBen Roth+ (BOOL)requiresMainQueueSetup 52b987b50fSBen Roth{ 53b987b50fSBen Roth return NO; 54b987b50fSBen Roth} 55b987b50fSBen Roth 56b987b50fSBen Roth- (NSDictionary *)constantsToExport 57b987b50fSBen Roth{ 58ec45106eSQuinlan Jung return @{ @"sdkVersions": _sdkVersions, 5972d142a5SStanisław Chmiela @"IOSClientReleaseType": [EXClientReleaseType clientReleaseType] }; 60b987b50fSBen Roth} 61b987b50fSBen Roth 62b987b50fSBen Roth#pragma mark - RCTEventEmitter methods 63b987b50fSBen Roth 64b987b50fSBen Roth- (NSArray<NSString *> *)supportedEvents 65b987b50fSBen Roth{ 66b987b50fSBen Roth return @[]; 67b987b50fSBen Roth} 68b987b50fSBen Roth 69b987b50fSBen Roth/** 70b987b50fSBen Roth * Override this method to avoid the [self supportedEvents] validation 71b987b50fSBen Roth */ 72b987b50fSBen Roth- (void)sendEventWithName:(NSString *)eventName body:(id)body 73b987b50fSBen Roth{ 74b987b50fSBen Roth // Note that this could be a versioned bridge! 75b987b50fSBen Roth [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" 76b987b50fSBen Roth args:body ? @[eventName, body] : @[eventName]]; 77b987b50fSBen Roth} 78b987b50fSBen Roth 79b987b50fSBen Roth#pragma mark - 80b987b50fSBen Roth 81b987b50fSBen Roth- (void)dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^)(NSDictionary *))success onFailure:(void (^)(NSString *))failure 82b987b50fSBen Roth{ 83b987b50fSBen Roth NSString *qualifiedEventName = [NSString stringWithFormat:@"ExponentKernel.%@", eventName]; 84b987b50fSBen Roth NSMutableDictionary *qualifiedEventBody = (eventBody) ? [eventBody mutableCopy] : [NSMutableDictionary dictionary]; 85b987b50fSBen Roth 86b987b50fSBen Roth if (success && failure) { 87b987b50fSBen Roth NSString *eventId = [[NSUUID UUID] UUIDString]; 88b987b50fSBen Roth [_eventSuccessBlocks setObject:success forKey:eventId]; 89b987b50fSBen Roth [_eventFailureBlocks setObject:failure forKey:eventId]; 90b987b50fSBen Roth [qualifiedEventBody setObject:eventId forKey:@"eventId"]; 91b987b50fSBen Roth } 92b987b50fSBen Roth 93b987b50fSBen Roth [self sendEventWithName:qualifiedEventName body:qualifiedEventBody]; 94b987b50fSBen Roth} 95b987b50fSBen Roth 96b987b50fSBen Roth/** 9724a0cefbSTomasz Sapeta * Requests JavaScript side to start closing the dev menu (start the animation or so). 9824a0cefbSTomasz Sapeta * Fully closes the dev menu once it receives a response from that event. 9924a0cefbSTomasz Sapeta */ 10024a0cefbSTomasz Sapeta- (void)requestToCloseDevMenu 10124a0cefbSTomasz Sapeta{ 10205aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 103f67462bcSTomasz Sapeta void (^callback)(id) = ^(id arg){ 104f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] closeWithoutAnimation]; 10524a0cefbSTomasz Sapeta }; 106f67462bcSTomasz Sapeta [self dispatchJSEvent:@"requestToCloseDevMenu" body:nil onSuccess:callback onFailure:callback]; 10705aa4e6cSTomasz Sapeta#endif 10824a0cefbSTomasz Sapeta} 10924a0cefbSTomasz Sapeta 11024a0cefbSTomasz Sapeta/** 111b987b50fSBen Roth * Duplicates Linking.openURL but does not validate that this is an exponent URL; 112b987b50fSBen Roth * in other words, we just take your word for it and never hand it off to iOS. 113b987b50fSBen Roth * Used by the home screen URL bar. 114b987b50fSBen Roth */ 115b987b50fSBen RothRCT_EXPORT_METHOD(openURL:(NSURL *)URL 116b987b50fSBen Roth resolve:(RCTPromiseResolveBlock)resolve 117b987b50fSBen Roth reject:(__unused RCTPromiseRejectBlock)reject) 118b987b50fSBen Roth{ 119b987b50fSBen Roth if (URL) { 120b987b50fSBen Roth [_delegate homeModule:self didOpenUrl:URL.absoluteString]; 121b987b50fSBen Roth resolve(@YES); 122b987b50fSBen Roth } else { 123b987b50fSBen Roth NSError *err = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot open a nil url" }]; 124b987b50fSBen Roth reject(@"E_INVALID_URL", err.localizedDescription, err); 125b987b50fSBen Roth } 126b987b50fSBen Roth} 127b987b50fSBen Roth 12824a0cefbSTomasz Sapeta/** 12924a0cefbSTomasz Sapeta * Returns boolean value determining whether the current app supports developer tools. 13024a0cefbSTomasz Sapeta */ 13124a0cefbSTomasz SapetaRCT_REMAP_METHOD(doesCurrentTaskEnableDevtoolsAsync, 132b987b50fSBen Roth doesCurrentTaskEnableDevtoolsWithResolver:(RCTPromiseResolveBlock)resolve 133b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 134b987b50fSBen Roth{ 135b987b50fSBen Roth if (_delegate) { 136b987b50fSBen Roth resolve(@([_delegate homeModuleShouldEnableDevtools:self])); 137b987b50fSBen Roth } else { 138b987b50fSBen Roth // don't reject, just disable devtools 139b987b50fSBen Roth resolve(@NO); 140b987b50fSBen Roth } 141b987b50fSBen Roth} 142b987b50fSBen Roth 14324a0cefbSTomasz Sapeta/** 14424a0cefbSTomasz Sapeta * Gets a dictionary of dev menu options available in the currently shown experience, 14524a0cefbSTomasz Sapeta * If the experience doesn't support developer tools just returns an empty response. 14624a0cefbSTomasz Sapeta */ 14724a0cefbSTomasz SapetaRCT_REMAP_METHOD(getDevMenuItemsToShowAsync, 148b987b50fSBen Roth getDevMenuItemsToShowWithResolver:(RCTPromiseResolveBlock)resolve 149b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 150b987b50fSBen Roth{ 151b987b50fSBen Roth if (_delegate && [_delegate homeModuleShouldEnableDevtools:self]) { 152b987b50fSBen Roth resolve([_delegate devMenuItemsForHomeModule:self]); 153b987b50fSBen Roth } else { 154b987b50fSBen Roth // don't reject, just show no devtools 155b987b50fSBen Roth resolve(@{}); 156b987b50fSBen Roth } 157b987b50fSBen Roth} 158b987b50fSBen Roth 15924a0cefbSTomasz Sapeta/** 16024a0cefbSTomasz Sapeta * Function called every time the dev menu option is selected. 16124a0cefbSTomasz Sapeta */ 16224a0cefbSTomasz SapetaRCT_EXPORT_METHOD(selectDevMenuItemWithKeyAsync:(NSString *)key) 163b987b50fSBen Roth{ 164b987b50fSBen Roth if (_delegate) { 165b987b50fSBen Roth [_delegate homeModule:self didSelectDevMenuItemWithKey:key]; 166b987b50fSBen Roth } 167b987b50fSBen Roth} 168b987b50fSBen Roth 16924a0cefbSTomasz Sapeta/** 17024a0cefbSTomasz Sapeta * Reloads currently shown app with the manifest. 17124a0cefbSTomasz Sapeta */ 17224a0cefbSTomasz SapetaRCT_EXPORT_METHOD(reloadAppAsync) 173b987b50fSBen Roth{ 174b987b50fSBen Roth if (_delegate) { 175b987b50fSBen Roth [_delegate homeModuleDidSelectRefresh:self]; 176b987b50fSBen Roth } 177b987b50fSBen Roth} 178b987b50fSBen Roth 17924a0cefbSTomasz Sapeta/** 18024a0cefbSTomasz Sapeta * Immediately closes the dev menu if it's visible. 18124a0cefbSTomasz Sapeta * Note: It skips the animation that would have been applied by the JS side. 18224a0cefbSTomasz Sapeta */ 18324a0cefbSTomasz SapetaRCT_EXPORT_METHOD(closeDevMenuAsync) 184b987b50fSBen Roth{ 18505aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 186f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] closeWithoutAnimation]; 18705aa4e6cSTomasz Sapeta#endif 188b987b50fSBen Roth} 189b987b50fSBen Roth 19024a0cefbSTomasz Sapeta/** 19124a0cefbSTomasz Sapeta * Goes back to the home app. 19224a0cefbSTomasz Sapeta */ 19324a0cefbSTomasz SapetaRCT_EXPORT_METHOD(goToHomeAsync) 194b987b50fSBen Roth{ 195b987b50fSBen Roth if (_delegate) { 196b987b50fSBen Roth [_delegate homeModuleDidSelectGoToHome:self]; 197b987b50fSBen Roth } 198b987b50fSBen Roth} 199b987b50fSBen Roth 20024a0cefbSTomasz Sapeta/** 20124a0cefbSTomasz Sapeta * Opens QR scanner to open another app by scanning its QR code. 20224a0cefbSTomasz Sapeta */ 2032fcba380SBen RothRCT_EXPORT_METHOD(selectQRReader) 2042fcba380SBen Roth{ 2052fcba380SBen Roth if (_delegate) { 2062fcba380SBen Roth [_delegate homeModuleDidSelectQRReader:self]; 2072fcba380SBen Roth } 2082fcba380SBen Roth} 2092fcba380SBen Roth 210f67462bcSTomasz SapetaRCT_REMAP_METHOD(getDevMenuSettingsAsync, 211f67462bcSTomasz Sapeta getDevMenuSettingsAsync:(RCTPromiseResolveBlock)resolve 212f67462bcSTomasz Sapeta rejecter:(RCTPromiseRejectBlock)reject) 213f67462bcSTomasz Sapeta{ 21405aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 215f67462bcSTomasz Sapeta EXDevMenuManager *manager = [EXDevMenuManager sharedInstance]; 216f67462bcSTomasz Sapeta 217f67462bcSTomasz Sapeta resolve(@{ 218f67462bcSTomasz Sapeta @"motionGestureEnabled": @(manager.interceptMotionGesture), 219f67462bcSTomasz Sapeta @"touchGestureEnabled": @(manager.interceptTouchGesture), 220f67462bcSTomasz Sapeta }); 22105aa4e6cSTomasz Sapeta#else 22205aa4e6cSTomasz Sapeta resolve(@{}); 22305aa4e6cSTomasz Sapeta#endif 224f67462bcSTomasz Sapeta} 225f67462bcSTomasz Sapeta 226f67462bcSTomasz SapetaRCT_REMAP_METHOD(setDevMenuSettingAsync, 227f67462bcSTomasz Sapeta setDevMenuSetting:(NSString *)key 228f67462bcSTomasz Sapeta withValue:(id)value 229f67462bcSTomasz Sapeta resolver:(RCTPromiseResolveBlock)resolve 230f67462bcSTomasz Sapeta rejecter:(RCTPromiseRejectBlock)reject) 231f67462bcSTomasz Sapeta{ 23205aa4e6cSTomasz Sapeta#ifndef EX_DETACHED 233f67462bcSTomasz Sapeta EXDevMenuManager *manager = [EXDevMenuManager sharedInstance]; 234f67462bcSTomasz Sapeta 235f67462bcSTomasz Sapeta if ([key isEqualToString:@"motionGestureEnabled"]) { 236f67462bcSTomasz Sapeta manager.interceptMotionGesture = [value boolValue]; 237f67462bcSTomasz Sapeta } else if ([key isEqualToString:@"touchGestureEnabled"]) { 238f67462bcSTomasz Sapeta manager.interceptTouchGesture = [value boolValue]; 239f67462bcSTomasz Sapeta } else { 240f67462bcSTomasz Sapeta return reject(@"ERR_DEV_MENU_SETTING_NOT_EXISTS", @"Specified dev menu setting doesn't exist.", nil); 241f67462bcSTomasz Sapeta } 24205aa4e6cSTomasz Sapeta#endif 243f67462bcSTomasz Sapeta resolve(nil); 244f67462bcSTomasz Sapeta} 245f67462bcSTomasz Sapeta 2468a20a963SEric SamelsonRCT_REMAP_METHOD(getSessionAsync, 2478a20a963SEric Samelson getSessionAsync:(RCTPromiseResolveBlock)resolve 2488a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2499a7f140aSEric Samelson{ 2508a20a963SEric Samelson NSDictionary *session = [[EXSession sharedInstance] session]; 2518a20a963SEric Samelson resolve(session); 2529a7f140aSEric Samelson} 2539a7f140aSEric Samelson 2548a20a963SEric SamelsonRCT_REMAP_METHOD(setSessionAsync, 2558a20a963SEric Samelson setSessionAsync:(NSDictionary *)session 2568a20a963SEric Samelson resolver:(RCTPromiseResolveBlock)resolve 2578a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2589a7f140aSEric Samelson{ 2598a20a963SEric Samelson NSError *error; 2608a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] saveSessionToKeychain:session error:&error]; 2618a20a963SEric Samelson if (success) { 2628a20a963SEric Samelson resolve(nil); 2638a20a963SEric Samelson } else { 2648a20a963SEric Samelson reject(@"ERR_SESSION_NOT_SAVED", @"Could not save session", error); 2658a20a963SEric Samelson } 2668a20a963SEric Samelson} 2678a20a963SEric Samelson 2688a20a963SEric SamelsonRCT_REMAP_METHOD(removeSessionAsync, 2698a20a963SEric Samelson removeSessionAsync:(RCTPromiseResolveBlock)resolve 2708a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2718a20a963SEric Samelson{ 2728a20a963SEric Samelson NSError *error; 2738a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] deleteSessionFromKeychainWithError:&error]; 2748a20a963SEric Samelson if (success) { 2758a20a963SEric Samelson resolve(nil); 2768a20a963SEric Samelson } else { 2778a20a963SEric Samelson reject(@"ERR_SESSION_NOT_REMOVED", @"Could not remove session", error); 2788a20a963SEric Samelson } 2799a7f140aSEric Samelson} 2809a7f140aSEric Samelson 28124a0cefbSTomasz Sapeta/** 28224a0cefbSTomasz Sapeta * Checks whether the dev menu onboarding is already finished. 28324a0cefbSTomasz Sapeta * Onboarding is a screen that shows the dev menu to the user that opens any experience for the first time. 28424a0cefbSTomasz Sapeta*/ 28524a0cefbSTomasz SapetaRCT_REMAP_METHOD(getIsOnboardingFinishedAsync, 28624a0cefbSTomasz Sapeta getIsOnboardingFinishedWithResolver:(RCTPromiseResolveBlock)resolve 28766169681SBen Roth rejecter:(RCTPromiseRejectBlock)reject) 288b987b50fSBen Roth{ 28966169681SBen Roth if (_delegate) { 29066169681SBen Roth BOOL isFinished = [_delegate homeModuleShouldFinishNux:self]; 29166169681SBen Roth resolve(@(isFinished)); 29266169681SBen Roth } else { 29366169681SBen Roth resolve(@(NO)); 29466169681SBen Roth } 29566169681SBen Roth} 29666169681SBen Roth 29724a0cefbSTomasz Sapeta/** 29824a0cefbSTomasz Sapeta * Sets appropriate setting in user defaults that user's onboarding has finished. 29924a0cefbSTomasz Sapeta */ 30024a0cefbSTomasz SapetaRCT_REMAP_METHOD(setIsOnboardingFinishedAsync, 30124a0cefbSTomasz Sapeta setIsOnboardingFinished:(BOOL)isOnboardingFinished) 30266169681SBen Roth{ 30366169681SBen Roth if (_delegate) { 30424a0cefbSTomasz Sapeta [_delegate homeModule:self didFinishNux:isOnboardingFinished]; 30566169681SBen Roth } 306b987b50fSBen Roth} 307b987b50fSBen Roth 30824a0cefbSTomasz Sapeta/** 30924a0cefbSTomasz Sapeta * Called when the native event has succeeded on the JS side. 31024a0cefbSTomasz Sapeta */ 311b987b50fSBen RothRCT_REMAP_METHOD(onEventSuccess, 312b987b50fSBen Roth eventId:(NSString *)eventId 313b987b50fSBen Roth body:(NSDictionary *)body) 314b987b50fSBen Roth{ 315b987b50fSBen Roth void (^success)(NSDictionary *) = [_eventSuccessBlocks objectForKey:eventId]; 316b987b50fSBen Roth if (success) { 317b987b50fSBen Roth success(body); 318b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 319b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 320b987b50fSBen Roth } 321b987b50fSBen Roth} 322b987b50fSBen Roth 32324a0cefbSTomasz Sapeta/** 32424a0cefbSTomasz Sapeta * Called when the native event has failed on the JS side. 32524a0cefbSTomasz Sapeta */ 326b987b50fSBen RothRCT_REMAP_METHOD(onEventFailure, 327b987b50fSBen Roth eventId:(NSString *)eventId 328b987b50fSBen Roth message:(NSString *)message) 329b987b50fSBen Roth{ 330b987b50fSBen Roth void (^failure)(NSString *) = [_eventFailureBlocks objectForKey:eventId]; 331b987b50fSBen Roth if (failure) { 332b987b50fSBen Roth failure(message); 333b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 334b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 335b987b50fSBen Roth } 336b987b50fSBen Roth} 337b987b50fSBen Roth 338b987b50fSBen Roth@end 339