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