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