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