diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md index 0934effc6c681..4f67a64510f30 100644 --- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.6.4 + +* Converts platform communication to Pigeon. + ## 5.6.3 * Adds pub topics to package metadata. diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj index 03329ae6b843d..abbb5ef47ca1e 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -273,7 +273,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index f4569c48ce10b..8e83ef7194ee3 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ *result) { - XCTAssertEqualObjects(result[@"displayName"], [NSNull null]); - XCTAssertEqualObjects(result[@"email"], [NSNull null]); - XCTAssertEqualObjects(result[@"id"], @"mockID"); - XCTAssertEqualObjects(result[@"photoUrl"], [NSNull null]); - XCTAssertEqualObjects(result[@"serverAuthCode"], [NSNull null]); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin signInSilentlyWithCompletion:^(FSIUserData *user, FlutterError *error) { + XCTAssertNil(error); + XCTAssertNotNil(user); + XCTAssertNil(user.displayName); + XCTAssertNil(user.email); + XCTAssertEqualObjects(user.userId, @"mockID"); + XCTAssertNil(user.photoUrl); + XCTAssertNil(user.serverAuthCode); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -285,15 +217,12 @@ - (void)testSignInSilentlyWithError { [[self.mockSignIn stub] restorePreviousSignInWithCallback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signInSilently" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"sign_in_required"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin signInSilentlyWithCompletion:^(FSIUserData *user, FlutterError *error) { + XCTAssertNil(user); + XCTAssertEqualObjects(error.code, @"sign_in_required"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -323,38 +252,29 @@ - (void)testSignIn { additionalScopes:@[] callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin - handleMethodCall:methodCall - result:^(NSDictionary *result) { - XCTAssertEqualObjects(result[@"displayName"], @"mockDisplay"); - XCTAssertEqualObjects(result[@"email"], @"mock@example.com"); - XCTAssertEqualObjects(result[@"id"], @"mockID"); - XCTAssertEqualObjects(result[@"photoUrl"], @"https://example.com/profile.png"); - XCTAssertEqualObjects(result[@"serverAuthCode"], @"mockAuthCode"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { + XCTAssertNil(error); + XCTAssertEqualObjects(user.displayName, @"mockDisplay"); + XCTAssertEqualObjects(user.email, @"mock@example.com"); + XCTAssertEqualObjects(user.userId, @"mockID"); + XCTAssertEqualObjects(user.photoUrl, @"https://example.com/profile.png"); + XCTAssertEqualObjects(user.serverAuthCode, @"mockAuthCode"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; OCMVerifyAll(self.mockSignIn); } - (void)testSignInWithInitializedScopes { - FlutterMethodCall *initMethodCall = - [FlutterMethodCall methodCallWithMethodName:@"init" - arguments:@{@"scopes" : @[ @"initial1", @"initial2" ]}]; - - XCTestExpectation *initExpectation = - [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:initMethodCall - result:^(id result) { - XCTAssertNil(result); - [initExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; + FlutterError *error; + [self.plugin + initializeSignInWithParameters:[FSIInitParams makeWithScopes:@[ @"initial1", @"initial2" ] + hostedDomain:nil + clientId:nil + serverClientId:nil] + error:&error]; id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); @@ -369,15 +289,12 @@ - (void)testSignInWithInitializedScopes { }] callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(NSDictionary *result) { - XCTAssertEqualObjects(result[@"id"], @"mockID"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { + XCTAssertNil(error); + XCTAssertEqualObjects(user.userId, @"mockID"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; OCMVerifyAll(self.mockSignIn); @@ -401,15 +318,12 @@ - (void)testSignInAlreadyGranted { presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(NSDictionary *result) { - XCTAssertEqualObjects(result[@"id"], @"mockID"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { + XCTAssertNil(error); + XCTAssertEqualObjects(user.userId, @"mockID"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -424,21 +338,16 @@ - (void)testSignInError { additionalScopes:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"sign_in_canceled"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { + XCTAssertNil(user); + XCTAssertEqualObjects(error.code, @"sign_in_canceled"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testSignInException { - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" - arguments:nil]; OCMExpect([self.mockSignIn signInWithConfiguration:OCMOCK_ANY presentingViewController:OCMOCK_ANY hint:OCMOCK_ANY @@ -447,10 +356,11 @@ - (void)testSignInException { .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); __block FlutterError *error; - XCTAssertThrows([self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - error = result; - }]); + XCTAssertThrows( + [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *signInError) { + XCTAssertNil(user); + error = signInError; + }]); XCTAssertEqualObjects(error.code, @"google_sign_in"); XCTAssertEqualObjects(error.message, @"MockReason"); @@ -470,16 +380,13 @@ - (void)testGetTokens { doWithFreshTokens:[OCMArg invokeBlockWithArgs:mockAuthentication, [NSNull null], nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(NSDictionary *result) { - XCTAssertEqualObjects(result[@"idToken"], @"mockIdToken"); - XCTAssertEqualObjects(result[@"accessToken"], @"mockAccessToken"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { + XCTAssertNil(error); + XCTAssertEqualObjects(token.idToken, @"mockIdToken"); + XCTAssertEqualObjects(token.accessToken, @"mockAccessToken"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -495,16 +402,13 @@ - (void)testGetTokensNoAuthKeychainError { doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"sign_in_required"); - XCTAssertEqualObjects(result.message, kGIDSignInErrorDomain); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { + XCTAssertNil(token); + XCTAssertEqualObjects(error.code, @"sign_in_required"); + XCTAssertEqualObjects(error.message, kGIDSignInErrorDomain); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -520,16 +424,13 @@ - (void)testGetTokensCancelledError { doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"sign_in_canceled"); - XCTAssertEqualObjects(result.message, kGIDSignInErrorDomain); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { + XCTAssertNil(token); + XCTAssertEqualObjects(error.code, @"sign_in_canceled"); + XCTAssertEqualObjects(error.message, kGIDSignInErrorDomain); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -543,16 +444,13 @@ - (void)testGetTokensURLError { doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"network_error"); - XCTAssertEqualObjects(result.message, NSURLErrorDomain); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { + XCTAssertNil(token); + XCTAssertEqualObjects(error.code, @"network_error"); + XCTAssertEqualObjects(error.message, NSURLErrorDomain); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -566,16 +464,13 @@ - (void)testGetTokensUnknownError { doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" - arguments:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"sign_in_failed"); - XCTAssertEqualObjects(result.message, @"BogusDomain"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { + XCTAssertNil(token); + XCTAssertEqualObjects(error.code, @"sign_in_failed"); + XCTAssertEqualObjects(error.message, @"BogusDomain"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -589,16 +484,13 @@ - (void)testRequestScopesResultErrorIfNotSignedIn { presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - FlutterMethodCall *methodCall = - [FlutterMethodCall methodCallWithMethodName:@"requestScopes" - arguments:@{@"scopes" : @[ @"mockScope1" ]}]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"sign_in_required"); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin requestScopes:@[ @"mockScope1" ] + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(success); + XCTAssertEqualObjects(error.code, @"sign_in_required"); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -610,16 +502,13 @@ - (void)testRequestScopesIfNoMissingScope { presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - FlutterMethodCall *methodCall = - [FlutterMethodCall methodCallWithMethodName:@"requestScopes" - arguments:@{@"scopes" : @[ @"mockScope1" ]}]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(NSNumber *result) { - XCTAssertTrue(result.boolValue); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin requestScopes:@[ @"mockScope1" ] + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(error); + XCTAssertTrue(success.boolValue); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -629,31 +518,27 @@ - (void)testRequestScopesWithUnknownError { presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - FlutterMethodCall *methodCall = - [FlutterMethodCall methodCallWithMethodName:@"requestScopes" - arguments:@{@"scopes" : @[ @"mockScope1" ]}]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(NSNumber *result) { - XCTAssertFalse(result.boolValue); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin requestScopes:@[ @"mockScope1" ] + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(error); + XCTAssertFalse(success.boolValue); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestScopesException { - FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" - arguments:nil]; OCMExpect([self.mockSignIn addScopes:@[] presentingViewController:OCMOCK_ANY callback:OCMOCK_ANY]) .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); - [self.plugin handleMethodCall:methodCall - result:^(FlutterError *result) { - XCTAssertEqualObjects(result.code, @"request_scopes"); - XCTAssertEqualObjects(result.message, @"MockReason"); - XCTAssertEqualObjects(result.details, @"MockName"); - }]; + [self.plugin requestScopes:@[] + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(success); + XCTAssertEqualObjects(error.code, @"request_scopes"); + XCTAssertEqualObjects(error.message, @"MockReason"); + XCTAssertEqualObjects(error.details, @"MockName"); + }]; } - (void)testRequestScopesReturnsFalseIfOnlySubsetGranted { @@ -668,43 +553,31 @@ - (void)testRequestScopesReturnsFalseIfOnlySubsetGranted { presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; - FlutterMethodCall *methodCall = - [FlutterMethodCall methodCallWithMethodName:@"requestScopes" - arguments:@{@"scopes" : requestedScopes}]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns false"]; - [self.plugin handleMethodCall:methodCall - result:^(NSNumber *result) { - XCTAssertFalse(result.boolValue); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin requestScopes:requestedScopes + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(error); + XCTAssertFalse(success.boolValue); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestsInitializedScopes { - FlutterMethodCall *initMethodCall = - [FlutterMethodCall methodCallWithMethodName:@"init" - arguments:@{@"scopes" : @[ @"initial1", @"initial2" ]}]; - - XCTestExpectation *initExpectation = - [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:initMethodCall - result:^(id result) { - XCTAssertNil(result); - [initExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; + FSIInitParams *params = [FSIInitParams makeWithScopes:@[ @"initial1", @"initial2" ] + hostedDomain:nil + clientId:nil + serverClientId:nil]; + FlutterError *error; + [self.plugin initializeSignInWithParameters:params error:&error]; + XCTAssertNil(error); // Include one of the initially requested scopes. NSArray *addedScopes = @[ @"initial1", @"addScope1", @"addScope2" ]; - FlutterMethodCall *methodCall = - [FlutterMethodCall methodCallWithMethodName:@"requestScopes" - arguments:@{@"scopes" : addedScopes}]; - - [self.plugin handleMethodCall:methodCall - result:^(id result){ - }]; + [self.plugin requestScopes:addedScopes + completion:^(NSNumber *success, FlutterError *error){ + }]; // All four scopes are requested. [[self.mockSignIn verify] @@ -729,16 +602,13 @@ - (void)testRequestScopesReturnsTrueIfGranted { presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; - FlutterMethodCall *methodCall = - [FlutterMethodCall methodCallWithMethodName:@"requestScopes" - arguments:@{@"scopes" : requestedScopes}]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin handleMethodCall:methodCall - result:^(NSNumber *result) { - XCTAssertTrue(result.boolValue); - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin requestScopes:requestedScopes + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(error); + XCTAssertTrue(success.boolValue); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h index cb6b51aab1bfd..ec5403f0d3d6b 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h @@ -4,5 +4,7 @@ #import -@interface FLTGoogleSignInPlugin : NSObject +#import "messages.g.h" + +@interface FLTGoogleSignInPlugin : NSObject @end diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m index 0699284228637..193c15e1ee3c0 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m @@ -72,12 +72,9 @@ - (instancetype)init; @implementation FLTGoogleSignInPlugin + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/google_sign_in_ios" - binaryMessenger:[registrar messenger]]; FLTGoogleSignInPlugin *instance = [[FLTGoogleSignInPlugin alloc] init]; [registrar addApplicationDelegate:instance]; - [registrar addMethodCallDelegate:instance channel:channel]; + FSIGoogleSignInApiSetup(registrar.messenger, instance); } - (instancetype)init { @@ -105,104 +102,122 @@ - (instancetype)initWithSignIn:(GIDSignIn *)signIn #pragma mark - protocol -- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([call.method isEqualToString:@"init"]) { - GIDConfiguration *configuration = - [self configurationWithClientIdArgument:call.arguments[@"clientId"] - serverClientIdArgument:call.arguments[@"serverClientId"] - hostedDomainArgument:call.arguments[@"hostedDomain"]]; - if (configuration != nil) { - if ([call.arguments[@"scopes"] isKindOfClass:[NSArray class]]) { - self.requestedScopes = [NSSet setWithArray:call.arguments[@"scopes"]]; - } - self.configuration = configuration; - result(nil); - } else { - result([FlutterError errorWithCode:@"missing-config" +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { + return [self.signIn handleURL:url]; +} + +#pragma mark - FSIGoogleSignInApi + +- (void)initializeSignInWithParameters:(nonnull FSIInitParams *)params + error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { + GIDConfiguration *configuration = [self configurationWithClientIdArgument:params.clientId + serverClientIdArgument:params.serverClientId + hostedDomainArgument:params.hostedDomain]; + if (configuration != nil) { + self.requestedScopes = [NSSet setWithArray:params.scopes]; + self.configuration = configuration; + } else { + *error = [FlutterError errorWithCode:@"missing-config" message:@"GoogleService-Info.plist file not found and clientId " @"was not provided programmatically." - details:nil]); - } - } else if ([call.method isEqualToString:@"signInSilently"]) { - [self.signIn restorePreviousSignInWithCallback:^(GIDGoogleUser *user, NSError *error) { - [self didSignInForUser:user result:result withError:error]; - }]; - } else if ([call.method isEqualToString:@"isSignedIn"]) { - result(@([self.signIn hasPreviousSignIn])); - } else if ([call.method isEqualToString:@"signIn"]) { - @try { - GIDConfiguration *configuration = self.configuration - ?: [self configurationWithClientIdArgument:nil - serverClientIdArgument:nil - hostedDomainArgument:nil]; - [self.signIn signInWithConfiguration:configuration - presentingViewController:[self topViewController] - hint:nil - additionalScopes:self.requestedScopes.allObjects - callback:^(GIDGoogleUser *user, NSError *error) { - [self didSignInForUser:user result:result withError:error]; - }]; - } @catch (NSException *e) { - result([FlutterError errorWithCode:@"google_sign_in" message:e.reason details:e.name]); - [e raise]; - } - } else if ([call.method isEqualToString:@"getTokens"]) { - GIDGoogleUser *currentUser = self.signIn.currentUser; - GIDAuthentication *auth = currentUser.authentication; - [auth doWithFreshTokens:^void(GIDAuthentication *authentication, NSError *error) { - result(error != nil ? getFlutterError(error) : @{ - @"idToken" : authentication.idToken, - @"accessToken" : authentication.accessToken, - }); - }]; - } else if ([call.method isEqualToString:@"signOut"]) { - [self.signIn signOut]; - result(nil); - } else if ([call.method isEqualToString:@"disconnect"]) { - [self.signIn disconnectWithCallback:^(NSError *error) { - [self respondWithAccount:@{} result:result error:nil]; - }]; - } else if ([call.method isEqualToString:@"requestScopes"]) { - id scopeArgument = call.arguments[@"scopes"]; - if ([scopeArgument isKindOfClass:[NSArray class]]) { - self.requestedScopes = [self.requestedScopes setByAddingObjectsFromArray:scopeArgument]; - } - NSSet *requestedScopes = self.requestedScopes; - - @try { - [self.signIn addScopes:requestedScopes.allObjects - presentingViewController:[self topViewController] - callback:^(GIDGoogleUser *addedScopeUser, NSError *addedScopeError) { - if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && - addedScopeError.code == kGIDSignInErrorCodeNoCurrentUser) { - result([FlutterError errorWithCode:@"sign_in_required" - message:@"No account to grant scopes." - details:nil]); - } else if ([addedScopeError.domain - isEqualToString:kGIDSignInErrorDomain] && - addedScopeError.code == - kGIDSignInErrorCodeScopesAlreadyGranted) { - // Scopes already granted, report success. - result(@YES); - } else if (addedScopeUser == nil) { - result(@NO); - } else { - NSSet *grantedScopes = - [NSSet setWithArray:addedScopeUser.grantedScopes]; - BOOL granted = [requestedScopes isSubsetOfSet:grantedScopes]; - result(@(granted)); - } - }]; - } @catch (NSException *e) { - result([FlutterError errorWithCode:@"request_scopes" message:e.reason details:e.name]); - } - } else { - result(FlutterMethodNotImplemented); + details:nil]; } } -- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - return [self.signIn handleURL:url]; +- (void)signInSilentlyWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, + FlutterError *_Nullable))completion { + [self.signIn restorePreviousSignInWithCallback:^(GIDGoogleUser *user, NSError *error) { + [self didSignInForUser:user withCompletion:completion error:error]; + }]; +} + +- (nullable NSNumber *)isSignedInWithError: + (FlutterError *_Nullable __autoreleasing *_Nonnull)error { + return @([self.signIn hasPreviousSignIn]); +} + +- (void)signInWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, + FlutterError *_Nullable))completion { + @try { + GIDConfiguration *configuration = self.configuration + ?: [self configurationWithClientIdArgument:nil + serverClientIdArgument:nil + hostedDomainArgument:nil]; + [self.signIn signInWithConfiguration:configuration + presentingViewController:[self topViewController] + hint:nil + additionalScopes:self.requestedScopes.allObjects + callback:^(GIDGoogleUser *user, NSError *error) { + [self didSignInForUser:user + withCompletion:completion + error:error]; + }]; + } @catch (NSException *e) { + completion(nil, [FlutterError errorWithCode:@"google_sign_in" message:e.reason details:e.name]); + [e raise]; + } +} + +- (void)getAccessTokenWithCompletion:(nonnull void (^)(FSITokenData *_Nullable, + FlutterError *_Nullable))completion { + GIDGoogleUser *currentUser = self.signIn.currentUser; + GIDAuthentication *auth = currentUser.authentication; + [auth doWithFreshTokens:^void(GIDAuthentication *authentication, NSError *error) { + if (error) { + completion(nil, getFlutterError(error)); + } else { + completion([FSITokenData makeWithIdToken:authentication.idToken + accessToken:authentication.accessToken], + nil); + } + }]; +} + +- (void)signOutWithError:(FlutterError *_Nullable *_Nonnull)error; +{ [self.signIn signOut]; } + +- (void)disconnectWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { + [self.signIn disconnectWithCallback:^(NSError *error) { + // TODO(stuartmorgan): This preserves the pre-Pigeon-migration behavior, but it's unclear why + // 'error' is being ignored here. + completion(nil); + }]; +} + +- (void)requestScopes:(nonnull NSArray *)scopes + completion:(nonnull void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { + self.requestedScopes = [self.requestedScopes setByAddingObjectsFromArray:scopes]; + NSSet *requestedScopes = self.requestedScopes; + + @try { + [self.signIn addScopes:requestedScopes.allObjects + presentingViewController:[self topViewController] + callback:^(GIDGoogleUser *addedScopeUser, NSError *addedScopeError) { + BOOL granted = NO; + FlutterError *error = nil; + if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && + addedScopeError.code == kGIDSignInErrorCodeNoCurrentUser) { + error = [FlutterError errorWithCode:@"sign_in_required" + message:@"No account to grant scopes." + details:nil]; + } else if ([addedScopeError.domain + isEqualToString:kGIDSignInErrorDomain] && + addedScopeError.code == + kGIDSignInErrorCodeScopesAlreadyGranted) { + // Scopes already granted, report success. + granted = YES; + } else if (addedScopeUser == nil) { + granted = NO; + } else { + NSSet *grantedScopes = + [NSSet setWithArray:addedScopeUser.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 - protocol @@ -250,35 +265,27 @@ - (GIDConfiguration *)configurationWithClientIdArgument:(id)clientIDArg } - (void)didSignInForUser:(GIDGoogleUser *)user - result:(FlutterResult)result - withError:(NSError *)error { + withCompletion:(nonnull void (^)(FSIUserData *_Nullable, + FlutterError *_Nullable))completion + error:(NSError *)error { if (error != nil) { // Forward all errors and let Dart side decide how to handle. - [self respondWithAccount:nil result:result error:error]; + completion(nil, getFlutterError(error)); } else { NSURL *photoUrl; if (user.profile.hasImage) { // Placeholder that will be replaced by on the Dart side based on screen size. photoUrl = [user.profile imageURLWithDimension:1337]; } - [self respondWithAccount:@{ - @"displayName" : user.profile.name ?: [NSNull null], - @"email" : user.profile.email ?: [NSNull null], - @"id" : user.userID ?: [NSNull null], - @"photoUrl" : [photoUrl absoluteString] ?: [NSNull null], - @"serverAuthCode" : user.serverAuthCode ?: [NSNull null] - } - result:result - error:nil]; + completion([FSIUserData makeWithDisplayName:user.profile.name + email:user.profile.email + userId:user.userID + photoUrl:[photoUrl absoluteString] + serverAuthCode:user.serverAuthCode], + nil); } } -- (void)respondWithAccount:(NSDictionary *)account - result:(FlutterResult)result - error:(NSError *)error { - result(error != nil ? getFlutterError(error) : account); -} - - (UIViewController *)topViewController { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -316,4 +323,5 @@ - (UIViewController *)topViewControllerFromViewController:(UIViewController *)vi } return viewController; } + @end diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h new file mode 100644 index 0000000000000..37493aa26c6a1 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h @@ -0,0 +1,95 @@ +// 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. +// Autogenerated from Pigeon (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#import + +@protocol FlutterBinaryMessenger; +@protocol FlutterMessageCodec; +@class FlutterError; +@class FlutterStandardTypedData; + +NS_ASSUME_NONNULL_BEGIN + +@class FSIInitParams; +@class FSIUserData; +@class FSITokenData; + +/// Pigeon version of SignInInitParams. +/// +/// See SignInInitParams for details. +@interface FSIInitParams : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithScopes:(NSArray *)scopes + hostedDomain:(nullable NSString *)hostedDomain + clientId:(nullable NSString *)clientId + serverClientId:(nullable NSString *)serverClientId; +@property(nonatomic, strong) NSArray *scopes; +@property(nonatomic, copy, nullable) NSString *hostedDomain; +@property(nonatomic, copy, nullable) NSString *clientId; +@property(nonatomic, copy, nullable) NSString *serverClientId; +@end + +/// Pigeon version of GoogleSignInUserData. +/// +/// See GoogleSignInUserData for details. +@interface FSIUserData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithDisplayName:(nullable NSString *)displayName + email:(NSString *)email + userId:(NSString *)userId + photoUrl:(nullable NSString *)photoUrl + serverAuthCode:(nullable NSString *)serverAuthCode; +@property(nonatomic, copy, nullable) NSString *displayName; +@property(nonatomic, copy) NSString *email; +@property(nonatomic, copy) NSString *userId; +@property(nonatomic, copy, nullable) NSString *photoUrl; +@property(nonatomic, copy, nullable) NSString *serverAuthCode; +@end + +/// Pigeon version of GoogleSignInTokenData. +/// +/// See GoogleSignInTokenData for details. +@interface FSITokenData : NSObject ++ (instancetype)makeWithIdToken:(nullable NSString *)idToken + accessToken:(nullable NSString *)accessToken; +@property(nonatomic, copy, nullable) NSString *idToken; +@property(nonatomic, copy, nullable) NSString *accessToken; +@end + +/// The codec used by FSIGoogleSignInApi. +NSObject *FSIGoogleSignInApiGetCodec(void); + +@protocol FSIGoogleSignInApi +/// Initializes a sign in request with the given parameters. +- (void)initializeSignInWithParameters:(FSIInitParams *)params + error:(FlutterError *_Nullable *_Nonnull)error; +/// Starts a silent sign in. +- (void)signInSilentlyWithCompletion:(void (^)(FSIUserData *_Nullable, + FlutterError *_Nullable))completion; +/// Starts a sign in with user interaction. +- (void)signInWithCompletion:(void (^)(FSIUserData *_Nullable, FlutterError *_Nullable))completion; +/// Requests the access token for the current sign in. +- (void)getAccessTokenWithCompletion:(void (^)(FSITokenData *_Nullable, + FlutterError *_Nullable))completion; +/// Signs out the current user. +- (void)signOutWithError:(FlutterError *_Nullable *_Nonnull)error; +/// Revokes scope grants to the application. +- (void)disconnectWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Returns whether the user is currently signed in. +/// +/// @return `nil` only when `error != nil`. +- (nullable NSNumber *)isSignedInWithError:(FlutterError *_Nullable *_Nonnull)error; +/// Requests access to the given scopes. +- (void)requestScopes:(NSArray *)scopes + completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +@end + +extern void FSIGoogleSignInApiSetup(id binaryMessenger, + NSObject *_Nullable api); + +NS_ASSUME_NONNULL_END diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m new file mode 100644 index 0000000000000..4d617ca81f080 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m @@ -0,0 +1,372 @@ +// 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. +// Autogenerated from Pigeon (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#import "messages.g.h" + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +#if !__has_feature(objc_arc) +#error File requires ARC to be enabled. +#endif + +static NSArray *wrapResult(id result, FlutterError *error) { + if (error) { + return @[ + error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] + ]; + } + return @[ result ?: [NSNull null] ]; +} +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { + id result = array[key]; + return (result == [NSNull null]) ? nil : result; +} + +@interface FSIInitParams () ++ (FSIInitParams *)fromList:(NSArray *)list; ++ (nullable FSIInitParams *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface FSIUserData () ++ (FSIUserData *)fromList:(NSArray *)list; ++ (nullable FSIUserData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface FSITokenData () ++ (FSITokenData *)fromList:(NSArray *)list; ++ (nullable FSITokenData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@implementation FSIInitParams ++ (instancetype)makeWithScopes:(NSArray *)scopes + hostedDomain:(nullable NSString *)hostedDomain + clientId:(nullable NSString *)clientId + serverClientId:(nullable NSString *)serverClientId { + FSIInitParams *pigeonResult = [[FSIInitParams alloc] init]; + pigeonResult.scopes = scopes; + pigeonResult.hostedDomain = hostedDomain; + pigeonResult.clientId = clientId; + pigeonResult.serverClientId = serverClientId; + return pigeonResult; +} ++ (FSIInitParams *)fromList:(NSArray *)list { + FSIInitParams *pigeonResult = [[FSIInitParams alloc] init]; + pigeonResult.scopes = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.scopes != nil, @""); + pigeonResult.hostedDomain = GetNullableObjectAtIndex(list, 1); + pigeonResult.clientId = GetNullableObjectAtIndex(list, 2); + pigeonResult.serverClientId = GetNullableObjectAtIndex(list, 3); + return pigeonResult; +} ++ (nullable FSIInitParams *)nullableFromList:(NSArray *)list { + return (list) ? [FSIInitParams fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.scopes ?: [NSNull null]), + (self.hostedDomain ?: [NSNull null]), + (self.clientId ?: [NSNull null]), + (self.serverClientId ?: [NSNull null]), + ]; +} +@end + +@implementation FSIUserData ++ (instancetype)makeWithDisplayName:(nullable NSString *)displayName + email:(NSString *)email + userId:(NSString *)userId + photoUrl:(nullable NSString *)photoUrl + serverAuthCode:(nullable NSString *)serverAuthCode { + FSIUserData *pigeonResult = [[FSIUserData alloc] init]; + pigeonResult.displayName = displayName; + pigeonResult.email = email; + pigeonResult.userId = userId; + pigeonResult.photoUrl = photoUrl; + pigeonResult.serverAuthCode = serverAuthCode; + return pigeonResult; +} ++ (FSIUserData *)fromList:(NSArray *)list { + FSIUserData *pigeonResult = [[FSIUserData alloc] init]; + pigeonResult.displayName = GetNullableObjectAtIndex(list, 0); + pigeonResult.email = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.email != nil, @""); + pigeonResult.userId = GetNullableObjectAtIndex(list, 2); + NSAssert(pigeonResult.userId != nil, @""); + pigeonResult.photoUrl = GetNullableObjectAtIndex(list, 3); + pigeonResult.serverAuthCode = GetNullableObjectAtIndex(list, 4); + return pigeonResult; +} ++ (nullable FSIUserData *)nullableFromList:(NSArray *)list { + return (list) ? [FSIUserData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.displayName ?: [NSNull null]), + (self.email ?: [NSNull null]), + (self.userId ?: [NSNull null]), + (self.photoUrl ?: [NSNull null]), + (self.serverAuthCode ?: [NSNull null]), + ]; +} +@end + +@implementation FSITokenData ++ (instancetype)makeWithIdToken:(nullable NSString *)idToken + accessToken:(nullable NSString *)accessToken { + FSITokenData *pigeonResult = [[FSITokenData alloc] init]; + pigeonResult.idToken = idToken; + pigeonResult.accessToken = accessToken; + return pigeonResult; +} ++ (FSITokenData *)fromList:(NSArray *)list { + FSITokenData *pigeonResult = [[FSITokenData alloc] init]; + pigeonResult.idToken = GetNullableObjectAtIndex(list, 0); + pigeonResult.accessToken = GetNullableObjectAtIndex(list, 1); + return pigeonResult; +} ++ (nullable FSITokenData *)nullableFromList:(NSArray *)list { + return (list) ? [FSITokenData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.idToken ?: [NSNull null]), + (self.accessToken ?: [NSNull null]), + ]; +} +@end + +@interface FSIGoogleSignInApiCodecReader : FlutterStandardReader +@end +@implementation FSIGoogleSignInApiCodecReader +- (nullable id)readValueOfType:(UInt8)type { + switch (type) { + case 128: + return [FSIInitParams fromList:[self readValue]]; + case 129: + return [FSITokenData fromList:[self readValue]]; + case 130: + return [FSIUserData fromList:[self readValue]]; + default: + return [super readValueOfType:type]; + } +} +@end + +@interface FSIGoogleSignInApiCodecWriter : FlutterStandardWriter +@end +@implementation FSIGoogleSignInApiCodecWriter +- (void)writeValue:(id)value { + if ([value isKindOfClass:[FSIInitParams class]]) { + [self writeByte:128]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FSITokenData class]]) { + [self writeByte:129]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FSIUserData class]]) { + [self writeByte:130]; + [self writeValue:[value toList]]; + } else { + [super writeValue:value]; + } +} +@end + +@interface FSIGoogleSignInApiCodecReaderWriter : FlutterStandardReaderWriter +@end +@implementation FSIGoogleSignInApiCodecReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { + return [[FSIGoogleSignInApiCodecWriter alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { + return [[FSIGoogleSignInApiCodecReader alloc] initWithData:data]; +} +@end + +NSObject *FSIGoogleSignInApiGetCodec(void) { + static FlutterStandardMessageCodec *sSharedObject = nil; + static dispatch_once_t sPred = 0; + dispatch_once(&sPred, ^{ + FSIGoogleSignInApiCodecReaderWriter *readerWriter = + [[FSIGoogleSignInApiCodecReaderWriter alloc] init]; + sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; + }); + return sSharedObject; +} + +void FSIGoogleSignInApiSetup(id binaryMessenger, + NSObject *api) { + /// Initializes a sign in request with the given parameters. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.init" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(initializeSignInWithParameters:error:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to " + @"@selector(initializeSignInWithParameters:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FSIInitParams *arg_params = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + [api initializeSignInWithParameters:arg_params error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Starts a silent sign in. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.signInSilently" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(signInSilentlyWithCompletion:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to " + @"@selector(signInSilentlyWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api signInSilentlyWithCompletion:^(FSIUserData *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Starts a sign in with user interaction. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.signIn" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(signInWithCompletion:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to @selector(signInWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api signInWithCompletion:^(FSIUserData *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Requests the access token for the current sign in. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.getAccessToken" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(getAccessTokenWithCompletion:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to " + @"@selector(getAccessTokenWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api getAccessTokenWithCompletion:^(FSITokenData *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Signs out the current user. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.signOut" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(signOutWithError:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to @selector(signOutWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api signOutWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Revokes scope grants to the application. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.disconnect" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(disconnectWithCompletion:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to @selector(disconnectWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api disconnectWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns whether the user is currently signed in. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.isSignedIn" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(isSignedInWithError:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to @selector(isSignedInWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + NSNumber *output = [api isSignedInWithError:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Requests access to the given scopes. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.requestScopes" + binaryMessenger:binaryMessenger + codec:FSIGoogleSignInApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(requestScopes:completion:)], + @"FSIGoogleSignInApi api (%@) doesn't respond to @selector(requestScopes:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSArray *arg_scopes = GetNullableObjectAtIndex(args, 0); + [api requestScopes:arg_scopes + completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } +} diff --git a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart index d7b6f7936b47f..73113abb49a7c 100644 --- a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart +++ b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart @@ -8,15 +8,16 @@ import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter/services.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; -import 'src/utils.dart'; +import 'src/messages.g.dart'; /// iOS implementation of [GoogleSignInPlatform]. class GoogleSignInIOS extends GoogleSignInPlatform { - /// This is only exposed for test purposes. It shouldn't be used by clients of - /// the plugin as it may break or change at any time. - @visibleForTesting - MethodChannel channel = - const MethodChannel('plugins.flutter.io/google_sign_in_ios'); + /// Creates a new plugin implementation instance. + GoogleSignInIOS({ + @visibleForTesting GoogleSignInApi? api, + }) : _api = api ?? GoogleSignInApi(); + + final GoogleSignInApi _api; /// Registers this class as the default instance of [GoogleSignInPlatform]. static void registerWith() { @@ -45,51 +46,43 @@ class GoogleSignInIOS extends GoogleSignInPlatform { code: 'unsupported-options', message: 'Games sign in is not supported on iOS'); } - return channel.invokeMethod('init', { - 'scopes': params.scopes, - 'hostedDomain': params.hostedDomain, - 'clientId': params.clientId, - 'serverClientId': params.serverClientId, - }); + return _api.init(InitParams( + scopes: params.scopes, + hostedDomain: params.hostedDomain, + clientId: params.clientId, + serverClientId: params.serverClientId, + )); } @override Future signInSilently() { - return channel - .invokeMapMethod('signInSilently') - .then(getUserDataFromMap); + return _api.signInSilently().then(_signInUserDataFromChannelData); } @override Future signIn() { - return channel - .invokeMapMethod('signIn') - .then(getUserDataFromMap); + return _api.signIn().then(_signInUserDataFromChannelData); } @override Future getTokens( {required String email, bool? shouldRecoverAuth = true}) { - return channel - .invokeMapMethod('getTokens', { - 'email': email, - 'shouldRecoverAuth': shouldRecoverAuth, - }).then((Map? result) => getTokenDataFromMap(result!)); + return _api.getAccessToken().then(_signInTokenDataFromChannelData); } @override Future signOut() { - return channel.invokeMapMethod('signOut'); + return _api.signOut(); } @override Future disconnect() { - return channel.invokeMapMethod('disconnect'); + return _api.disconnect(); } @override - Future isSignedIn() async { - return (await channel.invokeMethod('isSignedIn'))!; + Future isSignedIn() { + return _api.isSignedIn(); } @override @@ -99,10 +92,24 @@ class GoogleSignInIOS extends GoogleSignInPlatform { } @override - Future requestScopes(List scopes) async { - return (await channel.invokeMethod( - 'requestScopes', - >{'scopes': scopes}, - ))!; + Future requestScopes(List scopes) { + return _api.requestScopes(scopes); + } + + GoogleSignInUserData _signInUserDataFromChannelData(UserData data) { + return GoogleSignInUserData( + email: data.email, + id: data.userId, + displayName: data.displayName, + photoUrl: data.photoUrl, + serverAuthCode: data.serverAuthCode, + ); + } + + GoogleSignInTokenData _signInTokenDataFromChannelData(TokenData data) { + return GoogleSignInTokenData( + idToken: data.idToken, + accessToken: data.accessToken, + ); } } diff --git a/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart b/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart new file mode 100644 index 0000000000000..78fa37259beab --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart @@ -0,0 +1,376 @@ +// 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. +// Autogenerated from Pigeon (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +/// Pigeon version of SignInInitParams. +/// +/// See SignInInitParams for details. +class InitParams { + InitParams({ + required this.scopes, + this.hostedDomain, + this.clientId, + this.serverClientId, + }); + + List scopes; + + String? hostedDomain; + + String? clientId; + + String? serverClientId; + + Object encode() { + return [ + scopes, + hostedDomain, + clientId, + serverClientId, + ]; + } + + static InitParams decode(Object result) { + result as List; + return InitParams( + scopes: (result[0] as List?)!.cast(), + hostedDomain: result[1] as String?, + clientId: result[2] as String?, + serverClientId: result[3] as String?, + ); + } +} + +/// Pigeon version of GoogleSignInUserData. +/// +/// See GoogleSignInUserData for details. +class UserData { + UserData({ + this.displayName, + required this.email, + required this.userId, + this.photoUrl, + this.serverAuthCode, + }); + + String? displayName; + + String email; + + String userId; + + String? photoUrl; + + String? serverAuthCode; + + Object encode() { + return [ + displayName, + email, + userId, + photoUrl, + serverAuthCode, + ]; + } + + static UserData decode(Object result) { + result as List; + return UserData( + displayName: result[0] as String?, + email: result[1]! as String, + userId: result[2]! as String, + photoUrl: result[3] as String?, + serverAuthCode: result[4] as String?, + ); + } +} + +/// Pigeon version of GoogleSignInTokenData. +/// +/// See GoogleSignInTokenData for details. +class TokenData { + TokenData({ + this.idToken, + this.accessToken, + }); + + String? idToken; + + String? accessToken; + + Object encode() { + return [ + idToken, + accessToken, + ]; + } + + static TokenData decode(Object result) { + result as List; + return TokenData( + idToken: result[0] as String?, + accessToken: result[1] as String?, + ); + } +} + +class _GoogleSignInApiCodec extends StandardMessageCodec { + const _GoogleSignInApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is InitParams) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is TokenData) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is UserData) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return InitParams.decode(readValue(buffer)!); + case 129: + return TokenData.decode(readValue(buffer)!); + case 130: + return UserData.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class GoogleSignInApi { + /// Constructor for [GoogleSignInApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + GoogleSignInApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _GoogleSignInApiCodec(); + + /// Initializes a sign in request with the given parameters. + Future init(InitParams arg_params) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.init', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_params]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Starts a silent sign in. + Future signInSilently() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.signInSilently', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as UserData?)!; + } + } + + /// Starts a sign in with user interaction. + Future signIn() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.signIn', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as UserData?)!; + } + } + + /// Requests the access token for the current sign in. + Future getAccessToken() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.getAccessToken', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as TokenData?)!; + } + } + + /// Signs out the current user. + Future signOut() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.signOut', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Revokes scope grants to the application. + Future disconnect() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.disconnect', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Returns whether the user is currently signed in. + Future isSignedIn() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.isSignedIn', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + /// Requests access to the given scopes. + Future requestScopes(List arg_scopes) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_sign_in_ios.GoogleSignInApi.requestScopes', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_scopes]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } +} diff --git a/packages/google_sign_in/google_sign_in_ios/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_ios/lib/src/utils.dart deleted file mode 100644 index 5cd7c20b829a2..0000000000000 --- a/packages/google_sign_in/google_sign_in_ios/lib/src/utils.dart +++ /dev/null @@ -1,28 +0,0 @@ -// 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. - -import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; - -/// Converts user data coming from native code into the proper platform interface type. -GoogleSignInUserData? getUserDataFromMap(Map? data) { - if (data == null) { - return null; - } - return GoogleSignInUserData( - email: data['email']! as String, - id: data['id']! as String, - displayName: data['displayName'] as String?, - photoUrl: data['photoUrl'] as String?, - idToken: data['idToken'] as String?, - serverAuthCode: data['serverAuthCode'] as String?); -} - -/// Converts token data coming from native code into the proper platform interface type. -GoogleSignInTokenData getTokenDataFromMap(Map data) { - return GoogleSignInTokenData( - idToken: data['idToken'] as String?, - accessToken: data['accessToken'] as String?, - serverAuthCode: data['serverAuthCode'] as String?, - ); -} diff --git a/packages/google_sign_in/google_sign_in_ios/pigeons/copyright.txt b/packages/google_sign_in/google_sign_in_ios/pigeons/copyright.txt new file mode 100644 index 0000000000000..1236b63caf3aa --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/pigeons/copyright.txt @@ -0,0 +1,3 @@ +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. diff --git a/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart b/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart new file mode 100644 index 0000000000000..f92f1d06c631d --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart @@ -0,0 +1,100 @@ +// 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. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + objcOptions: ObjcOptions(prefix: 'FSI'), + objcHeaderOut: 'ios/Classes/messages.g.h', + objcSourceOut: 'ios/Classes/messages.g.m', + copyrightHeader: 'pigeons/copyright.txt', +)) + +/// Pigeon version of SignInInitParams. +/// +/// See SignInInitParams for details. +class InitParams { + /// The parameters to use when initializing the sign in process. + const InitParams({ + this.scopes = const [], + this.hostedDomain, + this.clientId, + this.serverClientId, + }); + + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The Obj-C code treats the values as non-nullable. + final List scopes; + final String? hostedDomain; + final String? clientId; + final String? serverClientId; +} + +/// Pigeon version of GoogleSignInUserData. +/// +/// See GoogleSignInUserData for details. +class UserData { + UserData({ + required this.email, + required this.userId, + this.displayName, + this.photoUrl, + this.serverAuthCode, + }); + + final String? displayName; + final String email; + final String userId; + final String? photoUrl; + final String? serverAuthCode; +} + +/// Pigeon version of GoogleSignInTokenData. +/// +/// See GoogleSignInTokenData for details. +class TokenData { + TokenData({ + this.idToken, + this.accessToken, + }); + + final String? idToken; + final String? accessToken; +} + +@HostApi() +abstract class GoogleSignInApi { + /// Initializes a sign in request with the given parameters. + @ObjCSelector('initializeSignInWithParameters:') + void init(InitParams params); + + /// Starts a silent sign in. + @async + UserData signInSilently(); + + /// Starts a sign in with user interaction. + @async + UserData signIn(); + + /// Requests the access token for the current sign in. + @async + TokenData getAccessToken(); + + /// Signs out the current user. + void signOut(); + + /// Revokes scope grants to the application. + @async + void disconnect(); + + /// Returns whether the user is currently signed in. + bool isSignedIn(); + + /// Requests access to the given scopes. + @async + @ObjCSelector('requestScopes:') + bool requestScopes(List scopes); +} diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index c3b6c84c9af00..c2e780e4ac409 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_ios description: iOS implementation of the google_sign_in plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 5.6.3 +version: 5.6.4 environment: sdk: ">=2.19.0 <4.0.0" @@ -20,12 +20,15 @@ dependencies: flutter: sdk: flutter google_sign_in_platform_interface: ^2.2.0 + pigeon: ^11.0.1 dev_dependencies: + build_runner: ^2.4.6 flutter_test: sdk: flutter integration_test: sdk: flutter + mockito: 5.4.1 topics: - authentication diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart index 6adbdec39b74e..ace1a0fec3037 100644 --- a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart +++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart @@ -5,65 +5,36 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in_ios/google_sign_in_ios.dart'; -import 'package:google_sign_in_ios/src/utils.dart'; +import 'package:google_sign_in_ios/src/messages.g.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; -const Map kUserData = { - 'email': 'john.doe@gmail.com', - 'id': '8162538176523816253123', - 'photoUrl': 'https://lh5.googleusercontent.com/photo.jpg', - 'displayName': 'John Doe', - 'idToken': '123', - 'serverAuthCode': '789', -}; - -const Map kTokenData = { - 'idToken': '123', - 'accessToken': '456', - 'serverAuthCode': '789', -}; - -const Map kDefaultResponses = { - 'init': null, - 'signInSilently': kUserData, - 'signIn': kUserData, - 'signOut': null, - 'disconnect': null, - 'isSignedIn': true, - 'getTokens': kTokenData, - 'requestScopes': true, -}; - -final GoogleSignInUserData? kUser = getUserDataFromMap(kUserData); -final GoogleSignInTokenData kToken = - getTokenDataFromMap(kTokenData as Map); +import 'google_sign_in_ios_test.mocks.dart'; +final GoogleSignInUserData _user = GoogleSignInUserData( + email: 'john.doe@gmail.com', + id: '8162538176523816253123', + photoUrl: 'https://lh5.googleusercontent.com/photo.jpg', + displayName: 'John Doe', + serverAuthCode: '789', +); + +final GoogleSignInTokenData _token = GoogleSignInTokenData( + idToken: '123', + accessToken: '456', +); + +@GenerateMocks([GoogleSignInApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final GoogleSignInIOS googleSignIn = GoogleSignInIOS(); - final MethodChannel channel = googleSignIn.channel; - - late List log; - late Map - responses; // Some tests mutate some kDefaultResponses + late GoogleSignInIOS googleSignIn; + late MockGoogleSignInApi api; setUp(() { - responses = Map.from(kDefaultResponses); - log = []; - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler( - channel, - (MethodCall methodCall) { - log.add(methodCall); - final dynamic response = responses[methodCall.method]; - if (response != null && response is Exception) { - return Future.error('$response'); - } - return Future.value(response); - }, - ); + api = MockGoogleSignInApi(); + googleSignIn = GoogleSignInIOS(api: api); }); test('registered instance', () { @@ -83,93 +54,135 @@ void main() { test('signInSilently transforms platform data to GoogleSignInUserData', () async { + when(api.signInSilently()).thenAnswer((_) async => UserData( + email: _user.email, + userId: _user.id, + photoUrl: _user.photoUrl, + displayName: _user.displayName, + serverAuthCode: _user.serverAuthCode, + )); + final dynamic response = await googleSignIn.signInSilently(); - expect(response, kUser); + + expect(response, _user); }); + test('signInSilently Exceptions -> throws', () async { - responses['signInSilently'] = Exception('Not a user'); + when(api.signInSilently()) + .thenAnswer((_) async => throw PlatformException(code: 'fail')); + expect(googleSignIn.signInSilently(), throwsA(isInstanceOf())); }); test('signIn transforms platform data to GoogleSignInUserData', () async { + when(api.signIn()).thenAnswer((_) async => UserData( + email: _user.email, + userId: _user.id, + photoUrl: _user.photoUrl, + displayName: _user.displayName, + serverAuthCode: _user.serverAuthCode, + )); + final dynamic response = await googleSignIn.signIn(); - expect(response, kUser); + + expect(response, _user); }); + test('signIn Exceptions -> throws', () async { - responses['signIn'] = Exception('Not a user'); + when(api.signIn()) + .thenAnswer((_) async => throw PlatformException(code: 'fail')); + expect(googleSignIn.signIn(), throwsA(isInstanceOf())); }); test('getTokens transforms platform data to GoogleSignInTokenData', () async { - final dynamic response = await googleSignIn.getTokens( - email: 'example@example.com', shouldRecoverAuth: false); - expect(response, kToken); - expect( - log[0], - isMethodCall('getTokens', arguments: { - 'email': 'example@example.com', - 'shouldRecoverAuth': false, - })); + const bool recoverAuth = false; + when(api.getAccessToken()).thenAnswer((_) async => + TokenData(idToken: _token.idToken, accessToken: _token.accessToken)); + + final GoogleSignInTokenData response = await googleSignIn.getTokens( + email: _user.email, shouldRecoverAuth: recoverAuth); + + expect(response, _token); }); - test('clearAuthCache is a no-op', () async { - await googleSignIn.clearAuthCache(token: 'abc'); - expect(log.isEmpty, true); + test('clearAuthCache silently no-ops', () async { + expect(googleSignIn.clearAuthCache(token: 'abc'), completes); }); - test('Other functions pass through arguments to the channel', () async { - final Map tests = { - () { - googleSignIn.init( - hostedDomain: 'example.com', - scopes: ['two', 'scopes'], - clientId: 'fakeClientId'); - }: isMethodCall('init', arguments: { - 'hostedDomain': 'example.com', - 'scopes': ['two', 'scopes'], - 'clientId': 'fakeClientId', - 'serverClientId': null, - }), - () { - googleSignIn.initWithParams(const SignInInitParameters( - hostedDomain: 'example.com', - scopes: ['two', 'scopes'], - clientId: 'fakeClientId', - serverClientId: 'fakeServerClientId')); - }: isMethodCall('init', arguments: { - 'hostedDomain': 'example.com', - 'scopes': ['two', 'scopes'], - 'clientId': 'fakeClientId', - 'serverClientId': 'fakeServerClientId', - }), - () { - googleSignIn.getTokens( - email: 'example@example.com', shouldRecoverAuth: false); - }: isMethodCall('getTokens', arguments: { - 'email': 'example@example.com', - 'shouldRecoverAuth': false, - }), - () { - googleSignIn.requestScopes(['newScope', 'anotherScope']); - }: isMethodCall('requestScopes', arguments: { - 'scopes': ['newScope', 'anotherScope'], - }), - googleSignIn.signOut: isMethodCall('signOut', arguments: null), - googleSignIn.disconnect: isMethodCall('disconnect', arguments: null), - googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null), - }; - - for (final void Function() f in tests.keys) { - f(); - } - - expect(log, tests.values); + test('initWithParams passes arguments', () async { + const SignInInitParameters initParams = SignInInitParameters( + hostedDomain: 'example.com', + scopes: ['two', 'scopes'], + clientId: 'fakeClientId', + ); + + await googleSignIn.init( + hostedDomain: initParams.hostedDomain, + scopes: initParams.scopes, + signInOption: initParams.signInOption, + clientId: initParams.clientId, + ); + + final VerificationResult result = verify(api.init(captureAny)); + final InitParams passedParams = result.captured[0] as InitParams; + expect(passedParams.hostedDomain, initParams.hostedDomain); + expect(passedParams.scopes, initParams.scopes); + expect(passedParams.clientId, initParams.clientId); + // This should use whatever the SignInInitParameters defaults are. + expect(passedParams.serverClientId, initParams.serverClientId); }); -} -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; + test('initWithParams passes arguments', () async { + const SignInInitParameters initParams = SignInInitParameters( + hostedDomain: 'example.com', + scopes: ['two', 'scopes'], + clientId: 'fakeClientId', + serverClientId: 'fakeServerClientId', + forceCodeForRefreshToken: true, + ); + + await googleSignIn.initWithParams(initParams); + + final VerificationResult result = verify(api.init(captureAny)); + final InitParams passedParams = result.captured[0] as InitParams; + expect(passedParams.hostedDomain, initParams.hostedDomain); + expect(passedParams.scopes, initParams.scopes); + expect(passedParams.clientId, initParams.clientId); + expect(passedParams.serverClientId, initParams.serverClientId); + }); + + test('requestScopes passes arguments', () async { + const List scopes = ['newScope', 'anotherScope']; + when(api.requestScopes(scopes)).thenAnswer((_) async => true); + + final bool response = await googleSignIn.requestScopes(scopes); + + expect(response, true); + }); + + test('signOut calls through', () async { + await googleSignIn.signOut(); + + verify(api.signOut()); + }); + + test('disconnect calls through', () async { + await googleSignIn.disconnect(); + + verify(api.disconnect()); + }); + + test('isSignedIn passes true response', () async { + when(api.isSignedIn()).thenAnswer((_) async => true); + + expect(await googleSignIn.isSignedIn(), true); + }); + + test('isSignedIn passes false response', () async { + when(api.isSignedIn()).thenAnswer((_) async => false); + + expect(await googleSignIn.isSignedIn(), false); + }); +} diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.mocks.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.mocks.dart new file mode 100644 index 0000000000000..30c420d7ad07f --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.mocks.dart @@ -0,0 +1,138 @@ +// Mocks generated by Mockito 5.4.1 from annotations +// in google_sign_in_ios/test/google_sign_in_ios_test.dart. +// Do not manually edit this file. + +// @dart=2.19 + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:google_sign_in_ios/src/messages.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeUserData_0 extends _i1.SmartFake implements _i2.UserData { + _FakeUserData_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeTokenData_1 extends _i1.SmartFake implements _i2.TokenData { + _FakeTokenData_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [GoogleSignInApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGoogleSignInApi extends _i1.Mock implements _i2.GoogleSignInApi { + MockGoogleSignInApi() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future init(_i2.InitParams? arg_params) => (super.noSuchMethod( + Invocation.method( + #init, + [arg_params], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + _i3.Future<_i2.UserData> signInSilently() => (super.noSuchMethod( + Invocation.method( + #signInSilently, + [], + ), + returnValue: _i3.Future<_i2.UserData>.value(_FakeUserData_0( + this, + Invocation.method( + #signInSilently, + [], + ), + )), + ) as _i3.Future<_i2.UserData>); + @override + _i3.Future<_i2.UserData> signIn() => (super.noSuchMethod( + Invocation.method( + #signIn, + [], + ), + returnValue: _i3.Future<_i2.UserData>.value(_FakeUserData_0( + this, + Invocation.method( + #signIn, + [], + ), + )), + ) as _i3.Future<_i2.UserData>); + @override + _i3.Future<_i2.TokenData> getAccessToken() => (super.noSuchMethod( + Invocation.method( + #getAccessToken, + [], + ), + returnValue: _i3.Future<_i2.TokenData>.value(_FakeTokenData_1( + this, + Invocation.method( + #getAccessToken, + [], + ), + )), + ) as _i3.Future<_i2.TokenData>); + @override + _i3.Future signOut() => (super.noSuchMethod( + Invocation.method( + #signOut, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + _i3.Future disconnect() => (super.noSuchMethod( + Invocation.method( + #disconnect, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + _i3.Future isSignedIn() => (super.noSuchMethod( + Invocation.method( + #isSignedIn, + [], + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); + @override + _i3.Future requestScopes(List? arg_scopes) => + (super.noSuchMethod( + Invocation.method( + #requestScopes, + [arg_scopes], + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); +}