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" 8*24a0cefbSTomasz Sapeta#import "EXKernelDevKeyCommands.h" 9b987b50fSBen Roth 10b987b50fSBen Roth#import <React/RCTEventDispatcher.h> 11b987b50fSBen Roth 12b987b50fSBen Roth@interface EXHomeModule () 13b987b50fSBen Roth 14b987b50fSBen Roth@property (nonatomic, assign) BOOL hasListeners; 15b987b50fSBen Roth@property (nonatomic, strong) NSMutableDictionary *eventSuccessBlocks; 16b987b50fSBen Roth@property (nonatomic, strong) NSMutableDictionary *eventFailureBlocks; 17b987b50fSBen Roth@property (nonatomic, strong) NSArray * _Nonnull sdkVersions; 18b987b50fSBen Roth@property (nonatomic, weak) id<EXHomeModuleDelegate> delegate; 19b987b50fSBen Roth 20b987b50fSBen Roth@end 21b987b50fSBen Roth 22b987b50fSBen Roth@implementation EXHomeModule 23b987b50fSBen Roth 24b987b50fSBen Roth+ (NSString *)moduleName { return @"ExponentKernel"; } 25b987b50fSBen Roth 26b987b50fSBen Roth- (instancetype)initWithExperienceId:(NSString *)experienceId kernelServiceDelegate:(id)kernelServiceInstance params:(NSDictionary *)params 27b987b50fSBen Roth{ 28b987b50fSBen Roth if (self = [super initWithExperienceId:experienceId kernelServiceDelegate:kernelServiceInstance params:params]) { 29b987b50fSBen Roth _eventSuccessBlocks = [NSMutableDictionary dictionary]; 30b987b50fSBen Roth _eventFailureBlocks = [NSMutableDictionary dictionary]; 31c350fdbbSTomasz Sapeta _sdkVersions = params[@"constants"][@"supportedExpoSdks"]; 32b987b50fSBen Roth _delegate = kernelServiceInstance; 33*24a0cefbSTomasz Sapeta 34*24a0cefbSTomasz Sapeta // Register keyboard commands like Cmd+D for the simulator. 35*24a0cefbSTomasz Sapeta [[EXKernelDevKeyCommands sharedInstance] registerDevCommands]; 36b987b50fSBen Roth } 37b987b50fSBen Roth return self; 38b987b50fSBen Roth} 39b987b50fSBen Roth 40b987b50fSBen Roth+ (BOOL)requiresMainQueueSetup 41b987b50fSBen Roth{ 42b987b50fSBen Roth return NO; 43b987b50fSBen Roth} 44b987b50fSBen Roth 45b987b50fSBen Roth- (NSDictionary *)constantsToExport 46b987b50fSBen Roth{ 47ec45106eSQuinlan Jung return @{ @"sdkVersions": _sdkVersions, 4872d142a5SStanisław Chmiela @"IOSClientReleaseType": [EXClientReleaseType clientReleaseType] }; 49b987b50fSBen Roth} 50b987b50fSBen Roth 51b987b50fSBen Roth#pragma mark - RCTEventEmitter methods 52b987b50fSBen Roth 53b987b50fSBen Roth- (NSArray<NSString *> *)supportedEvents 54b987b50fSBen Roth{ 55b987b50fSBen Roth return @[]; 56b987b50fSBen Roth} 57b987b50fSBen Roth 58b987b50fSBen Roth/** 59b987b50fSBen Roth * Override this method to avoid the [self supportedEvents] validation 60b987b50fSBen Roth */ 61b987b50fSBen Roth- (void)sendEventWithName:(NSString *)eventName body:(id)body 62b987b50fSBen Roth{ 63b987b50fSBen Roth // Note that this could be a versioned bridge! 64b987b50fSBen Roth [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" 65b987b50fSBen Roth args:body ? @[eventName, body] : @[eventName]]; 66b987b50fSBen Roth} 67b987b50fSBen Roth 68b987b50fSBen Roth#pragma mark - 69b987b50fSBen Roth 70b987b50fSBen Roth- (void)dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^)(NSDictionary *))success onFailure:(void (^)(NSString *))failure 71b987b50fSBen Roth{ 72b987b50fSBen Roth NSString *qualifiedEventName = [NSString stringWithFormat:@"ExponentKernel.%@", eventName]; 73b987b50fSBen Roth NSMutableDictionary *qualifiedEventBody = (eventBody) ? [eventBody mutableCopy] : [NSMutableDictionary dictionary]; 74b987b50fSBen Roth 75b987b50fSBen Roth if (success && failure) { 76b987b50fSBen Roth NSString *eventId = [[NSUUID UUID] UUIDString]; 77b987b50fSBen Roth [_eventSuccessBlocks setObject:success forKey:eventId]; 78b987b50fSBen Roth [_eventFailureBlocks setObject:failure forKey:eventId]; 79b987b50fSBen Roth [qualifiedEventBody setObject:eventId forKey:@"eventId"]; 80b987b50fSBen Roth } 81b987b50fSBen Roth 82b987b50fSBen Roth [self sendEventWithName:qualifiedEventName body:qualifiedEventBody]; 83b987b50fSBen Roth} 84b987b50fSBen Roth 85b987b50fSBen Roth/** 86*24a0cefbSTomasz Sapeta * Requests JavaScript side to start closing the dev menu (start the animation or so). 87*24a0cefbSTomasz Sapeta * Fully closes the dev menu once it receives a response from that event. 88*24a0cefbSTomasz Sapeta */ 89*24a0cefbSTomasz Sapeta- (void)requestToCloseDevMenu 90*24a0cefbSTomasz Sapeta{ 91*24a0cefbSTomasz Sapeta __weak typeof(self) weakSelf = self; 92*24a0cefbSTomasz Sapeta void (^close)(id) = ^(id arg){ 93*24a0cefbSTomasz Sapeta __strong typeof(weakSelf) strongSelf = weakSelf; 94*24a0cefbSTomasz Sapeta if (strongSelf->_delegate) { 95*24a0cefbSTomasz Sapeta [strongSelf->_delegate homeModuleDidSelectCloseMenu:strongSelf]; 96*24a0cefbSTomasz Sapeta } 97*24a0cefbSTomasz Sapeta }; 98*24a0cefbSTomasz Sapeta [self dispatchJSEvent:@"requestToCloseDevMenu" body:nil onSuccess:close onFailure:close]; 99*24a0cefbSTomasz Sapeta} 100*24a0cefbSTomasz Sapeta 101*24a0cefbSTomasz Sapeta/** 102b987b50fSBen Roth * Duplicates Linking.openURL but does not validate that this is an exponent URL; 103b987b50fSBen Roth * in other words, we just take your word for it and never hand it off to iOS. 104b987b50fSBen Roth * Used by the home screen URL bar. 105b987b50fSBen Roth */ 106b987b50fSBen RothRCT_EXPORT_METHOD(openURL:(NSURL *)URL 107b987b50fSBen Roth resolve:(RCTPromiseResolveBlock)resolve 108b987b50fSBen Roth reject:(__unused RCTPromiseRejectBlock)reject) 109b987b50fSBen Roth{ 110b987b50fSBen Roth if (URL) { 111b987b50fSBen Roth [_delegate homeModule:self didOpenUrl:URL.absoluteString]; 112b987b50fSBen Roth resolve(@YES); 113b987b50fSBen Roth } else { 114b987b50fSBen Roth NSError *err = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot open a nil url" }]; 115b987b50fSBen Roth reject(@"E_INVALID_URL", err.localizedDescription, err); 116b987b50fSBen Roth } 117b987b50fSBen Roth} 118b987b50fSBen Roth 119*24a0cefbSTomasz Sapeta/** 120*24a0cefbSTomasz Sapeta * Returns boolean value determining whether the current app supports developer tools. 121*24a0cefbSTomasz Sapeta */ 122*24a0cefbSTomasz SapetaRCT_REMAP_METHOD(doesCurrentTaskEnableDevtoolsAsync, 123b987b50fSBen Roth doesCurrentTaskEnableDevtoolsWithResolver:(RCTPromiseResolveBlock)resolve 124b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 125b987b50fSBen Roth{ 126b987b50fSBen Roth if (_delegate) { 127b987b50fSBen Roth resolve(@([_delegate homeModuleShouldEnableDevtools:self])); 128b987b50fSBen Roth } else { 129b987b50fSBen Roth // don't reject, just disable devtools 130b987b50fSBen Roth resolve(@NO); 131b987b50fSBen Roth } 132b987b50fSBen Roth} 133b987b50fSBen Roth 134*24a0cefbSTomasz Sapeta/** 135*24a0cefbSTomasz Sapeta * Gets a dictionary of dev menu options available in the currently shown experience, 136*24a0cefbSTomasz Sapeta * If the experience doesn't support developer tools just returns an empty response. 137*24a0cefbSTomasz Sapeta */ 138*24a0cefbSTomasz SapetaRCT_REMAP_METHOD(getDevMenuItemsToShowAsync, 139b987b50fSBen Roth getDevMenuItemsToShowWithResolver:(RCTPromiseResolveBlock)resolve 140b987b50fSBen Roth reject:(RCTPromiseRejectBlock)reject) 141b987b50fSBen Roth{ 142b987b50fSBen Roth if (_delegate && [_delegate homeModuleShouldEnableDevtools:self]) { 143b987b50fSBen Roth resolve([_delegate devMenuItemsForHomeModule:self]); 144b987b50fSBen Roth } else { 145b987b50fSBen Roth // don't reject, just show no devtools 146b987b50fSBen Roth resolve(@{}); 147b987b50fSBen Roth } 148b987b50fSBen Roth} 149b987b50fSBen Roth 150*24a0cefbSTomasz Sapeta/** 151*24a0cefbSTomasz Sapeta * Function called every time the dev menu option is selected. 152*24a0cefbSTomasz Sapeta */ 153*24a0cefbSTomasz SapetaRCT_EXPORT_METHOD(selectDevMenuItemWithKeyAsync:(NSString *)key) 154b987b50fSBen Roth{ 155b987b50fSBen Roth if (_delegate) { 156b987b50fSBen Roth [_delegate homeModule:self didSelectDevMenuItemWithKey:key]; 157b987b50fSBen Roth } 158b987b50fSBen Roth} 159b987b50fSBen Roth 160*24a0cefbSTomasz Sapeta/** 161*24a0cefbSTomasz Sapeta * Reloads currently shown app with the manifest. 162*24a0cefbSTomasz Sapeta */ 163*24a0cefbSTomasz SapetaRCT_EXPORT_METHOD(reloadAppAsync) 164b987b50fSBen Roth{ 165b987b50fSBen Roth if (_delegate) { 166b987b50fSBen Roth [_delegate homeModuleDidSelectRefresh:self]; 167b987b50fSBen Roth } 168b987b50fSBen Roth} 169b987b50fSBen Roth 170*24a0cefbSTomasz Sapeta/** 171*24a0cefbSTomasz Sapeta * Immediately closes the dev menu if it's visible. 172*24a0cefbSTomasz Sapeta * Note: It skips the animation that would have been applied by the JS side. 173*24a0cefbSTomasz Sapeta */ 174*24a0cefbSTomasz SapetaRCT_EXPORT_METHOD(closeDevMenuAsync) 175b987b50fSBen Roth{ 176b987b50fSBen Roth if (_delegate) { 177b987b50fSBen Roth [_delegate homeModuleDidSelectCloseMenu:self]; 178b987b50fSBen Roth } 179b987b50fSBen Roth} 180b987b50fSBen Roth 181*24a0cefbSTomasz Sapeta/** 182*24a0cefbSTomasz Sapeta * Goes back to the home app. 183*24a0cefbSTomasz Sapeta */ 184*24a0cefbSTomasz SapetaRCT_EXPORT_METHOD(goToHomeAsync) 185b987b50fSBen Roth{ 186b987b50fSBen Roth if (_delegate) { 187b987b50fSBen Roth [_delegate homeModuleDidSelectGoToHome:self]; 188b987b50fSBen Roth } 189b987b50fSBen Roth} 190b987b50fSBen Roth 191*24a0cefbSTomasz Sapeta/** 192*24a0cefbSTomasz Sapeta * Opens QR scanner to open another app by scanning its QR code. 193*24a0cefbSTomasz Sapeta */ 1942fcba380SBen RothRCT_EXPORT_METHOD(selectQRReader) 1952fcba380SBen Roth{ 1962fcba380SBen Roth if (_delegate) { 1972fcba380SBen Roth [_delegate homeModuleDidSelectQRReader:self]; 1982fcba380SBen Roth } 1992fcba380SBen Roth} 2002fcba380SBen Roth 2018a20a963SEric SamelsonRCT_REMAP_METHOD(getSessionAsync, 2028a20a963SEric Samelson getSessionAsync:(RCTPromiseResolveBlock)resolve 2038a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2049a7f140aSEric Samelson{ 2058a20a963SEric Samelson NSDictionary *session = [[EXSession sharedInstance] session]; 2068a20a963SEric Samelson resolve(session); 2079a7f140aSEric Samelson} 2089a7f140aSEric Samelson 2098a20a963SEric SamelsonRCT_REMAP_METHOD(setSessionAsync, 2108a20a963SEric Samelson setSessionAsync:(NSDictionary *)session 2118a20a963SEric Samelson resolver:(RCTPromiseResolveBlock)resolve 2128a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2139a7f140aSEric Samelson{ 2148a20a963SEric Samelson NSError *error; 2158a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] saveSessionToKeychain:session error:&error]; 2168a20a963SEric Samelson if (success) { 2178a20a963SEric Samelson resolve(nil); 2188a20a963SEric Samelson } else { 2198a20a963SEric Samelson reject(@"ERR_SESSION_NOT_SAVED", @"Could not save session", error); 2208a20a963SEric Samelson } 2218a20a963SEric Samelson} 2228a20a963SEric Samelson 2238a20a963SEric SamelsonRCT_REMAP_METHOD(removeSessionAsync, 2248a20a963SEric Samelson removeSessionAsync:(RCTPromiseResolveBlock)resolve 2258a20a963SEric Samelson rejecter:(RCTPromiseRejectBlock)reject) 2268a20a963SEric Samelson{ 2278a20a963SEric Samelson NSError *error; 2288a20a963SEric Samelson BOOL success = [[EXSession sharedInstance] deleteSessionFromKeychainWithError:&error]; 2298a20a963SEric Samelson if (success) { 2308a20a963SEric Samelson resolve(nil); 2318a20a963SEric Samelson } else { 2328a20a963SEric Samelson reject(@"ERR_SESSION_NOT_REMOVED", @"Could not remove session", error); 2338a20a963SEric Samelson } 2349a7f140aSEric Samelson} 2359a7f140aSEric Samelson 236*24a0cefbSTomasz Sapeta/** 237*24a0cefbSTomasz Sapeta * Checks whether the dev menu onboarding is already finished. 238*24a0cefbSTomasz Sapeta * Onboarding is a screen that shows the dev menu to the user that opens any experience for the first time. 239*24a0cefbSTomasz Sapeta*/ 240*24a0cefbSTomasz SapetaRCT_REMAP_METHOD(getIsOnboardingFinishedAsync, 241*24a0cefbSTomasz Sapeta getIsOnboardingFinishedWithResolver:(RCTPromiseResolveBlock)resolve 24266169681SBen Roth rejecter:(RCTPromiseRejectBlock)reject) 243b987b50fSBen Roth{ 24466169681SBen Roth if (_delegate) { 24566169681SBen Roth BOOL isFinished = [_delegate homeModuleShouldFinishNux:self]; 24666169681SBen Roth resolve(@(isFinished)); 24766169681SBen Roth } else { 24866169681SBen Roth resolve(@(NO)); 24966169681SBen Roth } 25066169681SBen Roth} 25166169681SBen Roth 252*24a0cefbSTomasz Sapeta/** 253*24a0cefbSTomasz Sapeta * Sets appropriate setting in user defaults that user's onboarding has finished. 254*24a0cefbSTomasz Sapeta */ 255*24a0cefbSTomasz SapetaRCT_REMAP_METHOD(setIsOnboardingFinishedAsync, 256*24a0cefbSTomasz Sapeta setIsOnboardingFinished:(BOOL)isOnboardingFinished) 25766169681SBen Roth{ 25866169681SBen Roth if (_delegate) { 259*24a0cefbSTomasz Sapeta [_delegate homeModule:self didFinishNux:isOnboardingFinished]; 26066169681SBen Roth } 261b987b50fSBen Roth} 262b987b50fSBen Roth 263*24a0cefbSTomasz Sapeta/** 264*24a0cefbSTomasz Sapeta * Called when the native event has succeeded on the JS side. 265*24a0cefbSTomasz Sapeta */ 266b987b50fSBen RothRCT_REMAP_METHOD(onEventSuccess, 267b987b50fSBen Roth eventId:(NSString *)eventId 268b987b50fSBen Roth body:(NSDictionary *)body) 269b987b50fSBen Roth{ 270b987b50fSBen Roth void (^success)(NSDictionary *) = [_eventSuccessBlocks objectForKey:eventId]; 271b987b50fSBen Roth if (success) { 272b987b50fSBen Roth success(body); 273b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 274b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 275b987b50fSBen Roth } 276b987b50fSBen Roth} 277b987b50fSBen Roth 278*24a0cefbSTomasz Sapeta/** 279*24a0cefbSTomasz Sapeta * Called when the native event has failed on the JS side. 280*24a0cefbSTomasz Sapeta */ 281b987b50fSBen RothRCT_REMAP_METHOD(onEventFailure, 282b987b50fSBen Roth eventId:(NSString *)eventId 283b987b50fSBen Roth message:(NSString *)message) 284b987b50fSBen Roth{ 285b987b50fSBen Roth void (^failure)(NSString *) = [_eventFailureBlocks objectForKey:eventId]; 286b987b50fSBen Roth if (failure) { 287b987b50fSBen Roth failure(message); 288b987b50fSBen Roth [_eventSuccessBlocks removeObjectForKey:eventId]; 289b987b50fSBen Roth [_eventFailureBlocks removeObjectForKey:eventId]; 290b987b50fSBen Roth } 291b987b50fSBen Roth} 292b987b50fSBen Roth 293b987b50fSBen Roth@end 294