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