1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXEnvironment.h"
4#import "EXHomeModule.h"
5#import "EXSession.h"
6#import "EXUnversioned.h"
7
8#import <React/RCTEventDispatcher.h>
9
10@interface EXHomeModule ()
11
12@property (nonatomic, assign) BOOL hasListeners;
13@property (nonatomic, strong) NSMutableDictionary *eventSuccessBlocks;
14@property (nonatomic, strong) NSMutableDictionary *eventFailureBlocks;
15@property (nonatomic, strong) NSArray * _Nonnull sdkVersions;
16@property (nonatomic, weak) id<EXHomeModuleDelegate> delegate;
17
18@end
19
20@implementation EXHomeModule
21
22+ (NSString *)moduleName { return @"ExponentKernel"; }
23
24- (instancetype)initWithExperienceId:(NSString *)experienceId kernelServiceDelegate:(id)kernelServiceInstance params:(NSDictionary *)params
25{
26  if (self = [super initWithExperienceId:experienceId kernelServiceDelegate:kernelServiceInstance params:params]) {
27    _eventSuccessBlocks = [NSMutableDictionary dictionary];
28    _eventFailureBlocks = [NSMutableDictionary dictionary];
29    _sdkVersions = params[@"supportedSdkVersions"];
30    _delegate = kernelServiceInstance;
31  }
32  return self;
33}
34
35+ (BOOL)requiresMainQueueSetup
36{
37  return NO;
38}
39
40- (NSDictionary *)constantsToExport
41{
42  return @{ @"sdkVersions": _sdkVersions };
43}
44
45#pragma mark - RCTEventEmitter methods
46
47- (NSArray<NSString *> *)supportedEvents
48{
49  return @[];
50}
51
52/**
53 *  Override this method to avoid the [self supportedEvents] validation
54 */
55- (void)sendEventWithName:(NSString *)eventName body:(id)body
56{
57  // Note that this could be a versioned bridge!
58  [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
59                        args:body ? @[eventName, body] : @[eventName]];
60}
61
62#pragma mark -
63
64- (void)dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^)(NSDictionary *))success onFailure:(void (^)(NSString *))failure
65{
66  NSString *qualifiedEventName = [NSString stringWithFormat:@"ExponentKernel.%@", eventName];
67  NSMutableDictionary *qualifiedEventBody = (eventBody) ? [eventBody mutableCopy] : [NSMutableDictionary dictionary];
68
69  if (success && failure) {
70    NSString *eventId = [[NSUUID UUID] UUIDString];
71    [_eventSuccessBlocks setObject:success forKey:eventId];
72    [_eventFailureBlocks setObject:failure forKey:eventId];
73    [qualifiedEventBody setObject:eventId forKey:@"eventId"];
74  }
75
76  [self sendEventWithName:qualifiedEventName body:qualifiedEventBody];
77}
78
79/**
80 *  Duplicates Linking.openURL but does not validate that this is an exponent URL;
81 *  in other words, we just take your word for it and never hand it off to iOS.
82 *  Used by the home screen URL bar.
83 */
84RCT_EXPORT_METHOD(openURL:(NSURL *)URL
85                  resolve:(RCTPromiseResolveBlock)resolve
86                  reject:(__unused RCTPromiseRejectBlock)reject)
87{
88  if (URL) {
89    [_delegate homeModule:self didOpenUrl:URL.absoluteString];
90    resolve(@YES);
91  } else {
92    NSError *err = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot open a nil url" }];
93    reject(@"E_INVALID_URL", err.localizedDescription, err);
94  }
95}
96
97RCT_REMAP_METHOD(doesCurrentTaskEnableDevtools,
98                 doesCurrentTaskEnableDevtoolsWithResolver:(RCTPromiseResolveBlock)resolve
99                 reject:(RCTPromiseRejectBlock)reject)
100{
101  if (_delegate) {
102    resolve(@([_delegate homeModuleShouldEnableDevtools:self]));
103  } else {
104    // don't reject, just disable devtools
105    resolve(@NO);
106  }
107}
108
109RCT_REMAP_METHOD(isLegacyMenuBehaviorEnabledAsync,
110                 isLegacyMenuBehaviorEnabledWithResolver:(RCTPromiseResolveBlock)resolve
111                 rejecter:(RCTPromiseRejectBlock)reject)
112{
113  if (_delegate) {
114    resolve(@([_delegate homeModuleShouldEnableLegacyMenuBehavior:self]));
115  } else {
116    resolve(@(NO));
117  }
118}
119
120RCT_EXPORT_METHOD(setIsLegacyMenuBehaviorEnabledAsync:(BOOL)isEnabled)
121{
122  if (_delegate) {
123    [_delegate homeModule:self didSelectEnableLegacyMenuBehavior:isEnabled];
124  }
125}
126
127RCT_REMAP_METHOD(getDevMenuItemsToShow,
128                 getDevMenuItemsToShowWithResolver:(RCTPromiseResolveBlock)resolve
129                 reject:(RCTPromiseRejectBlock)reject)
130{
131  if (_delegate && [_delegate homeModuleShouldEnableDevtools:self]) {
132    resolve([_delegate devMenuItemsForHomeModule:self]);
133  } else {
134    // don't reject, just show no devtools
135    resolve(@{});
136  }
137}
138
139RCT_EXPORT_METHOD(selectDevMenuItemWithKey:(NSString *)key)
140{
141  if (_delegate) {
142    [_delegate homeModule:self didSelectDevMenuItemWithKey:key];
143  }
144}
145
146RCT_EXPORT_METHOD(selectRefresh)
147{
148  if (_delegate) {
149    [_delegate homeModuleDidSelectRefresh:self];
150  }
151}
152
153RCT_EXPORT_METHOD(selectCloseMenu)
154{
155  if (_delegate) {
156    [_delegate homeModuleDidSelectCloseMenu:self];
157  }
158}
159
160RCT_EXPORT_METHOD(selectGoToHome)
161{
162  if (_delegate) {
163    [_delegate homeModuleDidSelectGoToHome:self];
164  }
165}
166
167RCT_EXPORT_METHOD(selectQRReader)
168{
169  if (_delegate) {
170    [_delegate homeModuleDidSelectQRReader:self];
171  }
172}
173
174RCT_REMAP_METHOD(getSessionAsync,
175                 getSessionAsync:(RCTPromiseResolveBlock)resolve
176                 rejecter:(RCTPromiseRejectBlock)reject)
177{
178  NSDictionary *session = [[EXSession sharedInstance] session];
179  resolve(session);
180}
181
182RCT_REMAP_METHOD(setSessionAsync,
183                 setSessionAsync:(NSDictionary *)session
184                 resolver:(RCTPromiseResolveBlock)resolve
185                 rejecter:(RCTPromiseRejectBlock)reject)
186{
187  NSError *error;
188  BOOL success = [[EXSession sharedInstance] saveSessionToKeychain:session error:&error];
189  if (success) {
190    resolve(nil);
191  } else {
192    reject(@"ERR_SESSION_NOT_SAVED", @"Could not save session", error);
193  }
194}
195
196RCT_REMAP_METHOD(removeSessionAsync,
197                 removeSessionAsync:(RCTPromiseResolveBlock)resolve
198                 rejecter:(RCTPromiseRejectBlock)reject)
199{
200  NSError *error;
201  BOOL success = [[EXSession sharedInstance] deleteSessionFromKeychainWithError:&error];
202  if (success) {
203    resolve(nil);
204  } else {
205    reject(@"ERR_SESSION_NOT_REMOVED", @"Could not remove session", error);
206  }
207}
208
209RCT_EXPORT_METHOD(addDevMenu)
210{
211  __weak typeof(self) weakSelf = self;
212  dispatch_async(dispatch_get_main_queue(), ^{
213    if (weakSelf.delegate) {
214      [weakSelf.delegate homeModuleDidSelectHomeDiagnostics:self];
215    }
216  });
217}
218
219RCT_REMAP_METHOD(getIsNuxFinishedAsync,
220                 getIsNuxFinishedWithResolver:(RCTPromiseResolveBlock)resolve
221                 rejecter:(RCTPromiseRejectBlock)reject)
222{
223  if (_delegate) {
224    BOOL isFinished = [_delegate homeModuleShouldFinishNux:self];
225    resolve(@(isFinished));
226  } else {
227    resolve(@(NO));
228  }
229}
230
231RCT_REMAP_METHOD(setIsNuxFinishedAsync,
232                 setIsNuxFinished:(BOOL)isNuxFinished)
233{
234  if (_delegate) {
235    [_delegate homeModule:self didFinishNux:isNuxFinished];
236  }
237}
238
239RCT_REMAP_METHOD(onEventSuccess,
240                 eventId:(NSString *)eventId
241                 body:(NSDictionary *)body)
242{
243  void (^success)(NSDictionary *) = [_eventSuccessBlocks objectForKey:eventId];
244  if (success) {
245    success(body);
246    [_eventSuccessBlocks removeObjectForKey:eventId];
247    [_eventFailureBlocks removeObjectForKey:eventId];
248  }
249}
250
251RCT_REMAP_METHOD(onEventFailure,
252                 eventId:(NSString *)eventId
253                 message:(NSString *)message)
254{
255  void (^failure)(NSString *) = [_eventFailureBlocks objectForKey:eventId];
256  if (failure) {
257    failure(message);
258    [_eventSuccessBlocks removeObjectForKey:eventId];
259    [_eventFailureBlocks removeObjectForKey:eventId];
260  }
261}
262
263@end
264