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 62d5b25245be..92b6d299c497 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,8 @@ +## 6.3.5 + +* Migrates ViewProvider and GID SDK wrappers from Objective-C to Swift. +* Returns an error when no host view is available to present Google Sign-In. + ## 6.3.4 * Migrates the plugin class from Objective-C to Swift. diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 07d0adfc886f..40ae274f4ee9 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -17,8 +17,8 @@ import Testing import Flutter #endif -// Test implementation of FSIViewProvider. -class TestViewProvider: NSObject, FSIViewProvider { +// Test implementation of ViewProvider. +class TestViewProvider: ViewProvider { #if os(OSX) // The view containing the Flutter content. var view: NSView? @@ -28,17 +28,17 @@ class TestViewProvider: NSObject, FSIViewProvider { #endif } -// Test implementation of FSIGIDSignIn. -class TestSignIn: NSObject, FSIGIDSignIn { +// Test implementation of GIDSignInProtocol. +class TestSignIn: NSObject, GIDSignInProtocol { var configuration: GIDConfiguration? // To cause methods to throw an exception. var exception: NSException? // Results to use in completion callbacks. - var user: (any FSIGIDGoogleUser)? + var user: (any GIDGoogleUserProtocol)? var error: Error? - var signInResult: (any FSIGIDSignInResult)? + var signInResult: (any GIDSignInResultProtocol)? // Passed parameters. var hint: String? @@ -62,7 +62,7 @@ class TestSignIn: NSObject, FSIGIDSignIn { return handleURLResult } - func restorePreviousSignIn(completion: (((any FSIGIDGoogleUser)?, Error?) -> Void)?) { + func restorePreviousSignIn(completion: (((any GIDGoogleUserProtocol)?, Error?) -> Void)?) { if let exception = exception { exception.raise() } @@ -90,7 +90,7 @@ class TestSignIn: NSObject, FSIGIDSignIn { hint: String?, additionalScopes: [String]?, nonce: String?, - completion: ((FSIGIDSignInResult?, Error?) -> Void)? + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? ) { if let exception = exception { exception.raise() @@ -111,7 +111,7 @@ class TestSignIn: NSObject, FSIGIDSignIn { hint: String?, additionalScopes: [String]?, nonce: String?, - completion: (((any FSIGIDSignInResult)?, Error?) -> Void)? + completion: (((any GIDSignInResultProtocol)?, Error?) -> Void)? ) { if let exception = exception { exception.raise() @@ -129,8 +129,8 @@ class TestSignIn: NSObject, FSIGIDSignIn { #endif } -// Test implementation of FSIGIDProfileData. -class TestProfileData: NSObject, FSIGIDProfileData { +// Test implementation of GIDProfileDataProtocol. +class TestProfileData: NSObject, GIDProfileDataProtocol { var email: String var name: String // A URL to return from imageURLWithDimension:. @@ -151,8 +151,8 @@ class TestProfileData: NSObject, FSIGIDProfileData { } } -// Test implementation of FSIGIDToken. -final class TestToken: NSObject, FSIGIDToken { +// Test implementation of GIDTokenProtocol. +final class TestToken: NSObject, GIDTokenProtocol { let tokenString: String let expirationDate: Date? @@ -162,31 +162,31 @@ final class TestToken: NSObject, FSIGIDToken { } } -// Test implementation of FSIGIDSignInResult. -class TestSignInResult: NSObject, FSIGIDSignInResult { - var user: any FSIGIDGoogleUser +// Test implementation of GIDSignInResultProtocol. +class TestSignInResult: NSObject, GIDSignInResultProtocol { + var user: any GIDGoogleUserProtocol var serverAuthCode: String? - init(user: any FSIGIDGoogleUser, serverAuthCode: String? = nil) { + init(user: any GIDGoogleUserProtocol, serverAuthCode: String? = nil) { self.user = user self.serverAuthCode = serverAuthCode } } -// Test implementation of FSIGIDGoogleUser. -class TestGoogleUser: NSObject, FSIGIDGoogleUser { +// Test implementation of GIDGoogleUserProtocol. +class TestGoogleUser: NSObject, GIDGoogleUserProtocol { var userID: String? - var profile: (any FSIGIDProfileData)? + var profile: (any GIDProfileDataProtocol)? var grantedScopes: [String]? - var accessToken: any FSIGIDToken = TestToken("Access") - var refreshToken: any FSIGIDToken = TestToken("Refresh") - var idToken: (any FSIGIDToken)? + var accessToken: any GIDTokenProtocol = TestToken("Access") + var refreshToken: any GIDTokenProtocol = TestToken("Refresh") + var idToken: (any GIDTokenProtocol)? // An exception to throw from methods. var exception: NSException? // The result to return from addScopes:presentingViewController:completion:. - var result: (any FSIGIDSignInResult)? + var result: (any GIDSignInResultProtocol)? // The error to return from methods. var error: Error? @@ -203,7 +203,7 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { userID = userIdentifier } - func refreshTokensIfNeeded(completion: @escaping ((any FSIGIDGoogleUser)?, Error?) -> Void) { + func refreshTokensIfNeeded(completion: @escaping ((any GIDGoogleUserProtocol)?, Error?) -> Void) { if let exception = exception { exception.raise() } @@ -214,7 +214,7 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { func addScopes( _ scopes: [String], presenting presentingViewController: UIViewController?, - completion: (((any FSIGIDSignInResult)?, Error?) -> Void)? + completion: (((any GIDSignInResultProtocol)?, Error?) -> Void)? ) { self.requestedScopes = scopes self.presentingViewController = presentingViewController @@ -227,7 +227,7 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { func addScopes( _ scopes: [String], presenting presentingWindow: NSWindow?, - completion: (((any FSIGIDSignInResult)?, Error?) -> Void)? + completion: (((any GIDSignInResultProtocol)?, Error?) -> Void)? ) { self.requestedScopes = scopes self.presentingWindow = presentingWindow @@ -836,6 +836,38 @@ struct GoogleSignInPluginTests { } } + @Suite("wrappers") struct WrapperTests { + @Test func signInCompletesWithErrorWhenPresenterIsNil() async { + let wrapper = GIDSignInWrapper() + await confirmation("completion called") { confirmed in + wrapper.signIn( + withPresenting: nil, hint: nil, additionalScopes: nil, nonce: nil + ) { result, error in + #expect(result == nil) + let nsError = error as NSError? + #expect(nsError?.domain == "google_sign_in") + #expect( + nsError?.localizedDescription + == "No host view available to present Google Sign-In.") + confirmed() + } + } + } + + @Test func pluginSignInWithoutPresenterReturnsFlutterError() async { + let plugin = GoogleSignInPlugin( + signIn: GIDSignInWrapper(), viewProvider: TestViewProvider()) + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + #expect(result == nil) + #expect(error?.code == "google_sign_in: 0") + #expect(error?.message == "No host view available to present Google Sign-In.") + confirmed() + } + } + } + } + #if os(iOS) || targetEnvironment(macCatalyst) @Suite("topViewController") @MainActor diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift index 1c62ea5c20ee..f3aa7ff0c04e 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift @@ -33,9 +33,6 @@ let package = Package( ), .target( name: "google_sign_in_ios_objc", - dependencies: [ - .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS") - ], publicHeadersPath: "include", cSettings: [ .headerSearchPath("include/google_sign_in_ios"), diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInPlugin.swift b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInPlugin.swift index d72c751d8696..8772046633fc 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInPlugin.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInPlugin.swift @@ -99,20 +99,20 @@ private func pigeonErrorCode(for gidSignInErrorCode: Int) -> FSIGoogleSignInErro public final class GoogleSignInPlugin: NSObject, FlutterPlugin, FSIGoogleSignInApi { /// Instance used to manage Google Sign In authentication including /// sign in, sign out, and requesting additional scopes. - let signIn: any FSIGIDSignIn + let signIn: any GIDSignInProtocol /// A mapping of user IDs to GIDGoogleUser instances to use for follow-up calls. - var usersByIdentifier: [String: any FSIGIDGoogleUser] = [:] + var usersByIdentifier: [String: any GIDGoogleUserProtocol] = [:] /// The contents of GoogleService-Info.plist, if it exists. private let googleServiceProperties: [String: Any]? /// The view provider, to access the current Flutter view. - private let viewProvider: any FSIViewProvider + private let viewProvider: ViewProvider public static func register(with registrar: FlutterPluginRegistrar) { let instance = GoogleSignInPlugin( - viewProvider: FSIDefaultViewProvider(registrar: registrar)) + viewProvider: DefaultViewProvider(registrar: registrar)) registrar.addApplicationDelegate(instance) #if os(iOS) registrar.addSceneDelegate(instance) @@ -124,22 +124,22 @@ public final class GoogleSignInPlugin: NSObject, FlutterPlugin, FSIGoogleSignInA } /// Inject view provider for testing. - convenience init(viewProvider: any FSIViewProvider) { - self.init(signIn: FSIGIDSignInWrapper(), viewProvider: viewProvider) + convenience init(viewProvider: ViewProvider) { + self.init(signIn: GIDSignInWrapper(), viewProvider: viewProvider) } - /// Inject `FSIGIDSignIn` for testing. - convenience init(signIn: any FSIGIDSignIn, viewProvider: any FSIViewProvider) { + /// Inject `GIDSignInProtocol` for testing. + convenience init(signIn: any GIDSignInProtocol, viewProvider: ViewProvider) { self.init( signIn: signIn, viewProvider: viewProvider, googleServiceProperties: loadGoogleServiceInfo()) } - /// Inject `FSIGIDSignIn` and `googleServiceProperties` for testing. + /// Inject `GIDSignInProtocol` and `googleServiceProperties` for testing. init( - signIn: any FSIGIDSignIn, - viewProvider: any FSIViewProvider, + signIn: any GIDSignInProtocol, + viewProvider: ViewProvider, googleServiceProperties: [String: Any]? ) { self.signIn = signIn @@ -310,7 +310,7 @@ public final class GoogleSignInPlugin: NSObject, FlutterPlugin, FSIGoogleSignInA hint: String?, additionalScopes: [String]?, nonce: String?, - completion: @escaping (FSIGIDSignInResult?, Error?) -> Void + completion: @escaping (GIDSignInResultProtocol?, Error?) -> Void ) { #if os(macOS) signIn.signIn( @@ -332,8 +332,8 @@ public final class GoogleSignInPlugin: NSObject, FlutterPlugin, FSIGoogleSignInA /// Wraps the iOS and macOS scope addition methods. private func performAddScopes( _ scopes: [String], - for user: any FSIGIDGoogleUser, - completion: @escaping (FSIGIDSignInResult?, Error?) -> Void + for user: any GIDGoogleUserProtocol, + completion: @escaping (GIDSignInResultProtocol?, Error?) -> Void ) { #if os(macOS) user.addScopes(scopes, presenting: viewProvider.view?.window, completion: completion) @@ -367,7 +367,7 @@ public final class GoogleSignInPlugin: NSObject, FlutterPlugin, FSIGoogleSignInA } private func handleAuthResult( - user: (any FSIGIDGoogleUser)?, + user: (any GIDGoogleUserProtocol)?, serverAuthCode: String?, error: Error?, completion: @escaping (FSISignInResult?, FlutterError?) -> Void @@ -395,7 +395,7 @@ public final class GoogleSignInPlugin: NSObject, FlutterPlugin, FSIGoogleSignInA } private func didSignIn( - for user: any FSIGIDGoogleUser, + for user: any GIDGoogleUserProtocol, serverAuthCode: String?, completion: @escaping (FSISignInResult?, FlutterError?) -> Void ) { diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInProtocols.swift b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInProtocols.swift new file mode 100644 index 000000000000..716e09bc75f7 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInProtocols.swift @@ -0,0 +1,108 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import GoogleSignIn + +#if os(macOS) + import AppKit +#else + import UIKit +#endif + +/// An abstraction around the GIDProfileData methods used by the plugin, to allow injecting an +/// alternate implementation in unit tests. +/// +/// See GIDProfileData for documentation, as this should always be implemented as a direct +/// passthrough to GIDProfileData. +protocol GIDProfileDataProtocol: AnyObject { + var email: String { get } + var name: String { get } + var hasImage: Bool { get } + func imageURL(withDimension dimension: UInt) -> URL? +} + +/// An abstraction around the GIDToken methods used by the plugin, to allow injecting an +/// alternate implementation in unit tests. +/// +/// See GIDToken for documentation, as this should always be implemented as a direct +/// passthrough to GIDToken. +protocol GIDTokenProtocol: AnyObject { + var tokenString: String { get } + var expirationDate: Date? { get } +} + +/// An abstraction around the GIDSignInResult methods used by the plugin, to allow injecting an +/// alternate implementation in unit tests. +/// +/// See GIDSignInResult for documentation, as this should always be implemented as a direct +/// passthrough to GIDSignInResult. +protocol GIDSignInResultProtocol: AnyObject { + var user: GIDGoogleUserProtocol { get } + var serverAuthCode: String? { get } +} + +/// An abstraction around the GIDGoogleUser methods used by the plugin, to allow injecting an +/// alternate implementation in unit tests. +/// +/// See GIDGoogleUser for documentation, as this should always be implemented as a direct +/// passthrough to GIDGoogleUser. +protocol GIDGoogleUserProtocol: AnyObject { + var userID: String? { get } + var profile: GIDProfileDataProtocol? { get } + var grantedScopes: [String]? { get } + var accessToken: GIDTokenProtocol { get } + var refreshToken: GIDTokenProtocol { get } + var idToken: GIDTokenProtocol? { get } + + func refreshTokensIfNeeded(completion: @escaping (GIDGoogleUserProtocol?, Error?) -> Void) + + #if os(iOS) || targetEnvironment(macCatalyst) + func addScopes( + _ scopes: [String], + presenting presentingViewController: UIViewController?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) + #elseif os(macOS) + func addScopes( + _ scopes: [String], + presenting presentingWindow: NSWindow?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) + #endif +} + +/// An abstraction around the GIDSignIn methods used by the plugin, to allow injecting an alternate +/// implementation in unit tests. +/// +/// See GIDSignIn for documentation, as this should always be implemented as a direct passthrough +/// to GIDSignIn. +protocol GIDSignInProtocol: AnyObject { + var configuration: GIDConfiguration? { get set } + + func handle(_ url: URL) -> Bool + + func restorePreviousSignIn(completion: ((GIDGoogleUserProtocol?, Error?) -> Void)?) + + func signOut() + + func disconnect(completion: ((Error?) -> Void)?) + + #if os(iOS) || targetEnvironment(macCatalyst) + func signIn( + withPresenting presentingViewController: UIViewController?, + hint: String?, + additionalScopes: [String]?, + nonce: String?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) + #elseif os(macOS) + func signIn( + withPresenting presentingWindow: NSWindow?, + hint: String?, + additionalScopes: [String]?, + nonce: String?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) + #endif +} diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInWrappers.swift b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInWrappers.swift new file mode 100644 index 000000000000..19f8f335bd26 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/GoogleSignInWrappers.swift @@ -0,0 +1,261 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import GoogleSignIn + +#if os(macOS) + import AppKit +#else + import UIKit +#endif + +/// Completes with an error instead of force-unwrapping a missing presenter. +/// +/// The GID SDK's Swift presenter argument is non-optional. Obj-C forwarded nil +/// through at runtime (a silent no-op); unwrapping it here would crash. +private func requirePresenter( + _ presenter: Presenter?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? +) -> Presenter? { + guard let presenter else { + completion?( + nil, + NSError( + domain: "google_sign_in", + code: 0, + userInfo: [ + NSLocalizedDescriptionKey: "No host view available to present Google Sign-In." + ])) + return nil + } + return presenter +} + +/// Implementation of `GIDSignInProtocol` that passes through to GIDSignIn. +final class GIDSignInWrapper: GIDSignInProtocol { + /// The underlying GIDSignIn instance. + let signIn: GIDSignIn + + init(signIn: GIDSignIn = .sharedInstance) { + self.signIn = signIn + } + + var configuration: GIDConfiguration? { + get { signIn.configuration } + set { signIn.configuration = newValue } + } + + func handle(_ url: URL) -> Bool { + return signIn.handle(url) + } + + func restorePreviousSignIn(completion: ((GIDGoogleUserProtocol?, Error?) -> Void)?) { + signIn.restorePreviousSignIn { user, error in + completion?(user.flatMap { GIDGoogleUserWrapper(user: $0) }, error) + } + } + + func signOut() { + signIn.signOut() + } + + func disconnect(completion: ((Error?) -> Void)?) { + signIn.disconnect(completion: completion) + } + + #if os(iOS) || targetEnvironment(macCatalyst) + func signIn( + withPresenting presentingViewController: UIViewController?, + hint: String?, + additionalScopes: [String]?, + nonce: String?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) { + guard + let presentingViewController = requirePresenter( + presentingViewController, completion: completion) + else { + return + } + signIn.signIn( + withPresenting: presentingViewController, + hint: hint, + additionalScopes: additionalScopes, + nonce: nonce + ) { result, error in + completion?(result.flatMap { GIDSignInResultWrapper(result: $0) }, error) + } + } + #elseif os(macOS) + func signIn( + withPresenting presentingWindow: NSWindow?, + hint: String?, + additionalScopes: [String]?, + nonce: String?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) { + guard let presentingWindow = requirePresenter(presentingWindow, completion: completion) + else { + return + } + signIn.signIn( + withPresenting: presentingWindow, + hint: hint, + additionalScopes: additionalScopes, + nonce: nonce + ) { result, error in + completion?(result.flatMap { GIDSignInResultWrapper(result: $0) }, error) + } + } + #endif +} + +/// Implementation of `GIDSignInResultProtocol` that passes through to GIDSignInResult. +final class GIDSignInResultWrapper: GIDSignInResultProtocol { + /// The underlying GIDSignInResult instance. + let result: GIDSignInResult + + init?(result: GIDSignInResult?) { + guard let result else { return nil } + self.result = result + } + + var user: GIDGoogleUserProtocol { + return GIDGoogleUserWrapper(user: result.user) + } + + var serverAuthCode: String? { + return result.serverAuthCode + } +} + +/// Implementation of `GIDGoogleUserProtocol` that passes through to GIDGoogleUser. +final class GIDGoogleUserWrapper: GIDGoogleUserProtocol { + /// The underlying GIDGoogleUser instance. + let user: GIDGoogleUser + + init(user: GIDGoogleUser) { + self.user = user + } + + convenience init?(user: GIDGoogleUser?) { + guard let user else { return nil } + self.init(user: user) + } + + var userID: String? { + return user.userID + } + + var profile: GIDProfileDataProtocol? { + return GIDProfileDataWrapper(profileData: user.profile) + } + + var grantedScopes: [String]? { + return user.grantedScopes + } + + var accessToken: GIDTokenProtocol { + return GIDTokenWrapper(token: user.accessToken) + } + + var refreshToken: GIDTokenProtocol { + return GIDTokenWrapper(token: user.refreshToken) + } + + var idToken: GIDTokenProtocol? { + return GIDTokenWrapper(token: user.idToken) + } + + func refreshTokensIfNeeded(completion: @escaping (GIDGoogleUserProtocol?, Error?) -> Void) { + user.refreshTokensIfNeeded { user, error in + completion(user.flatMap { GIDGoogleUserWrapper(user: $0) }, error) + } + } + + #if os(iOS) || targetEnvironment(macCatalyst) + func addScopes( + _ scopes: [String], + presenting presentingViewController: UIViewController?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) { + guard + let presentingViewController = requirePresenter( + presentingViewController, completion: completion) + else { + return + } + user.addScopes( + scopes, + presenting: presentingViewController + ) { result, error in + completion?(result.flatMap { GIDSignInResultWrapper(result: $0) }, error) + } + } + #elseif os(macOS) + func addScopes( + _ scopes: [String], + presenting presentingWindow: NSWindow?, + completion: ((GIDSignInResultProtocol?, Error?) -> Void)? + ) { + guard let presentingWindow = requirePresenter(presentingWindow, completion: completion) + else { + return + } + user.addScopes(scopes, presenting: presentingWindow) { result, error in + completion?(result.flatMap { GIDSignInResultWrapper(result: $0) }, error) + } + } + #endif +} + +/// Implementation of `GIDProfileDataProtocol` that passes through to GIDProfileData. +final class GIDProfileDataWrapper: GIDProfileDataProtocol { + /// The underlying GIDProfileData instance. + let profileData: GIDProfileData + + init?(profileData: GIDProfileData?) { + guard let profileData else { return nil } + self.profileData = profileData + } + + var email: String { + return profileData.email + } + + var name: String { + return profileData.name + } + + var hasImage: Bool { + return profileData.hasImage + } + + func imageURL(withDimension dimension: UInt) -> URL? { + return profileData.imageURL(withDimension: dimension) + } +} + +/// Implementation of `GIDTokenProtocol` that passes through to GIDToken. +final class GIDTokenWrapper: GIDTokenProtocol { + /// The underlying GIDToken instance. + let token: GIDToken + + init(token: GIDToken) { + self.token = token + } + + convenience init?(token: GIDToken?) { + guard let token else { return nil } + self.init(token: token) + } + + var tokenString: String { + return token.tokenString + } + + var expirationDate: Date? { + return token.expirationDate + } +} diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/ViewProvider.swift b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/ViewProvider.swift new file mode 100644 index 000000000000..6c2317ad9360 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/ViewProvider.swift @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if os(macOS) + import AppKit + import FlutterMacOS +#else + import Flutter + import UIKit +#endif + +/// Protocol for obtaining the view containing the Flutter content. +protocol ViewProvider: AnyObject { + #if os(macOS) + /// The view containing the Flutter content. + var view: NSView? { get } + #else + /// The view controller containing the Flutter content. + var viewController: UIViewController? { get } + #endif +} + +/// Implementation of `ViewProvider` that passes through to the registrar. +final class DefaultViewProvider: ViewProvider { + /// The registrar backing the provider. + let registrar: FlutterPluginRegistrar + + /// Returns a provider backed by the given registrar. + init(registrar: FlutterPluginRegistrar) { + self.registrar = registrar + } + + #if os(macOS) + var view: NSView? { + registrar.view + } + #else + var viewController: UIViewController? { + registrar.viewController + } + #endif +} diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/WrapperProtocolImplementations.m b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/WrapperProtocolImplementations.m deleted file mode 100644 index 311c004cf90b..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/WrapperProtocolImplementations.m +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "./include/google_sign_in_ios/WrapperProtocolImplementations.h" - -#import "./include/google_sign_in_ios/FSIViewProvider.h" - -@import GoogleSignIn; - -@implementation FSIDefaultViewProvider - -- (instancetype)initWithRegistrar:(NSObject *)registrar { - self = [super init]; - if (self) { - _registrar = registrar; - } - return self; -} - -#if TARGET_OS_OSX -- (NSView *)view { - return self.registrar.view; -} -#else -- (UIViewController *)viewController { - return self.registrar.viewController; -} -#endif - -@end - -#pragma mark - - -@implementation FSIGIDSignInWrapper - -- (instancetype)init { - self = [super init]; - if (self) { - _signIn = GIDSignIn.sharedInstance; - } - return self; -} - -- (GIDConfiguration *)configuration { - return self.signIn.configuration; -} - -- (void)setConfiguration:(GIDConfiguration *)configuration { - self.signIn.configuration = configuration; -} - -- (BOOL)handleURL:(NSURL *)url { - return [self.signIn handleURL:url]; -} - -- (void)restorePreviousSignInWithCompletion: - (nullable void (^)(NSObject *_Nullable user, - NSError *_Nullable error))completion { - [self.signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser *user, NSError *error) { - if (completion) { - completion([[FSIGIDGoogleUserWrapper alloc] initWithUser:user], error); - } - }]; -} - -- (void)signOut { - [self.signIn signOut]; -} - -- (void)disconnectWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { - [self.signIn disconnectWithCompletion:completion]; -} - -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST - -- (void)signInWithPresentingViewController:(nullable UIViewController *)presentingViewController - hint:(nullable NSString *)hint - additionalScopes:(nullable NSArray *)additionalScopes - nonce:(nullable NSString *)nonce - completion:(nullable void (^)( - NSObject *_Nullable signInResult, - NSError *_Nullable error))completion { - [self.signIn signInWithPresentingViewController:presentingViewController - hint:hint - additionalScopes:additionalScopes - nonce:nonce - completion:^(GIDSignInResult *result, NSError *error) { - if (completion) { - completion([[FSIGIDSignInResultWrapper alloc] - initWithResult:result], - error); - } - }]; -} - -#elif TARGET_OS_OSX - -- (void)signInWithPresentingWindow:(nullable NSWindow *)presentingWindow - hint:(nullable NSString *)hint - additionalScopes:(nullable NSArray *)additionalScopes - nonce:(nullable NSString *)nonce - completion: - (nullable void (^)(NSObject *_Nullable signInResult, - NSError *_Nullable error))completion { - [self.signIn - signInWithPresentingWindow:presentingWindow - hint:hint - additionalScopes:additionalScopes - nonce:nonce - completion:^(GIDSignInResult *result, NSError *error) { - if (completion) { - completion([[FSIGIDSignInResultWrapper alloc] initWithResult:result], - error); - } - }]; -} - -#endif - -@end - -#pragma mark - - -@implementation FSIGIDSignInResultWrapper - -- (instancetype)initWithResult:(GIDSignInResult *)result { - if (result == nil) { - return nil; - } - self = [super init]; - if (self) { - _result = result; - } - return self; -} - -- (NSObject *)user { - return [[FSIGIDGoogleUserWrapper alloc] initWithUser:self.result.user]; -} - -- (NSString *)serverAuthCode { - return self.result.serverAuthCode; -} - -@end - -#pragma mark - - -@implementation FSIGIDGoogleUserWrapper - -- (instancetype)initWithUser:(GIDGoogleUser *)user { - if (user == nil) { - return nil; - } - self = [super init]; - if (self) { - _user = user; - } - return self; -} - -- (NSString *)userID { - return self.user.userID; -} - -- (NSObject *)profile { - return [[FSIGIDProfileDataWrapper alloc] initWithProfileData:self.user.profile]; -} - -- (NSArray *)grantedScopes { - return self.user.grantedScopes; -} - -- (NSObject *)accessToken { - return [[FSIGIDTokenWrapper alloc] initWithToken:self.user.accessToken]; -} - -- (NSObject *)refreshToken { - return [[FSIGIDTokenWrapper alloc] initWithToken:self.user.refreshToken]; -} - -- (NSObject *)idToken { - return [[FSIGIDTokenWrapper alloc] initWithToken:self.user.idToken]; -} - -- (void)refreshTokensIfNeededWithCompletion:(void (^)(NSObject *_Nullable user, - NSError *_Nullable error))completion { - [self.user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser *user, NSError *error) { - if (completion) { - completion([[FSIGIDGoogleUserWrapper alloc] initWithUser:user], error); - } - }]; -} - -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST - -- (void)addScopes:(NSArray *)scopes - presentingViewController:(nullable UIViewController *)presentingViewController - completion: - (nullable void (^)(NSObject *_Nullable signInResult, - NSError *_Nullable error))completion { - [self.user addScopes:scopes - presentingViewController:presentingViewController - completion:^(GIDSignInResult *result, NSError *error) { - if (completion) { - completion([[FSIGIDSignInResultWrapper alloc] initWithResult:result], - error); - } - }]; -} - -#elif TARGET_OS_OSX - -- (void)addScopes:(NSArray *)scopes - presentingWindow:(nullable NSWindow *)presentingWindow - completion:(nullable void (^)(NSObject *_Nullable signInResult, - NSError *_Nullable error))completion { - [self.user addScopes:scopes - presentingWindow:presentingWindow - completion:^(GIDSignInResult *result, NSError *error) { - if (completion) { - completion([[FSIGIDSignInResultWrapper alloc] initWithResult:result], error); - } - }]; -} - -#endif - -@end - -#pragma mark - - -@implementation FSIGIDProfileDataWrapper - -- (instancetype)initWithProfileData:(GIDProfileData *)profileData { - if (profileData == nil) { - return nil; - } - self = [super init]; - if (self) { - _profileData = profileData; - } - return self; -} - -- (NSString *)email { - return self.profileData.email; -} - -- (NSString *)name { - return self.profileData.name; -} - -- (BOOL)hasImage { - return self.profileData.hasImage; -} - -- (NSURL *)imageURLWithDimension:(NSUInteger)dimension { - return [self.profileData imageURLWithDimension:dimension]; -} - -@end - -#pragma mark - - -@implementation FSIGIDTokenWrapper - -- (instancetype)initWithToken:(GIDToken *)token { - if (token == nil) { - return nil; - } - self = [super init]; - if (self) { - _token = token; - } - return self; -} - -- (NSString *)tokenString { - return self.token.tokenString; -} - -- (NSDate *)expirationDate { - return self.token.expirationDate; -} - -@end diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/FSIGoogleSignInProtocols.h b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/FSIGoogleSignInProtocols.h deleted file mode 100644 index ccbabe7cfed7..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/FSIGoogleSignInProtocols.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -@protocol FSIGIDGoogleUser; - -NS_ASSUME_NONNULL_BEGIN - -/// An abstraction around the GIDProfileData methods used by the plugin, to allow injecting an -/// alternate implementation in unit tests. -/// -/// See GIDProfileData for documentation, as this should always be implemented as a direct -/// passthrough to GIDProfileData. -@protocol FSIGIDProfileData - -@property(nonatomic, readonly) NSString *email; -@property(nonatomic, readonly) NSString *name; -@property(nonatomic, readonly) BOOL hasImage; -- (nullable NSURL *)imageURLWithDimension:(NSUInteger)dimension; - -@end - -#pragma mark - - -/// An abstraction around the GIDToken methods used by the plugin, to allow injecting an -/// alternate implementation in unit tests. -/// -/// See GIDToken for documentation, as this should always be implemented as a direct -/// passthrough to GIDToken. -@protocol FSIGIDToken - -@property(nonatomic, readonly) NSString *tokenString; -@property(nonatomic, readonly, nullable) NSDate *expirationDate; - -@end - -#pragma mark - - -/// An abstraction around the GIDSignInResult methods used by the plugin, to allow injecting an -/// alternate implementation in unit tests. -/// -/// See GIDSignInResult for documentation, as this should always be implemented as a direct -/// passthrough to GIDSignInResult. -@protocol FSIGIDSignInResult - -@property(nonatomic, readonly) NSObject *user; -@property(nonatomic, readonly, nullable) NSString *serverAuthCode; - -@end - -#pragma mark - - -/// An abstraction around the GIDGoogleUser methods used by the plugin, to allow injecting an -/// alternate implementation in unit tests. -/// -/// See GIDGoogleUser for documentation, as this should always be implemented as a direct -/// passthrough to GIDGoogleUser. -@protocol FSIGIDGoogleUser - -@property(nonatomic, readonly, nullable) NSString *userID; -@property(nonatomic, readonly, nullable) NSObject *profile; -@property(nonatomic, readonly, nullable) NSArray *grantedScopes; -@property(nonatomic, readonly) NSObject *accessToken; -@property(nonatomic, readonly) NSObject *refreshToken; -@property(nonatomic, readonly, nullable) NSObject *idToken; - -- (void)refreshTokensIfNeededWithCompletion:(void (^)(NSObject *_Nullable user, - NSError *_Nullable error))completion; - -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST - -- (void)addScopes:(NSArray *)scopes - presentingViewController:(nullable UIViewController *)presentingViewController - completion:(nullable void (^)(NSObject *_Nullable result, - NSError *_Nullable error))completion - NS_EXTENSION_UNAVAILABLE("The add scopes flow is not supported in App Extensions."); - -#elif TARGET_OS_OSX - -- (void)addScopes:(NSArray *)scopes - presentingWindow:(nullable NSWindow *)presentingWindow - completion:(nullable void (^)(NSObject *_Nullable result, - NSError *_Nullable error))completion; - -#endif - -@end - -#pragma mark - - -/// An abstraction around the GIDSignIn methods used by the plugin, to allow injecting an alternate -/// implementation in unit tests. -/// -/// See GIDSignIn for documentation, as this should always be implemented as a direct passthrough -/// to GIDSignIn. -@protocol FSIGIDSignIn - -@property(nonatomic, nullable) GIDConfiguration *configuration; - -- (BOOL)handleURL:(NSURL *)url; - -- (void)restorePreviousSignInWithCompletion: - (nullable void (^)(NSObject *_Nullable user, - NSError *_Nullable error))completion; - -- (void)signOut; - -- (void)disconnectWithCompletion:(nullable void (^)(NSError *_Nullable error))completion; - -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST - -- (void)signInWithPresentingViewController:(nullable UIViewController *)presentingViewController - hint:(nullable NSString *)hint - additionalScopes:(nullable NSArray *)additionalScopes - nonce:(nullable NSString *)nonce - completion:(nullable void (^)( - NSObject *_Nullable signInResult, - NSError *_Nullable error))completion - NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions."); - -#elif TARGET_OS_OSX - -- (void)signInWithPresentingWindow:(nullable NSWindow *)presentingWindow - hint:(nullable NSString *)hint - additionalScopes:(nullable NSArray *)additionalScopes - nonce:(nullable NSString *)nonce - completion: - (nullable void (^)(NSObject *_Nullable signInResult, - NSError *_Nullable error))completion; - -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/FSIViewProvider.h b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/FSIViewProvider.h deleted file mode 100644 index 5c1a48ae519a..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/FSIViewProvider.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2013 The Flutter Authors -// 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 Cocoa; -@import FlutterMacOS; -#else -@import Flutter; -@import UIKit; -#endif - -NS_ASSUME_NONNULL_BEGIN - -/// Protocol for obtaining the view containing the Flutter content. -@protocol FSIViewProvider -@required -#if TARGET_OS_OSX -/// The view containing the Flutter content. -@property(nonatomic, readonly, nullable) NSView *view; -#else -/// The view controller containing the Flutter content. -@property(nonatomic, readonly, nullable) UIViewController *viewController; -#endif -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/WrapperProtocolImplementations.h b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/WrapperProtocolImplementations.h deleted file mode 100644 index e03223e0a463..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios_objc/include/google_sign_in_ios/WrapperProtocolImplementations.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import GoogleSignIn; - -#import "FSIGoogleSignInProtocols.h" -#import "FSIViewProvider.h" - -NS_ASSUME_NONNULL_BEGIN - -// TODO(stuarmorgan): Replace these with protocol extensions when migrating to Swift. -// https://github.com/flutter/flutter/issues/119103 - -/// Implementation of @c FSIViewProvider that passes through to the registrar. -@interface FSIDefaultViewProvider : NSObject -/// Returns a provider backed by the given registrar. -- (instancetype)initWithRegistrar:(NSObject *)registrar - NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -/// The registrar backing the provider. -@property(readonly) NSObject *registrar; -@end - -#pragma mark - - -/// Implementation of @c FSIGIDSignIn that passes through to GIDSignIn. -@interface FSIGIDSignInWrapper : NSObject - -/// The underlying GIDSignIn instance. -@property(readonly) GIDSignIn *signIn; - -@end - -/// Implementation of @c FSIGIDSignInResult that passes through to GIDSignInResult. -@interface FSIGIDSignInResultWrapper : NSObject - -- (nullable instancetype)initWithResult:(nullable GIDSignInResult *)result - NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -/// The underlying GIDSignInResult instance. -@property(nonatomic, nullable) GIDSignInResult *result; - -@end - -#pragma mark - - -/// Implementation of @c FSIGIDGoogleUser that passes through to GIDGoogleUser. -@interface FSIGIDGoogleUserWrapper : NSObject - -- (nullable instancetype)initWithUser:(nullable GIDGoogleUser *)user NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -/// The underlying GIDGoogleUser instance. -@property(nonatomic, nullable) GIDGoogleUser *user; - -@end - -#pragma mark - - -/// Implementation of @c FSIGIDProfileData that passes through to GIDProfileData. -@interface FSIGIDProfileDataWrapper : NSObject - -- (nullable instancetype)initWithProfileData:(nullable GIDProfileData *)profileData - NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -/// The underlying GIDProfileData instance. -@property(nonatomic, nullable) GIDProfileData *profileData; - -@end - -#pragma mark - - -/// Implementation of @c FSIGIDToken that passes through to GIDToken. -@interface FSIGIDTokenWrapper : NSObject - -- (nullable instancetype)initWithToken:(nullable GIDToken *)token NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -/// The underlying GIDToken instance. -@property(nonatomic, nullable) GIDToken *token; - -@end - -NS_ASSUME_NONNULL_END 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 fc252dc77f40..e1a3ecb26d2a 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: 6.3.4 +version: 6.3.5 environment: sdk: ^3.10.0