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