1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import <ABI47_0_0React/ABI47_0_0RCTLinkingManager.h>
9
10#import <ABI47_0_0FBReactNativeSpec/ABI47_0_0FBReactNativeSpec.h>
11#import <ABI47_0_0React/ABI47_0_0RCTBridge.h>
12#import <ABI47_0_0React/ABI47_0_0RCTUtils.h>
13#import <ABI47_0_0React/ABI47_0_0RCTLog.h>
14
15#import "ABI47_0_0RCTLinkingPlugins.h"
16
17static NSString *const kOpenURLNotification = @"ABI47_0_0RCTOpenURLNotification";
18
19static void postNotificationWithURL(NSURL *URL, id sender)
20{
21  NSDictionary<NSString *, id> *payload = @{@"url": URL.absoluteString};
22  [[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification
23                                                      object:sender
24                                                    userInfo:payload];
25}
26
27@interface ABI47_0_0RCTLinkingManager() <ABI47_0_0NativeLinkingManagerSpec>
28@end
29
30@implementation ABI47_0_0RCTLinkingManager
31
32ABI47_0_0RCT_EXPORT_MODULE()
33
34- (dispatch_queue_t)methodQueue
35{
36  return dispatch_get_main_queue();
37}
38
39- (void)startObserving
40{
41  [[NSNotificationCenter defaultCenter] addObserver:self
42                                           selector:@selector(handleOpenURLNotification:)
43                                               name:kOpenURLNotification
44                                             object:nil];
45}
46
47- (void)stopObserving
48{
49  [[NSNotificationCenter defaultCenter] removeObserver:self];
50}
51
52- (NSArray<NSString *> *)supportedEvents
53{
54  return @[@"url"];
55}
56
57+ (BOOL)application:(UIApplication *)app
58            openURL:(NSURL *)URL
59            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
60{
61  postNotificationWithURL(URL, self);
62  return YES;
63}
64
65// Corresponding api deprecated in iOS 9
66+ (BOOL)application:(UIApplication *)application
67            openURL:(NSURL *)URL
68  sourceApplication:(NSString *)sourceApplication
69         annotation:(id)annotation
70{
71  postNotificationWithURL(URL, self);
72  return YES;
73}
74
75+ (BOOL)application:(UIApplication *)application
76continueUserActivity:(NSUserActivity *)userActivity
77  restorationHandler:
78    #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 12000) /* __IPHONE_12_0 */
79        (nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler {
80    #else
81        (nonnull void (^)(NSArray *_Nullable))restorationHandler {
82    #endif
83  if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
84    NSDictionary *payload = @{@"url": userActivity.webpageURL.absoluteString};
85    [[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification
86                                                        object:self
87                                                      userInfo:payload];
88  }
89  return YES;
90}
91
92- (void)handleOpenURLNotification:(NSNotification *)notification
93{
94  [self sendEventWithName:@"url" body:notification.userInfo];
95}
96
97ABI47_0_0RCT_EXPORT_METHOD(openURL:(NSURL *)URL
98                  resolve:(ABI47_0_0RCTPromiseResolveBlock)resolve
99                  reject:(ABI47_0_0RCTPromiseRejectBlock)reject)
100{
101  [ABI47_0_0RCTSharedApplication() openURL:URL options:@{} completionHandler:^(BOOL success) {
102    if (success) {
103      resolve(@YES);
104    } else {
105      #if TARGET_OS_SIMULATOR
106        // Simulator-specific code
107        if([URL.absoluteString hasPrefix:@"tel:"]){
108          ABI47_0_0RCTLogWarn(@"Unable to open the Phone app in the simulator for telephone URLs. URL:  %@", URL);
109          resolve(@NO);
110        } else {
111          reject(ABI47_0_0RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@", URL], nil);
112        }
113      #else
114        // Device-specific code
115        reject(ABI47_0_0RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@", URL], nil);
116      #endif
117    }
118  }];
119}
120
121ABI47_0_0RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
122                  resolve:(ABI47_0_0RCTPromiseResolveBlock)resolve
123                  reject:(__unused ABI47_0_0RCTPromiseRejectBlock)reject)
124{
125  if (ABI47_0_0RCTRunningInAppExtension()) {
126    // Technically Today widgets can open urls, but supporting that would require
127    // a reference to the NSExtensionContext
128    resolve(@NO);
129    return;
130  }
131
132  // This can be expensive, so we deliberately don't call on main thread
133  BOOL canOpen = [ABI47_0_0RCTSharedApplication() canOpenURL:URL];
134  NSString *scheme = [URL scheme];
135  if (canOpen) {
136    resolve(@YES);
137  } else if (![[scheme lowercaseString] hasPrefix:@"http"] && ![[scheme lowercaseString] hasPrefix:@"https"]) {
138    // On iOS 9 and above canOpenURL returns NO without a helpful error.
139    // Check if a custom scheme is being used, and if it exists in LSApplicationQueriesSchemes
140    NSArray *querySchemes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSApplicationQueriesSchemes"];
141    if (querySchemes != nil && ([querySchemes containsObject:scheme] || [querySchemes containsObject:[scheme lowercaseString]])) {
142      resolve(@NO);
143    } else {
144      reject(ABI47_0_0RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@. Add %@ to LSApplicationQueriesSchemes in your Info.plist.", URL, scheme], nil);
145    }
146  } else {
147    resolve(@NO);
148  }
149}
150
151ABI47_0_0RCT_EXPORT_METHOD(getInitialURL:(ABI47_0_0RCTPromiseResolveBlock)resolve
152                  reject:(__unused ABI47_0_0RCTPromiseRejectBlock)reject)
153{
154  NSURL *initialURL = nil;
155  if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
156    initialURL = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
157  } else {
158    NSDictionary *userActivityDictionary =
159      self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
160    if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) {
161      initialURL = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL;
162    }
163  }
164  resolve(ABI47_0_0RCTNullIfNil(initialURL.absoluteString));
165}
166
167ABI47_0_0RCT_EXPORT_METHOD(openSettings:(ABI47_0_0RCTPromiseResolveBlock)resolve
168                  reject:(__unused ABI47_0_0RCTPromiseRejectBlock)reject)
169{
170  NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
171  [ABI47_0_0RCTSharedApplication() openURL:url options:@{} completionHandler:^(BOOL success) {
172    if (success) {
173      resolve(nil);
174    } else {
175      reject(ABI47_0_0RCTErrorUnspecified, @"Unable to open app settings", nil);
176    }
177  }];
178}
179
180ABI47_0_0RCT_EXPORT_METHOD(sendIntent:(NSString *)action
181                  extras:(NSArray *_Nullable)extras
182                  resolve:(ABI47_0_0RCTPromiseResolveBlock)resolve
183                  reject:(ABI47_0_0RCTPromiseRejectBlock)reject)
184{
185  ABI47_0_0RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
186}
187
188- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule:(const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params
189{
190  return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeLinkingManagerSpecJSI>(params);
191}
192
193@end
194
195Class ABI47_0_0RCTLinkingManagerCls(void) {
196  return ABI47_0_0RCTLinkingManager.class;
197}
198