Skip to content

Commit

Permalink
feat(macos): implement macOS support
Browse files Browse the repository at this point in the history
  • Loading branch information
shirakaba committed Aug 18, 2024
1 parent 52432e8 commit 189ed8f
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 15 deletions.
4 changes: 2 additions & 2 deletions docs/docs/usage/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ See specific example [configurations for your provider](/docs/category/providers
- **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.
- **usePKCE** - (`boolean`) (default: true) optionally allows not sending the code_challenge parameter and skipping PKCE code verification, to support non-compliant providers.
- **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)
- **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.
- **iosPrefersEphemeralSession** - (`boolean`) (default: `false`) _IOS_ indicates whether the session should ask the browser for a private authentication session.
- **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`.
- **iosPrefersEphemeralSession** - (`boolean`) (default: `false`) _IOS_ and _MACOS_ indicates whether the session should ask the browser for a private authentication session.
- **androidAllowCustomBrowsers** - (`string[]`) (default: undefined) _ANDROID_ override the used browser for authorization. If no value is provided, all browsers are allowed.
- **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.
- **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.
18 changes: 10 additions & 8 deletions packages/react-native-app-auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ export const DEFAULT_TIMEOUT_ANDROID = 15;

const convertTimeoutForPlatform = (
platform,
connectionTimeout = Platform.OS === 'ios' ? DEFAULT_TIMEOUT_IOS : DEFAULT_TIMEOUT_ANDROID
connectionTimeout = Platform.OS === 'ios' || Platform.OS === 'macos'
? DEFAULT_TIMEOUT_IOS
: DEFAULT_TIMEOUT_ANDROID
) => (platform === 'android' ? connectionTimeout * SECOND_IN_MS : connectionTimeout);

export const prefetchConfiguration = async ({
Expand Down Expand Up @@ -187,7 +189,7 @@ export const register = ({
nativeMethodArguments.push(customHeaders);
}

if (Platform.OS === 'ios') {
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
nativeMethodArguments.push(additionalHeaders);
}

Expand Down Expand Up @@ -245,11 +247,11 @@ export const authorize = ({
nativeMethodArguments.push(androidTrustedWebActivity);
}

if (Platform.OS === 'ios') {
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
nativeMethodArguments.push(additionalHeaders);
nativeMethodArguments.push(useNonce);
nativeMethodArguments.push(usePKCE);
nativeMethodArguments.push(iosCustomBrowser);
nativeMethodArguments.push(Platform.OS === 'ios' ? iosCustomBrowser : null);
nativeMethodArguments.push(iosPrefersEphemeralSession);
}

Expand Down Expand Up @@ -303,9 +305,9 @@ export const refresh = (
nativeMethodArguments.push(androidAllowCustomBrowsers);
}

if (Platform.OS === 'ios') {
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
nativeMethodArguments.push(additionalHeaders);
nativeMethodArguments.push(iosCustomBrowser);
nativeMethodArguments.push(Platform.OS === 'ios' ? iosCustomBrowser : null);
}

return RNAppAuth.refresh(...nativeMethodArguments);
Expand Down Expand Up @@ -384,8 +386,8 @@ export const logout = (
nativeMethodArguments.push(androidAllowCustomBrowsers);
}

if (Platform.OS === 'ios') {
nativeMethodArguments.push(iosCustomBrowser);
if (Platform.OS === 'ios' || Platform.OS === 'macos') {
nativeMethodArguments.push(Platform.OS === 'ios' ? iosCustomBrowser : null);
nativeMethodArguments.push(iosPrefersEphemeralSession);
}

Expand Down
75 changes: 71 additions & 4 deletions packages/react-native-app-auth/ios/RNAppAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ - (dispatch_queue_t)methodQueue
return dispatch_get_main_queue();
}

UIBackgroundTaskIdentifier rnAppAuthTaskId;
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
UIBackgroundTaskIdentifier rnAppAuthTaskId;
#endif

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

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

#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
rnAppAuthTaskId = [UIApplication.sharedApplication beginBackgroundTaskWithExpirationHandler:^{
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
rnAppAuthTaskId = UIBackgroundTaskInvalid;
}];

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

#if TARGET_OS_MACCATALYST
id<OIDExternalUserAgent> externalUserAgent = nil;
#elif TARGET_OS_IOS
id<OIDExternalUserAgent> externalUserAgent = iosCustomBrowser != nil ? [self getCustomBrowser: iosCustomBrowser] : nil;
#elif TARGET_OS_OSX
id<OIDExternalUserAgent> externalUserAgent = nil;
#endif

OIDAuthorizationCallback callback = ^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) {
typeof(self) strongSelf = weakSelf;
strongSelf->_currentSession = nil;
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
rnAppAuthTaskId = UIBackgroundTaskInvalid;
#elif TARGET_OS_OSX
// Brings this app to the foreground.
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows)];
#endif
if (authorizationResponse) {
resolve([self formatAuthorizationResponse:authorizationResponse withCodeVerifier:codeVerifier]);
} else {
Expand All @@ -397,6 +418,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
externalUserAgent:externalUserAgent
callback:callback];
} else {
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
if (@available(iOS 13, *)) {
_currentSession = [OIDAuthorizationService presentAuthorizationRequest:request
presentingViewController:presentingViewController
Expand All @@ -407,6 +429,12 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
presentingViewController:presentingViewController
callback:callback];
}
#elif TARGET_OS_OSX
_currentSession = [OIDAuthorizationService presentAuthorizationRequest:request
presentingWindow:presentingWindow
prefersEphemeralSession:prefersEphemeralSession
callback:callback];
#endif
}
} else {
OIDAuthStateAuthorizationCallback callback = ^(
Expand All @@ -415,8 +443,13 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
) {
typeof(self) strongSelf = weakSelf;
strongSelf->_currentSession = nil;
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
rnAppAuthTaskId = UIBackgroundTaskInvalid;
#elif TARGET_OS_OSX
// Brings this app to the foreground.
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows)];
#endif
if (authState) {
resolve([self formatResponse:authState.lastTokenResponse
withAuthResponse:authState.lastAuthorizationResponse]);
Expand All @@ -433,6 +466,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
} else {


#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
if (@available(iOS 13, *)) {
_currentSession = [OIDAuthState authStateByPresentingAuthorizationRequest:request
presentingViewController:presentingViewController
Expand All @@ -443,6 +477,11 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
presentingViewController:presentingViewController
callback:callback];
}
#elif TARGET_OS_OSX
_currentSession = [OIDAuthState authStateByPresentingAuthorizationRequest:request
presentingWindow:presentingWindow
callback:callback];
#endif
}
}
}
Expand Down Expand Up @@ -499,35 +538,54 @@ - (void)endSessionWithConfiguration: (OIDServiceConfiguration *) configuration
postLogoutRedirectURL: [NSURL URLWithString:postLogoutRedirectURL]
additionalParameters: additionalParameters];

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

#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
rnAppAuthTaskId = [UIApplication.sharedApplication beginBackgroundTaskWithExpirationHandler:^{
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
rnAppAuthTaskId = UIBackgroundTaskInvalid;
}];

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

#if TARGET_OS_MACCATALYST
id<OIDExternalUserAgent> externalUserAgent = nil;
#elif TARGET_OS_IOS
id<OIDExternalUserAgent> externalUserAgent = iosCustomBrowser != nil ? [self getCustomBrowser: iosCustomBrowser] : [self getExternalUserAgentWithPresentingViewController:presentingViewController
prefersEphemeralSession:prefersEphemeralSession];
#elif TARGET_OS_OSX
id<OIDExternalUserAgent> externalUserAgent = nil;
#endif

_currentSession = [OIDAuthorizationService presentEndSessionRequest: endSessionRequest
externalUserAgent: externalUserAgent
callback: ^(OIDEndSessionResponse *_Nullable response, NSError *_Nullable error) {
typeof(self) strongSelf = weakSelf;
strongSelf->_currentSession = nil;
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
[UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId];
rnAppAuthTaskId = UIBackgroundTaskInvalid;
#elif TARGET_OS_OSX
// Brings this app to the foreground.
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows)];
#endif
if (response) {
resolve([self formatEndSessionResponse:response]);
} else {
Expand Down Expand Up @@ -694,10 +752,10 @@ - (NSString*)getErrorCode: (NSError*) error defaultCode: (NSString *) defaultCod
return defaultCode;
}

#if !TARGET_OS_MACCATALYST
#if TARGET_OS_IOS
- (id<OIDExternalUserAgent>)getCustomBrowser: (NSString *) browserType {
typedef id<OIDExternalUserAgent> (^BrowserBlock)(void);

NSDictionary *browsers = @{
@"safari":
^{
Expand Down Expand Up @@ -733,8 +791,13 @@ - (NSString*)getErrorMessage: (NSError*) error {
}
}

#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
- (id<OIDExternalUserAgent>)getExternalUserAgentWithPresentingViewController: (UIViewController *)presentingViewController
prefersEphemeralSession: (BOOL) prefersEphemeralSession
#elif TARGET_OS_OSX
- (id<OIDExternalUserAgent>)getExternalUserAgentWithPresentingWindow: (NSWindow *)presentingWindow
prefersEphemeralSession: (BOOL) prefersEphemeralSession
#endif
{
id<OIDExternalUserAgent> externalUserAgent;
#if TARGET_OS_MACCATALYST
Expand All @@ -749,7 +812,11 @@ - (NSString*)getErrorMessage: (NSError*) error {
presentingViewController];
}
#elif TARGET_OS_OSX
externalUserAgent = [[OIDExternalUserAgentMac alloc] init];
if (@available(macOS 10.15, *)) {
externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow];
} else {
externalUserAgent = [[OIDExternalUserAgentMac alloc] init];
}
#endif
return externalUserAgent;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/react-native-app-auth/react-native-app-auth.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Pod::Spec.new do |s|
s.license = package['license']
s.authors = package['author']
s.homepage = package['homepage']
s.platform = :ios, '10.0'
s.ios.deployment_target = '10.0'
s.osx.deployment_target = '10.15'
s.source = { :git => 'https://github.com/FormidableLabs/react-native-app-auth.git', :tag => "v#{s.version}" }
s.source_files = 'ios/**/*.{h,m}'
s.requires_arc = true
Expand Down

0 comments on commit 189ed8f

Please sign in to comment.