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
73 changes: 73 additions & 0 deletions .github/workflows/microsoft-build-spm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Build SPM

on:
workflow_call:

jobs:
build-hermes:
name: "Build Hermes"
runs-on: macos-26
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0

- name: Setup toolchain
uses: ./.github/actions/microsoft-setup-toolchain
with:
node-version: '22'
platform: macos

- name: Install npm dependencies
run: yarn install

- name: Build Hermes from source
working-directory: packages/react-native
run: node scripts/ios-prebuild.js -s -f Debug

- name: Upload Hermes artifacts
uses: actions/upload-artifact@v4
with:
name: hermes-artifacts
path: packages/react-native/.build/artifacts/hermes
retention-days: 1

build-spm:
name: "${{ matrix.platform }}"
needs: build-hermes
runs-on: macos-26
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
platform: [ios, macos, visionos]
steps:
- uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0

- name: Setup toolchain
uses: ./.github/actions/microsoft-setup-toolchain
with:
node-version: '22'
platform: ${{ matrix.platform }}

- name: Install npm dependencies
run: yarn install

- name: Download Hermes artifacts
uses: actions/download-artifact@v4
with:
name: hermes-artifacts
path: packages/react-native/.build/artifacts/hermes

- name: Setup SPM workspace (using prebuilt Hermes)
working-directory: packages/react-native
run: node scripts/ios-prebuild.js -s -f Debug

- name: Build SPM (${{ matrix.platform }})
working-directory: packages/react-native
run: node scripts/ios-prebuild.js -b -f Debug -p ${{ matrix.platform }}
6 changes: 6 additions & 0 deletions .github/workflows/microsoft-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ jobs:
permissions: {}
uses: ./.github/workflows/microsoft-build-rntester.yml

build-spm:
name: "Build SPM"
permissions: {}
uses: ./.github/workflows/microsoft-build-spm.yml

test-react-native-macos-init:
name: "Test react-native-macos init"
permissions: {}
Expand All @@ -156,6 +161,7 @@ jobs:
- yarn-constraints
- javascript-tests
- build-rntester
- build-spm
- test-react-native-macos-init
# - react-native-test-app-integration
steps:
Expand Down
119 changes: 119 additions & 0 deletions packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#import "RCTLinkingPlugins.h"

#if !TARGET_OS_OSX // [macOS]

static NSString *const kOpenURLNotification = @"RCTOpenURLNotification";

static void postNotificationWithURL(NSURL *URL, id sender)
Expand All @@ -22,6 +24,24 @@ static void postNotificationWithURL(NSURL *URL, id sender)
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification object:sender userInfo:payload];
}

#else // [macOS

NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification";

static NSString *initialURL = nil;
static BOOL moduleInitalized = NO;
static BOOL alwaysForegroundLastWindow = YES;

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

#endif // macOS]

@interface RCTLinkingManager () <NativeLinkingManagerSpec>
@end

Expand All @@ -34,6 +54,8 @@ - (dispatch_queue_t)methodQueue
return dispatch_get_main_queue();
}

#if !TARGET_OS_OSX // [macOS]

- (void)startObserving
{
[[NSNotificationCenter defaultCenter] addObserver:self
Expand All @@ -47,11 +69,33 @@ - (void)stopObserving
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

#else // [macOS

- (void)startObserving
{
moduleInitalized = YES;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleOpenURLNotification:)
name:RCTOpenURLNotification
object:nil];
}

- (void)stopObserving
{
moduleInitalized = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

#endif // macOS]

- (NSArray<NSString *> *)supportedEvents
{
return @[ @"url" ];
}

#if !TARGET_OS_OSX // [macOS]

+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)URL
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
Expand Down Expand Up @@ -87,6 +131,43 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
[self sendEventWithName:@"url" body:notification.userInfo];
}

#else // [macOS

+ (void)setAlwaysForegroundLastWindow:(BOOL)alwaysForeground
{
alwaysForegroundLastWindow = alwaysForeground;
}

+ (void)getUrlEventHandler:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
// extract url value from the event
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];

// If the application was launched via URL, this handler will be called before
// the module is initialized by the bridge. Store the initial URL, because we are not listening to the notification yet.
if (!moduleInitalized && initialURL == nil) {
initialURL = url;
}

postNotificationWithURL(url, self);
}

- (void)handleOpenURLNotification:(NSNotification *)notification
{
// Activate app, because [NSApp mainWindow] returns nil when the app is hidden and another app is maximized
[NSApp activateIgnoringOtherApps:YES];
// foreground top level window
if (alwaysForegroundLastWindow) {
NSWindow *lastWindow = [[NSApp windows] lastObject];
[lastWindow makeKeyAndOrderFront:nil];
}
[self sendEventWithName:@"url" body:notification.userInfo];
}

#endif // macOS]

#if !TARGET_OS_OSX // [macOS]

RCT_EXPORT_METHOD(openURL
: (NSURL *)URL resolve
: (RCTPromiseResolveBlock)resolve reject
Expand Down Expand Up @@ -184,6 +265,44 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
}];
}

#else // [macOS

RCT_EXPORT_METHOD(openURL:(NSURL *)URL
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
BOOL result = [[NSWorkspace sharedWorkspace] openURL:URL];
if (result) {
resolve(@YES);
} else {
reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@", URL], nil);
}
}

RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
resolve:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject)
{
resolve(@YES);
}

RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject)
{
resolve(RCTNullIfNil(initialURL));
}

RCT_EXPORT_METHOD(openSettings:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject)
{
// macOS doesn't have a direct equivalent of UIApplicationOpenSettingsURLString
// Open System Preferences instead
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"x-apple.systempreferences:"]];
resolve(nil);
}

#endif // macOS]

RCT_EXPORT_METHOD(sendIntent
: (NSString *)action extras
: (NSArray *_Nullable)extras resolve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ Pod::Spec.new do |s|
s.compiler_flags = '-Wno-nullability-completeness'
s.source = source
s.source_files = podspec_sources("*.{m,mm}", "")
# [macOS
s.osx.exclude_files = "RCTLinkingManager.mm"
s.osx.source_files = "macos/RCTLinkingManager.mm"
# macOS]
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTLinking"
s.pod_target_xcconfig = {
Expand Down

This file was deleted.

Loading
Loading