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(isLegacyMenuBehaviorEnabledAsync,
108                 isLegacyMenuBehaviorEnabledWithResolver:(RCTPromiseResolveBlock)resolve
109                 rejecter:(RCTPromiseRejectBlock)reject)
110{
111  if (_delegate) {
112    resolve(@([_delegate homeModuleShouldEnableLegacyMenuBehavior:self]));
113  } else {
114    resolve(@(NO));
115  }
116}
117
118RCT_EXPORT_METHOD(setIsLegacyMenuBehaviorEnabledAsync:(BOOL)isEnabled)
119{
120  if (_delegate) {
121    [_delegate homeModule:self didSelectEnableLegacyMenuBehavior:isEnabled];
122  }
123}
124
125RCT_REMAP_METHOD(getDevMenuItemsToShow,
126                 getDevMenuItemsToShowWithResolver:(RCTPromiseResolveBlock)resolve
127                 reject:(RCTPromiseRejectBlock)reject)
128{
129  if (_delegate && [_delegate homeModuleShouldEnableDevtools:self]) {
130    resolve([_delegate devMenuItemsForHomeModule:self]);
131  } else {
132    // don't reject, just show no devtools
133    resolve(@{});
134  }
135}
136
137RCT_EXPORT_METHOD(selectDevMenuItemWithKey:(NSString *)key)
138{
139  if (_delegate) {
140    [_delegate homeModule:self didSelectDevMenuItemWithKey:key];
141  }
142}
143
144RCT_EXPORT_METHOD(selectRefresh)
145{
146  if (_delegate) {
147    [_delegate homeModuleDidSelectRefresh:self];
148  }
149}
150
151RCT_EXPORT_METHOD(selectCloseMenu)
152{
153  if (_delegate) {
154    [_delegate homeModuleDidSelectCloseMenu:self];
155  }
156}
157
158RCT_EXPORT_METHOD(selectGoToHome)
159{
160  if (_delegate) {
161    [_delegate homeModuleDidSelectGoToHome:self];
162  }
163}
164
165RCT_EXPORT_METHOD(selectQRReader)
166{
167  if (_delegate) {
168    [_delegate homeModuleDidSelectQRReader:self];
169  }
170}
171
172RCT_EXPORT_METHOD(addDevMenu)
173{
174  __weak typeof(self) weakSelf = self;
175  dispatch_async(dispatch_get_main_queue(), ^{
176    if (weakSelf.delegate) {
177      [weakSelf.delegate homeModuleDidSelectHomeDiagnostics:self];
178    }
179  });
180}
181
182RCT_REMAP_METHOD(getIsNuxFinishedAsync,
183                 getIsNuxFinishedWithResolver:(RCTPromiseResolveBlock)resolve
184                 rejecter:(RCTPromiseRejectBlock)reject)
185{
186  if (_delegate) {
187    BOOL isFinished = [_delegate homeModuleShouldFinishNux:self];
188    resolve(@(isFinished));
189  } else {
190    resolve(@(NO));
191  }
192}
193
194RCT_REMAP_METHOD(setIsNuxFinishedAsync,
195                 setIsNuxFinished:(BOOL)isNuxFinished)
196{
197  if (_delegate) {
198    [_delegate homeModule:self didFinishNux:isNuxFinished];
199  }
200}
201
202RCT_REMAP_METHOD(onEventSuccess,
203                 eventId:(NSString *)eventId
204                 body:(NSDictionary *)body)
205{
206  void (^success)(NSDictionary *) = [_eventSuccessBlocks objectForKey:eventId];
207  if (success) {
208    success(body);
209    [_eventSuccessBlocks removeObjectForKey:eventId];
210    [_eventFailureBlocks removeObjectForKey:eventId];
211  }
212}
213
214RCT_REMAP_METHOD(onEventFailure,
215                 eventId:(NSString *)eventId
216                 message:(NSString *)message)
217{
218  void (^failure)(NSString *) = [_eventFailureBlocks objectForKey:eventId];
219  if (failure) {
220    failure(message);
221    [_eventSuccessBlocks removeObjectForKey:eventId];
222    [_eventFailureBlocks removeObjectForKey:eventId];
223  }
224}
225
226@end
227