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