Skip to content

[google_sign_in] Add macOS support #4962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Dec 6, 2023
Merged
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
10 changes: 10 additions & 0 deletions .ci/scripts/update_pods.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
set -e

# Ensure that the pods repos are up to date, since analyze will not check for
# the latest versions of pods, so can use stale Flutter or FlutterMacOS pods
# for analysis otherwise.
pod repo update --verbose
3 changes: 3 additions & 0 deletions .ci/targets/macos_check_podspecs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ tasks:
- name: prepare tool
script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running.
- name: update pods repo
script: .ci/scripts/update_pods.sh
infra_step: true # Note infra steps failing prevents "always" from running.
- name: validate iOS and macOS podspecs
script: script/tool_runner.sh
args: ["podspec-check"]
5 changes: 3 additions & 2 deletions packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT
## 5.7.0

* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
* Adds support for macOS.
* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.

## 5.6.5

Expand Down
17 changes: 16 additions & 1 deletion packages/google_sign_in/google_sign_in_ios/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# google\_sign\_in\_ios

The iOS implementation of [`google_sign_in`][1].
The iOS and macOS implementation of [`google_sign_in`][1].

## Usage

Expand All @@ -11,6 +11,21 @@ so you do not need to add it to your `pubspec.yaml`.
However, if you `import` this package to use any of its APIs directly, you
should add it to your `pubspec.yaml` as usual.

### macOS setup

The GoogleSignIn SDK requires keychain sharing to be enabled, by [adding the
following entitlements](https://docs.flutter.dev/platform-integration/macos/building#entitlements-and-the-app-sandbox):

```xml
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.google.GIDSignIn</string>
</array>
```

Without this step, the plugin will throw a `keychain error` `PlatformException`
when trying to sign in.

[1]: https://pub.dev/packages/google_sign_in
[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#if TARGET_OS_OSX
#import <FlutterMacOS/FlutterMacOS.h>
#else
#import <Flutter/Flutter.h>
#endif

#import "messages.g.h"

@interface FLTGoogleSignInPlugin : NSObject <FlutterPlugin, FSIGoogleSignInApi>

- (instancetype)init NS_UNAVAILABLE;
@end
Original file line number Diff line number Diff line change
Expand Up @@ -50,32 +50,37 @@ @interface FLTGoogleSignInPlugin ()
// The contents of GoogleService-Info.plist, if it exists.
@property(strong, nullable) NSDictionary<NSString *, id> *googleServiceProperties;

// Redeclared as not a designated initializer.
- (instancetype)init;
// The plugin registrar, for querying views.
@property(strong, nonnull) id<FlutterPluginRegistrar> registrar;

@end

@implementation FLTGoogleSignInPlugin

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FLTGoogleSignInPlugin *instance = [[FLTGoogleSignInPlugin alloc] init];
FLTGoogleSignInPlugin *instance = [[FLTGoogleSignInPlugin alloc] initWithRegistrar:registrar];
[registrar addApplicationDelegate:instance];
FSIGoogleSignInApiSetup(registrar.messenger, instance);
}

- (instancetype)init {
return [self initWithSignIn:GIDSignIn.sharedInstance];
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
return [self initWithSignIn:GIDSignIn.sharedInstance registrar:registrar];
}

- (instancetype)initWithSignIn:(GIDSignIn *)signIn {
return [self initWithSignIn:signIn withGoogleServiceProperties:loadGoogleServiceInfo()];
- (instancetype)initWithSignIn:(GIDSignIn *)signIn
registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
return [self initWithSignIn:signIn
registrar:registrar
googleServiceProperties:loadGoogleServiceInfo()];
}

- (instancetype)initWithSignIn:(GIDSignIn *)signIn
withGoogleServiceProperties:(nullable NSDictionary<NSString *, id> *)googleServiceProperties {
registrar:(NSObject<FlutterPluginRegistrar> *)registrar
googleServiceProperties:(nullable NSDictionary<NSString *, id> *)googleServiceProperties {
self = [super init];
if (self) {
_signIn = signIn;
_registrar = registrar;
_googleServiceProperties = googleServiceProperties;

// On the iOS simulator, we get "Broken pipe" errors after sign-in for some
Expand All @@ -88,9 +93,19 @@ - (instancetype)initWithSignIn:(GIDSignIn *)signIn

#pragma mark - <FlutterPlugin> protocol

#if TARGET_OS_IOS
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
return [self.signIn handleURL:url];
}
#else
- (BOOL)handleOpenURLs:(NSArray<NSURL *> *)urls {
BOOL handled = NO;
for (NSURL *url in urls) {
handled = handled || [self.signIn handleURL:url];
}
return handled;
}
#endif

#pragma mark - FSIGoogleSignInApi

Expand Down Expand Up @@ -134,23 +149,21 @@ - (void)signInWithCompletion:(nonnull void (^)(FSIUserData *_Nullable,
self.signIn.configuration = self.configuration;
}

[self.signIn signInWithPresentingViewController:[self topViewController]
hint:nil
additionalScopes:self.requestedScopes.allObjects
completion:^(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error) {
GIDGoogleUser *user;
NSString *serverAuthCode;
if (signInResult) {
user = signInResult.user;
serverAuthCode = signInResult.serverAuthCode;
}

[self didSignInForUser:user
withServerAuthCode:serverAuthCode
completion:completion
error:error];
}];
[self signInWithHint:nil
additionalScopes:self.requestedScopes.allObjects
completion:^(GIDSignInResult *_Nullable signInResult, NSError *_Nullable error) {
GIDGoogleUser *user;
NSString *serverAuthCode;
if (signInResult) {
user = signInResult.user;
serverAuthCode = signInResult.serverAuthCode;
}

[self didSignInForUser:user
withServerAuthCode:serverAuthCode
completion:completion
error:error];
}];
} @catch (NSException *e) {
completion(nil, [FlutterError errorWithCode:@"google_sign_in" message:e.reason details:e.name]);
[e raise];
Expand Down Expand Up @@ -196,51 +209,67 @@ - (void)requestScopes:(nonnull NSArray<NSString *> *)scopes
message:@"No account to grant scopes."
details:nil]);
}
[currentUser addScopes:requestedScopes.allObjects
presentingViewController:[self topViewController]
completion:^(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable addedScopeError) {
BOOL granted = NO;
FlutterError *error = nil;

if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] &&
addedScopeError.code == kGIDSignInErrorCodeMismatchWithCurrentUser) {
error =
[FlutterError errorWithCode:@"mismatch_user"
message:@"There is an operation on a previous "
@"user. Try signing in again."
details:nil];
} else if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] &&
addedScopeError.code ==
kGIDSignInErrorCodeScopesAlreadyGranted) {
// Scopes already granted, report success.
granted = YES;
} else if (signInResult.user) {
NSSet<NSString *> *grantedScopes =
[NSSet setWithArray:signInResult.user.grantedScopes];
granted = [requestedScopes isSubsetOfSet:grantedScopes];
}
completion(error == nil ? @(granted) : nil, error);
}];
[self addScopes:requestedScopes.allObjects
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason it looks like there are a lot of changes here (and in the previous block) is that switching to the new helper function without presentingViewController changed the indentation of the whole block. The code in the block doesn't have any non-whitespace changes.

completion:^(GIDSignInResult *_Nullable signInResult, NSError *_Nullable addedScopeError) {
BOOL granted = NO;
FlutterError *error = nil;

if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] &&
addedScopeError.code == kGIDSignInErrorCodeMismatchWithCurrentUser) {
error = [FlutterError errorWithCode:@"mismatch_user"
message:@"There is an operation on a previous "
@"user. Try signing in again."
details:nil];
} else if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] &&
addedScopeError.code == kGIDSignInErrorCodeScopesAlreadyGranted) {
// Scopes already granted, report success.
granted = YES;
} else if (signInResult.user) {
NSSet<NSString *> *grantedScopes =
[NSSet setWithArray:signInResult.user.grantedScopes];
granted = [requestedScopes isSubsetOfSet:grantedScopes];
}
completion(error == nil ? @(granted) : nil, error);
}];
} @catch (NSException *e) {
completion(nil, [FlutterError errorWithCode:@"request_scopes" message:e.reason details:e.name]);
}
}

#pragma mark - <GIDSignInUIDelegate> protocol
#pragma mark - private methods

- (void)signIn:(GIDSignIn *)signIn presentViewController:(UIViewController *)viewController {
UIViewController *rootViewController =
[UIApplication sharedApplication].delegate.window.rootViewController;
[rootViewController presentViewController:viewController animated:YES completion:nil];
// Wraps the iOS and macOS sign in display methods.
- (void)signInWithHint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion {
#if TARGET_OS_OSX
[self.signIn signInWithPresentingWindow:self.registrar.view.window
hint:hint
additionalScopes:additionalScopes
completion:completion];
#else
[self.signIn signInWithPresentingViewController:[self topViewController]
hint:hint
additionalScopes:additionalScopes
completion:completion];
#endif
}

- (void)signIn:(GIDSignIn *)signIn dismissViewController:(UIViewController *)viewController {
[viewController dismissViewControllerAnimated:YES completion:nil];
// Wraps the iOS and macOS scope addition methods.
- (void)addScopes:(NSArray<NSString *> *)scopes
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion {
GIDGoogleUser *currentUser = self.signIn.currentUser;
#if TARGET_OS_OSX
[currentUser addScopes:scopes presentingWindow:self.registrar.view.window completion:completion];
#else
[currentUser addScopes:scopes
presentingViewController:[self topViewController]
completion:completion];
#endif
}

#pragma mark - private methods

/// @return @c nil if GoogleService-Info.plist not found and clientId is not provided.
- (GIDConfiguration *)configurationWithClientIdArgument:(id)clientIDArg
serverClientIdArgument:(id)serverClientIDArg
Expand Down Expand Up @@ -299,6 +328,8 @@ - (void)didSignInForUser:(GIDGoogleUser *)user
}
}

#if TARGET_OS_IOS

- (UIViewController *)topViewController {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
Expand Down Expand Up @@ -337,4 +368,6 @@ - (UIViewController *)topViewControllerFromViewController:(UIViewController *)vi
return viewController;
}

#endif

@end
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@ NS_ASSUME_NONNULL_BEGIN
// sign in, sign out, and requesting additional scopes.
@property(strong, readonly) GIDSignIn *signIn;

/// Inject @c FlutterPluginRegistrar for testing.
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar;

/// Inject @c GIDSignIn for testing.
- (instancetype)initWithSignIn:(GIDSignIn *)signIn;
- (instancetype)initWithSignIn:(GIDSignIn *)signIn
registrar:(NSObject<FlutterPluginRegistrar> *)registrar;

/// Inject @c GIDSignIn and @c googleServiceProperties for testing.
- (instancetype)initWithSignIn:(GIDSignIn *)signIn
withGoogleServiceProperties:(nullable NSDictionary<NSString *, id> *)googleServiceProperties
registrar:(NSObject<FlutterPluginRegistrar> *)registrar
googleServiceProperties:(nullable NSDictionary<NSString *, id> *)googleServiceProperties
NS_DESIGNATED_INITIALIZER;

@end
Expand Down
Loading