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