Skip to content

Commit

Permalink
[google_sign_in] Add macOS support (#4962)
Browse files Browse the repository at this point in the history
Adds macOS support to `google_sign_in_ios` by moving the existing source to `darwin/`, adjusting it somewhat to be macOS-compatible, and enabling `sharedDarwinSource` in the podspec.

Unlike previous cases of code sharing, this doesn't rename the package to be something more generic, since when we do that the name is based on the underlying SDK, and the SDK is still mostly called "Google Sign-In for iOS", so I don't think there's a better name to give it at the moment.

Most of flutter/flutter#46157
(Once this lands, there will be a follow-up PR to update the app-facing package to endorse it and list it in the README.)
  • Loading branch information
stuartmorgan authored Dec 6, 2023
1 parent cc11b14 commit afd4517
Show file tree
Hide file tree
Showing 47 changed files with 1,931 additions and 273 deletions.
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
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

0 comments on commit afd4517

Please sign in to comment.