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