Skip to content

Commit c9a2a6d

Browse files
committed
feat(macos): implement macOS support
1 parent 8d2f1e7 commit c9a2a6d

File tree

4 files changed

+85
-15
lines changed

4 files changed

+85
-15
lines changed

docs/docs/usage/config.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ See specific example [configurations for your provider](/docs/category/providers
4646
- **useNonce** - (`boolean`) (default: true) optionally allows not sending the nonce parameter, to support non-compliant providers. To specify custom nonce, provide it in `additionalParameters` under the `nonce` key.
4747
- **usePKCE** - (`boolean`) (default: true) optionally allows not sending the code_challenge parameter and skipping PKCE code verification, to support non-compliant providers.
4848
- **skipCodeExchange** - (`boolean`) (default: false) just return the authorization response, instead of automatically exchanging the authorization code. This is useful if this exchange needs to be done manually (not client-side)
49-
- **iosCustomBrowser** - (`string`) (default: undefined) _IOS_ override the used browser for authorization, used to open an external browser. If no value is provided, the `ASWebAuthenticationSession` or `SFSafariViewController` are used by the `AppAuth-iOS` library.
50-
- **iosPrefersEphemeralSession** - (`boolean`) (default: `false`) _IOS_ indicates whether the session should ask the browser for a private authentication session.
49+
- **iosCustomBrowser** - (`string | null`) (default: `null`) _IOS_ override the used browser for authorization, used to open an external browser. If no value is provided, the `ASWebAuthenticationSession` or `SFSafariViewController` are used by the `AppAuth-iOS` library. On Mac Catalyst and macOS AppKit, this option has no effect and is implicitly treated as `null`.
50+
- **iosPrefersEphemeralSession** - (`boolean`) (default: `false`) _IOS_ and _MACOS_ indicates whether the session should ask the browser for a private authentication session.
5151
- **androidAllowCustomBrowsers** - (`string[]`) (default: undefined) _ANDROID_ override the used browser for authorization. If no value is provided, all browsers are allowed.
5252
- **androidTrustedWebActivity** - (`boolean`) (default: `false`) _ANDROID_ Use [`EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY`](https://developer.chrome.com/docs/android/trusted-web-activity/) when opening web view.
5353
- **connectionTimeoutSeconds** - (`number`) configure the request timeout interval in seconds. This must be a positive number. The default values are 60 seconds on iOS and 15 seconds on Android.

packages/react-native-app-auth/index.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ export const DEFAULT_TIMEOUT_ANDROID = 15;
8989

9090
const convertTimeoutForPlatform = (
9191
platform,
92-
connectionTimeout = Platform.OS === 'ios' ? DEFAULT_TIMEOUT_IOS : DEFAULT_TIMEOUT_ANDROID
92+
connectionTimeout = Platform.OS === 'ios' || Platform.OS === 'macos'
93+
? DEFAULT_TIMEOUT_IOS
94+
: DEFAULT_TIMEOUT_ANDROID
9395
) => (platform === 'android' ? connectionTimeout * SECOND_IN_MS : connectionTimeout);
9496

9597
export const prefetchConfiguration = async ({
@@ -185,7 +187,7 @@ export const register = ({
185187
nativeMethodArguments.push(customHeaders);
186188
}
187189

188-
if (Platform.OS === 'ios') {
190+
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
189191
nativeMethodArguments.push(additionalHeaders);
190192
}
191193

@@ -243,11 +245,11 @@ export const authorize = ({
243245
nativeMethodArguments.push(androidTrustedWebActivity);
244246
}
245247

246-
if (Platform.OS === 'ios') {
248+
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
247249
nativeMethodArguments.push(additionalHeaders);
248250
nativeMethodArguments.push(useNonce);
249251
nativeMethodArguments.push(usePKCE);
250-
nativeMethodArguments.push(iosCustomBrowser);
252+
nativeMethodArguments.push(Platform.OS === 'ios' ? iosCustomBrowser : null);
251253
nativeMethodArguments.push(iosPrefersEphemeralSession);
252254
}
253255

@@ -301,9 +303,9 @@ export const refresh = (
301303
nativeMethodArguments.push(androidAllowCustomBrowsers);
302304
}
303305

304-
if (Platform.OS === 'ios') {
306+
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
305307
nativeMethodArguments.push(additionalHeaders);
306-
nativeMethodArguments.push(iosCustomBrowser);
308+
nativeMethodArguments.push(Platform.OS === 'ios' ? iosCustomBrowser : null);
307309
}
308310

309311
return RNAppAuth.refresh(...nativeMethodArguments);
@@ -382,8 +384,8 @@ export const logout = (
382384
nativeMethodArguments.push(androidAllowCustomBrowsers);
383385
}
384386

385-
if (Platform.OS === 'ios') {
386-
nativeMethodArguments.push(iosCustomBrowser);
387+
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
388+
nativeMethodArguments.push(Platform.OS === 'ios' ? iosCustomBrowser : null);
387389
nativeMethodArguments.push(iosPrefersEphemeralSession);
388390
}
389391

packages/react-native-app-auth/ios/RNAppAuth.m

+71-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ - (dispatch_queue_t)methodQueue
2424
return dispatch_get_main_queue();
2525
}
2626

27-
UIBackgroundTaskIdentifier rnAppAuthTaskId;
27+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
28+
UIBackgroundTaskIdentifier rnAppAuthTaskId;
29+
#endif
2830

2931
/*! @brief Number of random bytes generated for the @ state.
3032
*/
@@ -356,32 +358,51 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
356358
additionalParameters:additionalParameters];
357359

358360
// performs authentication request
361+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
359362
id<UIApplicationDelegate, RNAppAuthAuthorizationFlowManager> appDelegate = (id<UIApplicationDelegate, RNAppAuthAuthorizationFlowManager>)[UIApplication sharedApplication].delegate;
363+
#elif TARGET_OS_OSX
364+
id<NSApplicationDelegate, RNAppAuthAuthorizationFlowManager> appDelegate = (id<NSApplicationDelegate, RNAppAuthAuthorizationFlowManager>)[NSApplication sharedApplication].delegate;
365+
#endif
360366
if (![[appDelegate class] conformsToProtocol:@protocol(RNAppAuthAuthorizationFlowManager)]) {
361367
[NSException raise:@"RNAppAuth Missing protocol conformance"
362368
format:@"%@ does not conform to RNAppAuthAuthorizationFlowManager", appDelegate];
363369
}
364370
appDelegate.authorizationFlowManagerDelegate = self;
365371
__weak typeof(self) weakSelf = self;
366372

373+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
367374
rnAppAuthTaskId = [UIApplication.sharedApplication beginBackgroundTaskWithExpirationHandler:^{
368375
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
369376
rnAppAuthTaskId = UIBackgroundTaskInvalid;
370377
}];
371378

372379
UIViewController *presentingViewController = appDelegate.window.rootViewController.view.window ? appDelegate.window.rootViewController : appDelegate.window.rootViewController.presentedViewController;
380+
#elif TARGET_OS_OSX
381+
NSWindow *presentingWindow = [NSApplication sharedApplication].mainWindow;
382+
if(!presentingWindow){
383+
reject(@"authentication_failed", @"Unable to get the main window to present an authorization request. Try authenticating again with the main window open.", nil);
384+
return;
385+
}
386+
#endif
373387

374388
#if TARGET_OS_MACCATALYST
375389
id<OIDExternalUserAgent> externalUserAgent = nil;
376390
#elif TARGET_OS_IOS
377391
id<OIDExternalUserAgent> externalUserAgent = iosCustomBrowser != nil ? [self getCustomBrowser: iosCustomBrowser] : nil;
392+
#elif TARGET_OS_OSX
393+
id<OIDExternalUserAgent> externalUserAgent = nil;
378394
#endif
379395

380396
OIDAuthorizationCallback callback = ^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) {
381397
typeof(self) strongSelf = weakSelf;
382398
strongSelf->_currentSession = nil;
399+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
383400
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
384401
rnAppAuthTaskId = UIBackgroundTaskInvalid;
402+
#elif TARGET_OS_OSX
403+
// Brings this app to the foreground.
404+
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows)];
405+
#endif
385406
if (authorizationResponse) {
386407
resolve([self formatAuthorizationResponse:authorizationResponse withCodeVerifier:codeVerifier]);
387408
} else {
@@ -397,6 +418,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
397418
externalUserAgent:externalUserAgent
398419
callback:callback];
399420
} else {
421+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
400422
if (@available(iOS 13, *)) {
401423
_currentSession = [OIDAuthorizationService presentAuthorizationRequest:request
402424
presentingViewController:presentingViewController
@@ -407,6 +429,12 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
407429
presentingViewController:presentingViewController
408430
callback:callback];
409431
}
432+
#elif TARGET_OS_OSX
433+
_currentSession = [OIDAuthorizationService presentAuthorizationRequest:request
434+
presentingWindow:presentingWindow
435+
prefersEphemeralSession:prefersEphemeralSession
436+
callback:callback];
437+
#endif
410438
}
411439
} else {
412440
OIDAuthStateAuthorizationCallback callback = ^(
@@ -415,8 +443,13 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
415443
) {
416444
typeof(self) strongSelf = weakSelf;
417445
strongSelf->_currentSession = nil;
446+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
418447
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
419448
rnAppAuthTaskId = UIBackgroundTaskInvalid;
449+
#elif TARGET_OS_OSX
450+
// Brings this app to the foreground.
451+
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows)];
452+
#endif
420453
if (authState) {
421454
resolve([self formatResponse:authState.lastTokenResponse
422455
withAuthResponse:authState.lastAuthorizationResponse]);
@@ -433,6 +466,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
433466
} else {
434467

435468

469+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
436470
if (@available(iOS 13, *)) {
437471
_currentSession = [OIDAuthState authStateByPresentingAuthorizationRequest:request
438472
presentingViewController:presentingViewController
@@ -443,6 +477,11 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
443477
presentingViewController:presentingViewController
444478
callback:callback];
445479
}
480+
#elif TARGET_OS_OSX
481+
_currentSession = [OIDAuthState authStateByPresentingAuthorizationRequest:request
482+
presentingWindow:presentingWindow
483+
callback:callback];
484+
#endif
446485
}
447486
}
448487
}
@@ -499,35 +538,54 @@ - (void)endSessionWithConfiguration: (OIDServiceConfiguration *) configuration
499538
postLogoutRedirectURL: [NSURL URLWithString:postLogoutRedirectURL]
500539
additionalParameters: additionalParameters];
501540

541+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
502542
id<UIApplicationDelegate, RNAppAuthAuthorizationFlowManager> appDelegate = (id<UIApplicationDelegate, RNAppAuthAuthorizationFlowManager>)[UIApplication sharedApplication].delegate;
543+
#elif TARGET_OS_OSX
544+
id<NSApplicationDelegate, RNAppAuthAuthorizationFlowManager> appDelegate = (id<NSApplicationDelegate, RNAppAuthAuthorizationFlowManager>)[NSApplication sharedApplication].delegate;
545+
#endif
503546
if (![[appDelegate class] conformsToProtocol:@protocol(RNAppAuthAuthorizationFlowManager)]) {
504547
[NSException raise:@"RNAppAuth Missing protocol conformance"
505548
format:@"%@ does not conform to RNAppAuthAuthorizationFlowManager", appDelegate];
506549
}
507550
appDelegate.authorizationFlowManagerDelegate = self;
508551
__weak typeof(self) weakSelf = self;
509552

553+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
510554
rnAppAuthTaskId = [UIApplication.sharedApplication beginBackgroundTaskWithExpirationHandler:^{
511555
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
512556
rnAppAuthTaskId = UIBackgroundTaskInvalid;
513557
}];
514558

515559
UIViewController *presentingViewController = appDelegate.window.rootViewController.view.window ? appDelegate.window.rootViewController : appDelegate.window.rootViewController.presentedViewController;
560+
#elif TARGET_OS_OSX
561+
NSWindow *presentingWindow = [NSApplication sharedApplication].mainWindow;
562+
if(!presentingWindow){
563+
reject(@"authentication_failed", @"Unable to get the main window to present an authorization request. Try authenticating again with the main window open.", nil);
564+
return;
565+
}
566+
#endif
516567

517568
#if TARGET_OS_MACCATALYST
518569
id<OIDExternalUserAgent> externalUserAgent = nil;
519570
#elif TARGET_OS_IOS
520571
id<OIDExternalUserAgent> externalUserAgent = iosCustomBrowser != nil ? [self getCustomBrowser: iosCustomBrowser] : [self getExternalUserAgentWithPresentingViewController:presentingViewController
521572
prefersEphemeralSession:prefersEphemeralSession];
573+
#elif TARGET_OS_OSX
574+
id<OIDExternalUserAgent> externalUserAgent = nil;
522575
#endif
523576

524577
_currentSession = [OIDAuthorizationService presentEndSessionRequest: endSessionRequest
525578
externalUserAgent: externalUserAgent
526579
callback: ^(OIDEndSessionResponse *_Nullable response, NSError *_Nullable error) {
527580
typeof(self) strongSelf = weakSelf;
528581
strongSelf->_currentSession = nil;
582+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
529583
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
530584
rnAppAuthTaskId = UIBackgroundTaskInvalid;
585+
#elif TARGET_OS_OSX
586+
// Brings this app to the foreground.
587+
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows)];
588+
#endif
531589
if (response) {
532590
resolve([self formatEndSessionResponse:response]);
533591
} else {
@@ -694,10 +752,10 @@ - (NSString*)getErrorCode: (NSError*) error defaultCode: (NSString *) defaultCod
694752
return defaultCode;
695753
}
696754

697-
#if !TARGET_OS_MACCATALYST
755+
#if TARGET_OS_IOS
698756
- (id<OIDExternalUserAgent>)getCustomBrowser: (NSString *) browserType {
699757
typedef id<OIDExternalUserAgent> (^BrowserBlock)(void);
700-
758+
701759
NSDictionary *browsers = @{
702760
@"safari":
703761
^{
@@ -733,8 +791,13 @@ - (NSString*)getErrorMessage: (NSError*) error {
733791
}
734792
}
735793

794+
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
736795
- (id<OIDExternalUserAgent>)getExternalUserAgentWithPresentingViewController: (UIViewController *)presentingViewController
737796
prefersEphemeralSession: (BOOL) prefersEphemeralSession
797+
#elif TARGET_OS_OSX
798+
- (id<OIDExternalUserAgent>)getExternalUserAgentWithPresentingWindow: (NSWindow *)presentingWindow
799+
prefersEphemeralSession: (BOOL) prefersEphemeralSession
800+
#endif
738801
{
739802
id<OIDExternalUserAgent> externalUserAgent;
740803
#if TARGET_OS_MACCATALYST
@@ -749,7 +812,11 @@ - (NSString*)getErrorMessage: (NSError*) error {
749812
presentingViewController];
750813
}
751814
#elif TARGET_OS_OSX
752-
externalUserAgent = [[OIDExternalUserAgentMac alloc] init];
815+
if (@available(macOS 10.15, *)) {
816+
externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow];
817+
} else {
818+
externalUserAgent = [[OIDExternalUserAgentMac alloc] init];
819+
}
753820
#endif
754821
return externalUserAgent;
755822
}

packages/react-native-app-auth/react-native-app-auth.podspec

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ Pod::Spec.new do |s|
99
s.license = package['license']
1010
s.authors = package['author']
1111
s.homepage = package['homepage']
12-
s.platform = :ios, '10.0'
12+
s.ios.deployment_target = '10.0'
13+
s.osx.deployment_target = '10.15'
1314
s.source = { :git => 'https://github.com/FormidableLabs/react-native-app-auth.git', :tag => "v#{s.version}" }
1415
s.source_files = 'ios/**/*.{h,m}'
1516
s.requires_arc = true

0 commit comments

Comments
 (0)