Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 43 additions & 66 deletions packages/google_sign_in/google_sign_in/lib/google_sign_in.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,38 @@
import 'dart:async';
import 'dart:ui' show hashValues;

import 'package:flutter/services.dart' show MethodChannel, PlatformException;
import 'package:meta/meta.dart' show visibleForTesting;
import 'package:flutter/services.dart' show PlatformException;
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';

import 'src/common.dart';

export 'src/common.dart';
export 'widgets.dart';

enum SignInOption { standard, games }
// enum SignInOption { standard, games }

class GoogleSignInAuthentication {
GoogleSignInAuthentication._(this._data);

final Map<String, dynamic> _data;
final GoogleSignInTokenData _data;

/// An OpenID Connect ID token that identifies the user.
String get idToken => _data['idToken'];
String get idToken => _data.idToken;

/// The OAuth2 access token to access Google services.
String get accessToken => _data['accessToken'];
String get accessToken => _data.accessToken;

@override
String toString() => 'GoogleSignInAuthentication:$_data';
}

class GoogleSignInAccount implements GoogleIdentity {
GoogleSignInAccount._(this._googleSignIn, Map<String, dynamic> data)
: displayName = data['displayName'],
email = data['email'],
id = data['id'],
photoUrl = data['photoUrl'],
_idToken = data['idToken'] {
GoogleSignInAccount._(this._googleSignIn, GoogleSignInUserData data)
: displayName = data.displayName,
email = data.email,
id = data.id,
photoUrl = data.photoUrl,
_idToken = data.idToken {
assert(id != null);
}

Expand Down Expand Up @@ -78,18 +78,12 @@ class GoogleSignInAccount implements GoogleIdentity {
throw StateError('User is no longer signed in.');
}

final Map<String, dynamic> response =
await GoogleSignIn.channel.invokeMapMethod<String, dynamic>(
'getTokens',
<String, dynamic>{
'email': email,
'shouldRecoverAuth': true,
},
);
final GoogleSignInTokenData response = await GoogleSignInPlatform.instance.getTokens(email: email, shouldRecoverAuth: true);

// On Android, there isn't an API for refreshing the idToken, so re-use
// the one we obtained on login.
if (response['idToken'] == null) {
response['idToken'] = _idToken;
if (response.idToken == null) {
response.idToken = _idToken;
}
return GoogleSignInAuthentication._(response);
}
Expand All @@ -108,10 +102,7 @@ class GoogleSignInAccount implements GoogleIdentity {
/// this method and grab `authHeaders` once again.
Future<void> clearAuthCache() async {
final String token = (await authentication).accessToken;
await GoogleSignIn.channel.invokeMethod<void>(
'clearAuthCache',
<String, dynamic>{'token': token},
);
await GoogleSignInPlatform.instance.clearAuthCache(token: token);
}

@override
Expand Down Expand Up @@ -146,7 +137,7 @@ class GoogleSignIn {
/// Initializes global sign-in configuration settings.
///
/// The [signInOption] determines the user experience. [SigninOption.games]
/// must not be used on iOS.
/// must not be used on iOS or Web.
///
/// The list of [scopes] are OAuth scope codes to request when signing in.
/// These scope codes will determine the level of data access that is granted
Expand All @@ -157,7 +148,7 @@ class GoogleSignIn {
/// The [hostedDomain] argument specifies a hosted domain restriction. By
/// setting this, sign in will be restricted to accounts of the user in the
/// specified domain. By default, the list of accounts will not be restricted.
GoogleSignIn({this.signInOption, this.scopes, this.hostedDomain});
GoogleSignIn({this.signInOption = SignInOption.standard, this.scopes = const <String>[], this.hostedDomain});

/// Factory for creating default sign in user experience.
factory GoogleSignIn.standard({List<String> scopes, String hostedDomain}) {
Expand All @@ -168,7 +159,7 @@ class GoogleSignIn {
}

/// Factory for creating sign in suitable for games. This option must not be
/// used on iOS because the games API is not supported.
/// used on iOS or Web because the games API is not supported.
factory GoogleSignIn.games() {
return GoogleSignIn(signInOption: SignInOption.games);
}
Expand All @@ -186,11 +177,6 @@ class GoogleSignIn {
/// Error code indicating that attempt to sign in failed.
static const String kSignInFailedError = 'sign_in_failed';

/// The [MethodChannel] over which this class communicates.
@visibleForTesting
static const MethodChannel channel =
MethodChannel('plugins.flutter.io/google_sign_in');

/// Option to determine the sign in user experience. [SignInOption.games] must
/// not be used on iOS.
final SignInOption signInOption;
Expand All @@ -211,14 +197,12 @@ class GoogleSignIn {
// Future that completes when we've finished calling `init` on the native side
Future<void> _initialization;

Future<GoogleSignInAccount> _callMethod(String method) async {
Future<GoogleSignInAccount> _callMethod(Function method) async {
await _ensureInitialized();

final Map<String, dynamic> response =
await channel.invokeMapMethod<String, dynamic>(method);
return _setCurrentUser(response != null && response.isNotEmpty
? GoogleSignInAccount._(this, response)
: null);
final dynamic response = await method();

return _setCurrentUser(response != null && response is GoogleSignInUserData ? GoogleSignInAccount._(this, response) : null);
}

GoogleSignInAccount _setCurrentUser(GoogleSignInAccount currentUser) {
Expand All @@ -230,16 +214,14 @@ class GoogleSignIn {
}

Future<void> _ensureInitialized() {
return _initialization ??=
channel.invokeMethod<void>('init', <String, dynamic>{
'signInOption': (signInOption ?? SignInOption.standard).toString(),
'scopes': scopes ?? <String>[],
'hostedDomain': hostedDomain,
})
..catchError((dynamic _) {
// Invalidate initialization if it errored out.
_initialization = null;
});
return _initialization ??= GoogleSignInPlatform.instance.init(
signInOption: signInOption,
scopes: scopes,
hostedDomain: hostedDomain,
)..catchError((dynamic _) {
// Invalidate initialization if it errored out.
_initialization = null;
});
}

/// The most recently scheduled method call.
Expand All @@ -251,6 +233,7 @@ class GoogleSignIn {
final Completer<void> completer = Completer<void>();
future.whenComplete(completer.complete).catchError((dynamic _) {
// Ignore if previous call completed with an error.
// TODO: Should we log errors here?
});
return completer.future;
}
Expand All @@ -259,26 +242,20 @@ class GoogleSignIn {
///
/// At most one in flight call is allowed to prevent concurrent (out of order)
/// updates to [currentUser] and [onCurrentUserChanged].
Future<GoogleSignInAccount> _addMethodCall(String method) async {
Future<GoogleSignInAccount> _addMethodCall(Function method, {bool canSkipCall = false}) async {
Future<GoogleSignInAccount> response;
if (_lastMethodCall == null) {
// First call
response = _callMethod(method);
} else {
response = _lastMethodCall.then((_) {
// If after the last completed call `currentUser` is not `null` and requested
// method is a sign in method, re-use the same authenticated user
// instead of making extra call to the native side.
const List<String> kSignInMethods = <String>[
'signIn',
'signInSilently'
];
if (kSignInMethods.contains(method) && _currentUser != null) {
if (canSkipCall && _currentUser != null) {
return _currentUser;
} else {
return _callMethod(method);
}
return _callMethod(method);
});
}
// Enqueue the response as the last method call
_lastMethodCall = _waitFor(response);
return response;
}
Expand Down Expand Up @@ -306,7 +283,7 @@ class GoogleSignIn {
Future<GoogleSignInAccount> signInSilently(
{bool suppressErrors = true}) async {
try {
return await _addMethodCall('signInSilently');
return _addMethodCall(GoogleSignInPlatform.instance.signInSilently, canSkipCall: true);
} catch (_) {
if (suppressErrors) {
return null;
Expand All @@ -319,7 +296,7 @@ class GoogleSignIn {
/// Returns a future that resolves to whether a user is currently signed in.
Future<bool> isSignedIn() async {
await _ensureInitialized();
return await channel.invokeMethod<bool>('isSignedIn');
return GoogleSignInPlatform.instance.isSignedIn();
}

/// Starts the interactive sign-in process.
Expand All @@ -333,16 +310,16 @@ class GoogleSignIn {
///
/// Re-authentication can be triggered only after [signOut] or [disconnect].
Future<GoogleSignInAccount> signIn() {
final Future<GoogleSignInAccount> result = _addMethodCall('signIn');
final Future<GoogleSignInAccount> result = _addMethodCall(GoogleSignInPlatform.instance.signIn, canSkipCall: true);
bool isCanceled(dynamic error) =>
error is PlatformException && error.code == kSignInCanceledError;
return result.catchError((dynamic _) => null, test: isCanceled);
}

/// Marks current user as being in the signed out state.
Future<GoogleSignInAccount> signOut() => _addMethodCall('signOut');
Future<GoogleSignInAccount> signOut() => _addMethodCall(GoogleSignInPlatform.instance.signOut);

/// Disconnects the current user from the app and revokes previous
/// authentication.
Future<GoogleSignInAccount> disconnect() => _addMethodCall('disconnect');
Future<GoogleSignInAccount> disconnect() => _addMethodCall(GoogleSignInPlatform.instance.disconnect);
}
2 changes: 2 additions & 0 deletions packages/google_sign_in/google_sign_in/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ dependencies:
flutter:
sdk: flutter
meta: ^1.0.4
google_sign_in_platform_interface:
path: ../google_sign_in_platform_interface

dev_dependencies:
http: ^0.12.0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1.0.0

* Initial release.
27 changes: 27 additions & 0 deletions packages/google_sign_in/google_sign_in_platform_interface/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# google_sign_in_platform_interface

A common platform interface for the [`google_sign_in`][1] plugin.

This interface allows platform-specific implementations of the `google_sign_in`
plugin, as well as the plugin itself, to ensure they are supporting the
same interface.

# Usage

To implement a new platform-specific implementation of `google_sign_in`, extend
[`GoogleSignInPlatform`][2] with an implementation that performs the
platform-specific behavior, and when you register your plugin, set the default
`GoogleSignInPlatform` by calling
`GoogleSignInPlatform.instance = MyPlatformGoogleSignIn()`.

# Note on breaking changes

Strongly prefer non-breaking changes (such as adding a method to the interface)
over breaking changes for this package.

See https://flutter.dev/go/platform-interface-breaking-changes for a discussion
on why a less-clean interface is preferable to a breaking change.

[1]: ../google_sign_in
[2]: lib/google_sign_in_platform_interface.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2017 The Chromium 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 'dart:async';

import 'package:meta/meta.dart' show required;

import 'method_channel_google_sign_in.dart';

enum SignInOption { standard, games }

class GoogleSignInUserData {
GoogleSignInUserData(
{this.displayName, this.email, this.id, this.photoUrl, this.idToken});
String displayName;
String email;
String id;
String photoUrl;
String idToken;
}

class GoogleSignInTokenData {
GoogleSignInTokenData({this.idToken, this.accessToken});
String idToken;
String accessToken;
}

/// The interface that implementations of google_sign_in must implement.
///
/// Platform implementations that live in a separate package should extend this
/// class rather than implement it as `google_sign_in` does not consider newly
/// added methods to be breaking changes. Extending this class (using `extends`)
/// ensures that the subclass will get the default implementation, while
/// platform implementations that `implements` this interface will be broken by
/// newly added [GoogleSignInPlatform] methods.
abstract class GoogleSignInPlatform {
/// The default instance of [GoogleSignInPlatform] to use.
///
/// Platform-specific plugins should override this with their own
/// platform-specific class that extends [GoogleSignInPlatform] when they
/// register themselves.
///
/// Defaults to [MethodChannelGoogleSignIn].
static GoogleSignInPlatform instance = MethodChannelGoogleSignIn();

Future<void> init(
{@required String hostedDomain,
List<String> scopes,
SignInOption signInOption,
String clientId}) async {
throw UnimplementedError('init() has not been implemented.');
}

Future<GoogleSignInUserData> signInSilently() async {
throw UnimplementedError('signInSilently() has not been implemented.');
}

Future<GoogleSignInUserData> signIn() async {
throw UnimplementedError('signIn() has not been implemented.');
}

Future<GoogleSignInTokenData> getTokens(
{@required String email, bool shouldRecoverAuth}) async {
throw UnimplementedError('getTokens() has not been implemented.');
}

Future<void> signOut() async {
throw UnimplementedError('signOut() has not been implemented.');
}

Future<void> disconnect() async {
throw UnimplementedError('disconnect() has not been implemented.');
}

Future<bool> isSignedIn() async {
throw UnimplementedError('isSignedIn() has not been implemented.');
}

Future<void> clearAuthCache({@required String token}) async {
throw UnimplementedError('clearAuthCache() has not been implemented.');
}
}
Loading