Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[google_sign_in_web] Fix race condition on init. #2455

Merged
merged 7 commits into from
Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions packages/google_sign_in/google_sign_in_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.8.3

* Fix initialization error that causes https://github.com/flutter/flutter/issues/48527
* Throw a PlatformException when there's an initialization problem (like wrong server-side config).

## 0.8.2+1

* Add a non-op Android implementation to avoid a flaky Gradle issue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
import 'dart:async';
import 'dart:html' as html;

import 'package:flutter/services.dart';
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:js/js.dart';
import 'package:meta/meta.dart';

import 'src/generated/gapiauth2.dart' as auth2;
// TODO: Remove once this lands https://github.com/dart-lang/language/issues/671
import 'src/generated/gapiauth2.dart' show GoogleAuthExtensions;
import 'src/load_gapi.dart' as gapi;
import 'src/utils.dart' show gapiUserToPluginUserData;

Expand All @@ -39,14 +38,15 @@ class GoogleSignInPlugin extends GoogleSignInPlatform {
}

Future<void> _isGapiInitialized;
Future<void> _isAuthInitialized = Completer<void>().future;
ditman marked this conversation as resolved.
Show resolved Hide resolved

/// This is only exposed for testing. It shouldn't be accessed by users of the
/// plugin as it could break at any point.
@visibleForTesting
Future<void> get initialized => _isGapiInitialized;
Future<void> get initialized =>
Future.wait([_isGapiInitialized, _isAuthInitialized]);

String _autoDetectedClientId;
FutureOr<auth2.GoogleUser> _lastSeenUser;

/// Factory method that initializes the plugin with [GoogleSignInPlatform].
static void registerWith(Registrar registrar) {
Expand All @@ -72,7 +72,7 @@ class GoogleSignInPlugin extends GoogleSignInPlatform {
'Check https://developers.google.com/identity/protocols/googlescopes '
'for a list of valid OAuth 2.0 scopes.');

await initialized;
await _isGapiInitialized;

final auth2.GoogleAuth auth = auth2.init(auth2.ClientConfig(
hosted_domain: hostedDomain,
Expand All @@ -81,19 +81,25 @@ class GoogleSignInPlugin extends GoogleSignInPlatform {
client_id: appClientId,
));

// Subscribe to changes in the auth instance returned by init,
// and cache the _lastSeenUser as we get notified of new values.
final Completer<auth2.GoogleUser> initUserCompleter =
Completer<auth2.GoogleUser>();

auth.currentUser.listen(allowInterop((auth2.GoogleUser nextUser) {
if (!initUserCompleter.isCompleted) {
initUserCompleter.complete(nextUser);
} else {
_lastSeenUser = nextUser;
}
Completer<void> isAuthInitialized = Completer<void>();
_isAuthInitialized = isAuthInitialized.future;

auth.then(allowInterop((auth2.GoogleAuth initializedAuth) {
// onSuccess
ditman marked this conversation as resolved.
Show resolved Hide resolved

// TODO: https://github.com/flutter/flutter/issues/48528
// This plugin doesn't notify the app of external changes to the
// state of the authentication, i.e: if you logout elsewhere...

isAuthInitialized.complete();
}), allowInterop((dynamic reason) {
// onError
throw (PlatformException(
ditman marked this conversation as resolved.
Show resolved Hide resolved
code: 'google_sign_in',
message: reason.error,
details: reason.details,
));
}));
_lastSeenUser = initUserCompleter.future;

return null;
}
Expand All @@ -102,7 +108,8 @@ class GoogleSignInPlugin extends GoogleSignInPlatform {
Future<GoogleSignInUserData> signInSilently() async {
await initialized;

return gapiUserToPluginUserData(await _lastSeenUser);
return gapiUserToPluginUserData(
await auth2.getAuthInstance().currentUser.get());
}

@override
Expand Down Expand Up @@ -154,7 +161,6 @@ class GoogleSignInPlugin extends GoogleSignInPlatform {
Future<void> clearAuthCache({String token}) async {
await initialized;

_lastSeenUser = null;
return auth2.getAuthInstance().disconnect();
}
}
2 changes: 1 addition & 1 deletion packages/google_sign_in/google_sign_in_web/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: google_sign_in_web
description: Flutter plugin for Google Sign-In, a secure authentication system
for signing in with a Google account on Android, iOS and Web.
homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_web
version: 0.8.2+1
version: 0.8.3

flutter:
plugin:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ void main() {

GoogleSignInPlugin plugin;

setUp(() async {
setUp(() {
plugin = GoogleSignInPlugin();
await plugin.initialized;
});

test('Init requires clientId', () async {
Expand All @@ -47,12 +46,13 @@ void main() {
});

group('Successful .init, then', () {
setUp(() {
plugin.init(
setUp(() async {
await plugin.init(
hostedDomain: 'foo',
scopes: <String>['some', 'scope'],
clientId: '1234',
);
await plugin.initialized;
});

test('signInSilently', () async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
import 'dart:html' as html;

import 'package:flutter_test/flutter_test.dart';
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
import 'package:google_sign_in_web/google_sign_in_web.dart';
import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks;
import 'utils.dart';

void main() {
gapiUrl = toBase64Url(gapi_mocks.gapiInitSuccess());
gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(GoogleSignInUserData()));

test('Plugin is initialized after GAPI fully loads', () async {
test('Plugin is initialized after GAPI fully loads and init is called',
() async {
expect(
html.querySelector('script[src^="data:"]'),
isNull,
Expand All @@ -26,6 +28,12 @@ void main() {
isNotNull,
reason: 'Mock script should be injected',
);
expect(
plugin.initialized,
doesNotComplete,
reason: 'The plugin should only complete the future after calling .init',
);
await plugin.init(hostedDomain: '', clientId: '');
await plugin.initialized;
expect(
plugin.initialized,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ import 'src/gapi.dart';
import 'src/google_user.dart';
import 'src/test_iife.dart';

part 'src/gapi_load.dart';
part 'src/auth2_init.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ var mockUser = ${googleUser(userData)};
function GapiAuth2() {}
GapiAuth2.prototype.init = function (initOptions) {
return {
then: (onSuccess, onError) => {
window.setTimeout(() => {
onSuccess(window.gapi.auth2);
}, 30);
},
currentUser: {
listen: (cb) => {
window.setTimeout(() => {
Expand Down

This file was deleted.