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