Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ class RuntimeScheduler;
RCT_EXTERN NSArray<NSString *> *RCTAppSetupUnstableModulesRequiringMainQueueSetup(
id<RCTDependencyProvider> dependencyProvider);

RCT_EXTERN id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(
Class moduleClass,
id<RCTDependencyProvider> dependencyProvider);
RCT_EXTERN id<RCTTurboModule>
RCTAppSetupDefaultModuleFromClass(Class moduleClass, id<RCTDependencyProvider> dependencyProvider, id rnInstanceId);

std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupDefaultJsExecutorFactory(
RCTBridge *bridge,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
return dependencyProvider ? dependencyProvider.unstableModulesRequiringMainQueueSetup : @[];
}

id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(Class moduleClass, id<RCTDependencyProvider> dependencyProvider)
id<RCTTurboModule>
RCTAppSetupDefaultModuleFromClass(Class moduleClass, id<RCTDependencyProvider> dependencyProvider, id rnInstanceId)
{
// private block used to filter out modules depending on protocol conformance
NSArray * (^extractModuleConformingToProtocol)(RCTModuleRegistry *, Protocol *) =
Expand Down Expand Up @@ -111,6 +112,12 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
] arrayByAddingObjectsFromArray:URLRequestHandlerModules];
}];
}

// attempt to initialize with initWithUniqueRNInstance, which require the RN instance id for multi-instance support
if ([moduleClass instancesRespondToSelector:@selector(initWithUniqueRNInstance:)]) {
return [[moduleClass alloc] initWithUniqueRNInstance:rnInstanceId];
}

// No custom initializer here.
return [moduleClass new];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
initialProperties:(NSDictionary *_Nullable)initialProperties
launchOptions:(NSDictionary *_Nullable)launchOptions;

/// This is a SceneDelegate entrypoint method to start a React Native instance with the specified module name, window
/// and connection options for linking & user activity information. As it's usual for the typical deep-linking use case,
/// only the first item in URLContexts from connectionOptions will be checked; the same applies to userActivities.
/// @param moduleName name of the JS module to load
/// @param window the window to launch in
/// @param connectionOptions the scene's connection options
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions;

/// This is a SceneDelegate entrypoint method to start a React Native instance with the specified module name, window
/// and connection options for linking, initial properties & user activity information. As it's usual for the typical
/// deep-linking use case, only the first item in URLContexts from connectionOptions will be checked; the same applies
/// to userActivities.
/// @param moduleName name of the JS module to load
/// @param window the window to launch in
/// @param initialProperties the initial root properties
/// @param connectionOptions the scene's connection options
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
initialProperties:(NSDictionary *_Nullable)initialProperties
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions;

@property (nonatomic, nullable) RCTBridge *bridge;
@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import "RCTReactNativeFactory.h"
#import <React/RCTColorSpaceUtils.h>
#import <React/RCTLog.h>
#import <React/RCTMultiWindowUtils.h>
#import <React/RCTRootView.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <React/RCTUtils.h>
Expand Down Expand Up @@ -37,6 +38,13 @@ @interface RCTReactNativeFactory () <
RCTHostDelegate,
RCTJSRuntimeConfiguratorProtocol,
RCTTurboModuleManagerDelegate>

/// Adapter for SceneDelegate entrypoint's UISceneConnectionOptions that converts it to the AppDelegate-style
/// NSDictionary for internal RN needs
/// @param connectionOptions the scene's connection options
/// @return an NSDictionary with proper UIApplicationLaunchOptions- keys set to values from connectionOptions
- (NSDictionary *)convertConnectionOptionsToLaunchOptions:(UISceneConnectionOptions *)connectionOptions;

@end

@implementation RCTReactNativeFactory
Expand Down Expand Up @@ -87,6 +95,59 @@ - (void)startReactNativeWithModuleName:(NSString *)moduleName
[_delegate setRootView:rootView toRootViewController:rootViewController];
window.rootViewController = rootViewController;
[window makeKeyAndVisible];
[RCTMultiWindowRegistry registerWindow:window withRNInstance:[self.rootViewFactory.reactHost getRCTInstance]];
}

#pragma mark - UIScene.ConnectionOptions

- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions
{
[self startReactNativeWithModuleName:moduleName
inWindow:window
initialProperties:nil
launchOptions:[self convertConnectionOptionsToLaunchOptions:connectionOptions]];
}

- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
initialProperties:(NSDictionary *_Nullable)initialProperties
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions
{
[self startReactNativeWithModuleName:moduleName
inWindow:window
initialProperties:initialProperties
launchOptions:[self convertConnectionOptionsToLaunchOptions:connectionOptions]];
}

- (NSDictionary *)convertConnectionOptionsToLaunchOptions:(UISceneConnectionOptions *)connectionOptions
{
NSMutableDictionary *launchOptions = [NSMutableDictionary dictionary];

// handle launch URL
if (connectionOptions.URLContexts.count > 0) {
UIOpenURLContext *urlContext = connectionOptions.URLContexts.allObjects.firstObject;

if (urlContext.URL) {
launchOptions[UIApplicationLaunchOptionsURLKey] = urlContext.URL;
}
}

// handle user activities
if (connectionOptions.userActivities.count > 0) {
NSUserActivity *activity = connectionOptions.userActivities.allObjects.firstObject;

if (activity) {
NSMutableDictionary *userActivityDict = [NSMutableDictionary dictionary];
userActivityDict[UIApplicationLaunchOptionsUserActivityTypeKey] = activity.activityType;
userActivityDict[@"UIApplicationLaunchOptionsUserActivityKey"] = activity;

launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey] = userActivityDict;
}
}

return launchOptions;
}

#pragma mark - RCTUIConfiguratorProtocol
Expand Down Expand Up @@ -188,7 +249,8 @@ - (Class)getModuleClassFromName:(const char *)name
return moduleInstance;
}
}
return RCTAppSetupDefaultModuleFromClass(moduleClass, self.delegate.dependencyProvider);
return RCTAppSetupDefaultModuleFromClass(
moduleClass, self.delegate.dependencyProvider, [self.rootViewFactory.reactHost getRCTInstance]);
}

- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,11 @@ - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge

std::shared_ptr<facebook::react::CallInvoker> callInvoker =
std::make_shared<facebook::react::RuntimeSchedulerCallInvoker>(_runtimeScheduler);
RCTTurboModuleManager *turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:_turboModuleManagerDelegate
jsInvoker:callInvoker];
RCTTurboModuleManager *turboModuleManager =
[[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:_turboModuleManagerDelegate
jsInvoker:callInvoker
rnInstanceId:[self.reactHost getRCTInstance]];
_contextContainer->erase("RuntimeScheduler");
_contextContainer->insert("RuntimeScheduler", _runtimeScheduler);
return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler);
Expand Down
20 changes: 20 additions & 0 deletions packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,37 @@

@interface RCTLinkingManager : RCTEventEmitter

#pragma mark - AppDelegate methods

/// Lifecycle method informing of a URL being opened with the app, must be invoked from the AppDelegate.
/// Must be invoked from the AppDelegate.
/// Note: this is an implementation using the iOS 9.0-26.0 API
+ (BOOL)application:(nonnull UIApplication *)app
openURL:(nonnull NSURL *)URL
options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;

/// Lifecycle method handling a URL being opened with the app, must be invoked from the AppDelegate.
/// Must be invoked from the AppDelegate.
/// Note: this is an implementation using the iOS 4.2-9.0 API
+ (BOOL)application:(nonnull UIApplication *)application
openURL:(nonnull NSURL *)URL
sourceApplication:(nullable NSString *)sourceApplication
annotation:(nonnull id)annotation;

/// Lifecycle method handling user activity being performed.
/// Must be invoked from the AppDelegate.
+ (BOOL)application:(nonnull UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler;

#pragma mark - SceneDelegate methods

/// Successor to AppDelegate's application:continueUserActivity:restorationHandler:, which handles user activity being
/// performed. Must be invoked from the SceneDelegate.
+ (void)scene:(nonnull UIScene *)scene continueUserActivity:(nonnull NSUserActivity *)userActivity;

/// Successor to AppDelegate's application:openURL:options:, which handles user activity being performed.
/// Must be invoked from the SceneDelegate.
+ (void)scene:(nonnull UIScene *)scene openURLContexts:(nonnull NSSet<UIOpenURLContext *> *)URLContexts;

@end
104 changes: 76 additions & 28 deletions packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,47 @@

static NSString *const kOpenURLNotification = @"RCTOpenURLNotification";

static void postNotificationWithURL(NSURL *URL, id sender)
{
NSDictionary<NSString *, id> *payload = @{@"url" : URL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification object:sender userInfo:payload];
}

@interface RCTLinkingManager () <NativeLinkingManagerSpec>

/// Common logic for handling user activities originating from both AppDelegate- and SceneDelegate- lifecycle methods
+ (void)handleUserActivity:(NSUserActivity *)userActivity window:(UIWindow *)window;

/// Common logic for handling user activities from AppDelegate-lifecycle methods.
+ (BOOL)handleAppDelegateURL:(NSURL *)URL app:(UIApplication *)app;

/// Posts a URL notification that will be handled by the emitter to JS; this method is used to invoke instance methods
/// of RCTLinkingManager from class methods via NSNotificationCenter.
/// @param URL The URL to be emitted.
/// @param sender The sender object, which is critical to differentiate the recipient instance. It should be the
/// RCTTurboModuleManagerDelegate instance that the event should be emitted to. Such design makes sure that only one RN
/// instance is the recipient of this event.
+ (void)postNotificationWithURL:(NSURL *)URL sender:(id)sender;

@end

@implementation RCTLinkingManager

RCT_EXPORT_MODULE()

+ (void)postNotificationWithURL:(NSURL *)URL sender:(id)sender
{
NSDictionary<NSString *, id> *payload = @{@"url" : URL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification object:sender userInfo:payload];
}

- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}

#pragma mark - RCTEventEmitter methods

- (void)startObserving
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleOpenURLNotification:)
name:kOpenURLNotification
object:nil];
object:self.windowForRNInstance];
}

- (void)stopObserving
Expand All @@ -52,45 +69,81 @@ - (void)stopObserving
return @[ @"url" ];
}

#pragma mark - JS methods

+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)URL
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
postNotificationWithURL(URL, self);
return YES;
return [self handleAppDelegateURL:URL app:app];
}

// Corresponding api deprecated in iOS 9
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
postNotificationWithURL(URL, self);
return YES;
return [self handleAppDelegateURL:URL app:application];
}

+ (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler
{
if (!RCTIsSceneDelegateApp()) {
[RCTLinkingManager handleUserActivity:userActivity window:RCTKeyWindowFromApplication(application)];
return YES;
}

return NO;
}

#pragma mark - SceneDelegate methods

+ (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity
{
[RCTLinkingManager handleUserActivity:userActivity window:RCTKeyWindowFromScene(scene)];
}

+ (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
{
if (URLContexts.count == 0) {
return;
}

NSURL *URL = URLContexts.allObjects.firstObject.URL;
[RCTLinkingManager postNotificationWithURL:URL sender:RCTKeyWindowFromScene(scene)];
}

#pragma mark - Common logic methods

+ (void)handleUserActivity:(NSUserActivity *)userActivity window:(UIWindow *)window
{
// This can be nullish when launching an App Clip.
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] && userActivity.webpageURL != nil) {
NSDictionary *payload = @{@"url" : userActivity.webpageURL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification object:self userInfo:payload];
[RCTLinkingManager postNotificationWithURL:userActivity.webpageURL sender:window];
}
return YES;
}

+ (BOOL)handleAppDelegateURL:(NSURL *)URL app:(UIApplication *)app
{
if (!RCTIsSceneDelegateApp()) {
[RCTLinkingManager postNotificationWithURL:URL sender:RCTKeyWindowFromApplication(app)];
return YES;
}

return NO;
}

- (void)handleOpenURLNotification:(NSNotification *)notification
{
[self sendEventWithName:@"url" body:notification.userInfo];
}

RCT_EXPORT_METHOD(openURL
: (NSURL *)URL resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
#pragma mark - JS methods

RCT_EXPORT_METHOD(openURL : (NSURL *)URL resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)
reject)
{
[RCTSharedApplication() openURL:URL
options:@{}
Expand All @@ -114,10 +167,8 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
}];
}

RCT_EXPORT_METHOD(canOpenURL
: (NSURL *)URL resolve
: (RCTPromiseResolveBlock)resolve reject
: (__unused RCTPromiseRejectBlock)reject)
RCT_EXPORT_METHOD(canOpenURL : (NSURL *)URL resolve : (RCTPromiseResolveBlock)
resolve reject : (__unused RCTPromiseRejectBlock)reject)
{
if (RCTRunningInAppExtension()) {
// Technically Today widgets can open urls, but supporting that would require
Expand Down Expand Up @@ -181,11 +232,8 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
}];
}

RCT_EXPORT_METHOD(sendIntent
: (NSString *)action extras
: (NSArray *_Nullable)extras resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
RCT_EXPORT_METHOD(sendIntent : (NSString *)action extras : (NSArray *_Nullable)extras resolve : (RCTPromiseResolveBlock)
resolve reject : (RCTPromiseRejectBlock)reject)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}
Expand Down
Loading
Loading