1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXLinkingManager.h"
4#import "EXScopedModuleRegistry.h"
5#import "EXUtil.h"
6
7#import <React/RCTBridge.h>
8#import <React/RCTEventDispatcher.h>
9#import <React/RCTUtils.h>
10
11NSString * const EXLinkingEventOpenUrl = @"url";
12
13@interface EXLinkingManager ()
14
15@property (nonatomic, weak) id <EXLinkingManagerScopedModuleDelegate> kernelLinkingDelegate;
16@property (nonatomic, strong) NSURL *initialUrl;
17@property (nonatomic) BOOL hasListeners;
18
19@end
20
21@implementation EXLinkingManager
22
23EX_EXPORT_SCOPED_MODULE(RCTLinkingManager, KernelLinkingManager);
24
25- (instancetype)initWithExperienceStableLegacyId:(NSString *)experienceStableLegacyId
26                        scopeKey:(NSString *)scopeKey
27                     kernelServiceDelegate:(id)kernelServiceInstance params:(NSDictionary *)params
28{
29  if (self = [super initWithExperienceStableLegacyId:experienceStableLegacyId scopeKey:scopeKey kernelServiceDelegate:kernelServiceInstance params:params]) {
30    _kernelLinkingDelegate = kernelServiceInstance;
31    _initialUrl = params[@"initialUri"];
32    if (_initialUrl == [NSNull null]) {
33      _initialUrl = nil;
34    }
35  }
36  return self;
37}
38
39#pragma mark - RCTEventEmitter methods
40
41- (NSArray<NSString *> *)supportedEvents
42{
43  return @[EXLinkingEventOpenUrl];
44}
45
46- (void)startObserving
47{
48  _hasListeners = YES;
49}
50
51- (void)stopObserving
52{
53  _hasListeners = NO;
54}
55
56#pragma mark - Linking methods
57
58- (void)dispatchOpenUrlEvent:(NSURL *)url
59{
60  if (!url || !url.absoluteString) {
61    RCTFatal(RCTErrorWithMessage([NSString stringWithFormat:@"Tried to open a deep link to an invalid url: %@", url]));
62    return;
63  }
64  if (_hasListeners) {
65    [self sendEventWithName:EXLinkingEventOpenUrl body:@{@"url": url.absoluteString}];
66  }
67}
68
69RCT_EXPORT_METHOD(openURL:(NSURL *)URL
70                  resolve:(RCTPromiseResolveBlock)resolve
71                  reject:(RCTPromiseRejectBlock)reject)
72{
73  if ([_kernelLinkingDelegate linkingModule:self shouldOpenExpoUrl:URL]) {
74    [_kernelLinkingDelegate linkingModule:self didOpenUrl:URL.absoluteString];
75    resolve(@YES);
76  } else {
77    [EXUtil performSynchronouslyOnMainThread:^{
78      [RCTSharedApplication() openURL:URL options:@{} completionHandler:^(BOOL success) {
79        if (success) {
80          resolve(nil);
81        } else {
82          reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@", URL], nil);
83        }
84      }];
85    }];
86  }
87}
88
89RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
90                  resolve:(RCTPromiseResolveBlock)resolve
91                  reject:(__unused RCTPromiseRejectBlock)reject)
92{
93  __block BOOL canOpen = [_kernelLinkingDelegate linkingModule:self shouldOpenExpoUrl:URL];
94  if (!canOpen) {
95    [EXUtil performSynchronouslyOnMainThread:^{
96      canOpen = [RCTSharedApplication() canOpenURL:URL];
97    }];
98  }
99  resolve(@(canOpen));
100}
101
102RCT_EXPORT_METHOD(openSettings:(RCTPromiseResolveBlock)resolve
103                        reject:(RCTPromiseRejectBlock)reject)
104{
105  [EXUtil performSynchronouslyOnMainThread:^{
106    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
107    [RCTSharedApplication() openURL:url options:@{} completionHandler:^(BOOL success) {
108      if (success) {
109        resolve(nil);
110      } else {
111        reject(RCTErrorUnspecified, @"Unable to open app settings", nil);
112      }
113    }];
114  }];
115}
116
117RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
118                  reject:(__unused RCTPromiseRejectBlock)reject)
119{
120  resolve(RCTNullIfNil(_initialUrl.absoluteString));
121}
122
123@end
124