From 8c55b955b28d70d91c8d1540224c0e7c75406382 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Thu, 31 Oct 2019 16:25:11 -0700 Subject: [PATCH 1/6] Add google_sign_in_web plugin. --- .../google_sign_in_web/CHANGELOG.md | 3 + .../google_sign_in/google_sign_in_web/LICENSE | 26 + .../google_sign_in_web/README.md | 66 +++ .../lib/google_sign_in_web.dart | 149 ++++++ .../lib/src/generated/gapi.dart | 455 +++++++++++++++++ .../lib/src/generated/gapiauth2.dart | 460 ++++++++++++++++++ .../google_sign_in_web/lib/src/load_gapi.dart | 47 ++ .../google_sign_in_web/lib/src/utils.dart | 42 ++ .../google_sign_in_web/pubspec.yaml | 31 ++ .../test/gapi_load_test.dart | 20 + .../test/gapi_mocks/gapi_mocks.dart | 6 + .../test/gapi_mocks/src/gapi_load.dart | 27 + .../google_sign_in_web/test/utils.dart | 14 + 13 files changed, 1346 insertions(+) create mode 100644 packages/google_sign_in/google_sign_in_web/CHANGELOG.md create mode 100644 packages/google_sign_in/google_sign_in_web/LICENSE create mode 100644 packages/google_sign_in/google_sign_in_web/README.md create mode 100644 packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/utils.dart create mode 100644 packages/google_sign_in/google_sign_in_web/pubspec.yaml create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/utils.dart diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md new file mode 100644 index 000000000000..989b09e4d9ac --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* Flutter for web initial release \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_web/LICENSE b/packages/google_sign_in/google_sign_in_web/LICENSE new file mode 100644 index 000000000000..4da9688730d1 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/LICENSE @@ -0,0 +1,26 @@ +Copyright 2016, the Flutter project 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. diff --git a/packages/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md new file mode 100644 index 000000000000..63f85c45a6e9 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/README.md @@ -0,0 +1,66 @@ +# google_sign_in_web + +The web implementation of [google_sign_in](https://pub.dev/google_sign_in/google_sign_in) + +## Web integration + +First, go through the instructions [here](https://developers.google.com/identity/sign-in/web/sign-in#before_you_begin) to create your Google Sign-In OAuth client ID. + +On your `web/index.html` file, add the following `meta` tag, somewhere in the +`head` of the document: + +``` + +``` + +Read the rest of the instructions if you need to add extra APIs (like Google People API). + + +## Usage + +### Import the package +To use this plugin, follow the [plugin installation instructions](https://pub.dartlang.org/packages/google_sign_in#pub-pkg-tab-installing). + +### Use the plugin +Add the following import to your Dart code: + +```dart +import 'package:google_sign_in/google_sign_in.dart'; +``` + +Initialize GoogleSignIn with the scopes you want: + +```dart +GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + 'email', + 'https://www.googleapis.com/auth/contacts.readonly', + ], +); +``` +[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes). + +You can now use the `GoogleSignIn` class to authenticate in your Dart code, e.g. + +```dart +Future _handleSignIn() async { + try { + await _googleSignIn.signIn(); + } catch (error) { + print(error); + } +} +``` + +## Example + +Find the example wiring in the [Google sign-in example application](https://github.com/flutter/plugins/blob/master/packages/google_sign_in/google_sign_in/example/lib/main.dart). + +## API details + +See the [google_sign_in.dart](https://github.com/flutter/plugins/blob/master/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart) for more API details. + +## Issues and feedback + +Please file [issues](https://github.com/flutter/flutter/issues/new) +to send feedback or report a bug. Thank you! diff --git a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart new file mode 100644 index 000000000000..ae6d1df9476e --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart @@ -0,0 +1,149 @@ +// 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 'dart:html' as html; + +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; + +const String _kClientIdMetaSelector = 'meta[name=google-signin-client_id]'; +const String _kClientIdAttributeName = 'content'; + +@visibleForTesting +String gapiUrl = 'https://apis.google.com/js/platform.js'; + +/// Implementation of the google_sign_in plugin for Web +class GoogleSignInPlugin extends GoogleSignInPlatform { + GoogleSignInPlugin() { + _autoDetectedClientId = html + .querySelector(_kClientIdMetaSelector) + ?.getAttribute(_kClientIdAttributeName); + + _isGapiInitialized = gapi.inject(gapiUrl).then((_) => gapi.init()).then((_) { + isInitialized = true; + }); + } + + Future _isGapiInitialized; + + @visibleForTesting + Future get isInitializing => _isGapiInitialized; + @visibleForTesting + bool isInitialized; + + String _autoDetectedClientId; + FutureOr _lastSeenUser; + + static void registerWith(Registrar registrar) { + GoogleSignInPlatform.instance = GoogleSignInPlugin(); + } + + @override + Future init( + {@required String hostedDomain, + List scopes = const [], + SignInOption signInOption = SignInOption.standard, + String clientId}) async { + final String appClientId = clientId ?? _autoDetectedClientId; + assert( + appClientId != null, + 'ClientID not set. Either set it on a ' + ' tag,' + ' or pass clientId when calling init()'); + + await _isGapiInitialized; + + // This init returns an user, so let's wait for its future + final auth2.GoogleAuth auth = auth2.init(auth2.ClientConfig( + hosted_domain: hostedDomain, + // The js lib wants a space-separated list of values + scope: scopes.join(' '), + client_id: appClientId, + )); + + // Subscribe to changes in the auth user, and cache the latest seen for signInSilently + final Completer initUserCompleter = + Completer(); + + auth.currentUser.listen(allowInterop((auth2.GoogleUser nextUser) { + if (!initUserCompleter.isCompleted) { + initUserCompleter.complete(nextUser); + } else { + _lastSeenUser = nextUser; + } + })); + _lastSeenUser = initUserCompleter.future; + + return null; + } + + @override + Future signInSilently() async { + await _isGapiInitialized; + + return gapiUserToPluginUserData(await _lastSeenUser); + } + + @override + Future signIn() async { + await _isGapiInitialized; + + return gapiUserToPluginUserData(await auth2.getAuthInstance().signIn()); + } + + @override + Future getTokens( + {@required String email, bool shouldRecoverAuth}) async { + await _isGapiInitialized; + + final auth2.GoogleUser currentUser = + auth2.getAuthInstance()?.currentUser?.get(); + final auth2.AuthResponse response = currentUser.getAuthResponse(); + + return GoogleSignInTokenData( + idToken: response.id_token, accessToken: response.access_token); + } + + @override + Future signOut() async { + await _isGapiInitialized; + + return auth2.getAuthInstance().signOut(); + } + + @override + Future disconnect() async { + await _isGapiInitialized; + + final auth2.GoogleUser currentUser = + auth2.getAuthInstance()?.currentUser?.get(); + return currentUser.disconnect(); + } + + @override + Future isSignedIn() async { + await _isGapiInitialized; + + final auth2.GoogleUser currentUser = + auth2.getAuthInstance()?.currentUser?.get(); + return currentUser.isSignedIn(); + } + + @override + Future clearAuthCache({String token}) async { + await _isGapiInitialized; + + _lastSeenUser = null; + return auth2.getAuthInstance().disconnect(); + } +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart new file mode 100644 index 000000000000..2e5b641bbe46 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart @@ -0,0 +1,455 @@ +@JS() +library gapi; + +import "package:js/js.dart"; +import "package:js/js_util.dart" show promiseToFuture; + +/// Type definitions for Google API Client +/// Project: https://github.com/google/google-api-javascript-client +/// Definitions by: Frank M , grant +/// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/// TypeScript Version: 2.3 + +/// The OAuth 2.0 token object represents the OAuth 2.0 token and any associated data. +@anonymous +@JS() +abstract class GoogleApiOAuth2TokenObject { + /// The OAuth 2.0 token. Only present in successful responses + external String get access_token; + external set access_token(String v); + + /// Details about the error. Only present in error responses + external String get error; + external set error(String v); + + /// The duration, in seconds, the token is valid for. Only present in successful responses + external String get expires_in; + external set expires_in(String v); + external GoogleApiOAuth2TokenSessionState get session_state; + external set session_state(GoogleApiOAuth2TokenSessionState v); + + /// The Google API scopes related to this token + external String get state; + external set state(String v); + external factory GoogleApiOAuth2TokenObject( + {String access_token, + String error, + String expires_in, + GoogleApiOAuth2TokenSessionState session_state, + String state}); +} + +@anonymous +@JS() +abstract class GoogleApiOAuth2TokenSessionState { + external dynamic /*{ + authuser: string, + }*/ + get extraQueryParams; + external set extraQueryParams( + dynamic + /*{ + authuser: string, + }*/ + v); + external factory GoogleApiOAuth2TokenSessionState( + {dynamic + /*{ + authuser: string, + }*/ + extraQueryParams}); +} + +/// Fix for #8215 +/// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8215 +/// Usage example: +/// https://developers.google.com/identity/sign-in/web/session-state + +// Module gapi +typedef void LoadCallback( + [dynamic args1, + dynamic args2, + dynamic args3, + dynamic args4, + dynamic args5]); + +@anonymous +@JS() +abstract class LoadConfig { + external LoadCallback get callback; + external set callback(LoadCallback v); + external Function get onerror; + external set onerror(Function v); + external num get timeout; + external set timeout(num v); + external Function get ontimeout; + external set ontimeout(Function v); + external factory LoadConfig( + {LoadCallback callback, + Function onerror, + num timeout, + Function ontimeout}); +} + +/*type CallbackOrConfig = LoadConfig | LoadCallback;*/ +/// Pragmatically initialize gapi class member. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiloadlibraries-callbackorconfig +@JS("gapi.load") +external void load( + String apiName, dynamic /*LoadConfig|LoadCallback*/ callback); +// End module gapi + +// Module gapi.auth +/// Initiates the OAuth 2.0 authorization process. The browser displays a popup window prompting the user authenticate and authorize. After the user authorizes, the popup closes and the callback function fires. +@JS("gapi.auth.authorize") +external void authorize( + dynamic + /*{ + /** + * The application's client ID. + */ + client_id?: string; + /** + * If true, then login uses "immediate mode", which means that the token is refreshed behind the scenes, and no UI is shown to the user. + */ + immediate?: boolean; + /** + * The OAuth 2.0 response type property. Default: token + */ + response_type?: string; + /** + * The auth scope or scopes to authorize. Auth scopes for individual APIs can be found in their documentation. + */ + scope?: any; + /** + * The user to sign in as. -1 to toggle a multi-account chooser, 0 to default to the user's current account, and 1 to automatically sign in if the user is signed into Google Plus. + */ + authuser?: number; + }*/ + params, + dynamic callback(GoogleApiOAuth2TokenObject token)); + +/// Initializes the authorization feature. Call this when the client loads to prevent popup blockers from blocking the auth window on gapi.auth.authorize calls. +@JS("gapi.auth.init") +external void init(dynamic callback()); + +/// Retrieves the OAuth 2.0 token for the application. +@JS("gapi.auth.getToken") +external GoogleApiOAuth2TokenObject getToken(); + +/// Sets the OAuth 2.0 token for the application. +@JS("gapi.auth.setToken") +external void setToken(GoogleApiOAuth2TokenObject token); + +/// Initiates the client-side Google+ Sign-In OAuth 2.0 flow. +/// When the method is called, the OAuth 2.0 authorization dialog is displayed to the user and when they accept, the callback function is called. +@JS("gapi.auth.signIn") +external void signIn( + dynamic + /*{ + /** + * Your OAuth 2.0 client ID that you obtained from the Google Developers Console. + */ + clientid?: string; + /** + * Directs the sign-in button to store user and session information in a session cookie and HTML5 session storage on the user's client for the purpose of minimizing HTTP traffic and distinguishing between multiple Google accounts a user might be signed into. + */ + cookiepolicy?: string; + /** + * A function in the global namespace, which is called when the sign-in button is rendered and also called after a sign-in flow completes. + */ + callback?: () => void; + /** + * If true, all previously granted scopes remain granted in each incremental request, for incremental authorization. The default value true is correct for most use cases; use false only if employing delegated auth, where you pass the bearer token to a less-trusted component with lower programmatic authority. + */ + includegrantedscopes?: boolean; + /** + * If your app will write moments, list the full URI of the types of moments that you intend to write. + */ + requestvisibleactions?: any; + /** + * The OAuth 2.0 scopes for the APIs that you would like to use as a space-delimited list. + */ + scope?: any; + /** + * If you have an Android app, you can drive automatic Android downloads from your web sign-in flow. + */ + apppackagename?: string; + }*/ + params); + +/// Signs a user out of your app without logging the user out of Google. This method will only work when the user is signed in with Google+ Sign-In. +@JS("gapi.auth.signOut") +external void signOut(); +// End module gapi.auth + +// Module gapi.client +@anonymous +@JS() +abstract class RequestOptions { + /// The URL to handle the request + external String get path; + external set path(String v); + + /// The HTTP request method to use. Default is GET + external String get method; + external set method(String v); + + /// URL params in key-value pair form + external dynamic get params; + external set params(dynamic v); + + /// Additional HTTP request headers + external dynamic get headers; + external set headers(dynamic v); + + /// The HTTP request body (applies to PUT or POST). + external dynamic get body; + external set body(dynamic v); + + /// If supplied, the request is executed immediately and no gapi.client.HttpRequest object is returned + external dynamic Function() get callback; + external set callback(dynamic Function() v); + external factory RequestOptions( + {String path, + String method, + dynamic params, + dynamic headers, + dynamic body, + dynamic Function() callback}); +} + +@anonymous +@JS() +abstract class _RequestOptions { + @JS("gapi.client.init") + external Promise client_init( + dynamic + /*{ + /** + * The API Key to use. + */ + apiKey?: string; + /** + * An array of discovery doc URLs or discovery doc JSON objects. + */ + discoveryDocs?: string[]; + /** + * The app's client ID, found and created in the Google Developers Console. + */ + clientId?: string; + /** + * The scopes to request, as a space-delimited string. + */ + scope?: string, + + hosted_domain?: string; + }*/ + args); +} + +extension RequestOptionsExtensions on RequestOptions {} + +@anonymous +@JS() +abstract class TokenObject { + /// The access token to use in requests. + external String get access_token; + external set access_token(String v); + external factory TokenObject({String access_token}); +} + +/// Creates a HTTP request for making RESTful requests. +/// An object encapsulating the various arguments for this method. +@JS("gapi.client.request") +external HttpRequest request(RequestOptions args); + +/// Creates an RPC Request directly. The method name and version identify the method to be executed and the RPC params are provided upon RPC creation. +@JS("gapi.client.rpcRequest") +external RpcRequest rpcRequest(String method, + [String version, dynamic rpcParams]); + +/// Sets the API key for the application. +@JS("gapi.client.setApiKey") +external void setApiKey(String apiKey); + +/// Retrieves the OAuth 2.0 token for the application. +@JS("gapi.client.getToken") +external GoogleApiOAuth2TokenObject client_getToken(); + +/// Sets the authentication token to use in requests. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiclientsettokentokenobject +@JS("gapi.client.setToken") +external void client_setToken(TokenObject /*TokenObject|Null*/ token); + +@anonymous +@JS() +abstract class HttpRequestFulfilled { + external T get result; + external set result(T v); + external String get body; + external set body(String v); + external List get headers; + external set headers(List v); + external num get status; + external set status(num v); + external String get statusText; + external set statusText(String v); + external factory HttpRequestFulfilled( + {T result, + String body, + List headers, + num status, + String statusText}); +} + +@anonymous +@JS() +abstract class _HttpRequestFulfilled { + /*external Promise client_load(String name, String version);*/ + /*external void client_load(String name, String version, dynamic callback(), + [String url]); +*/ + @JS("gapi.client.load") + external dynamic /*Promise|void*/ client_load( + String name, String version, + [dynamic callback(), String url]); +} + +extension HttpRequestFulfilledExtensions on HttpRequestFulfilled {} + +@anonymous +@JS() +abstract class HttpRequestRejected { + external dynamic /*dynamic|bool*/ get result; + external set result(dynamic /*dynamic|bool*/ v); + external String get body; + external set body(String v); + external List get headers; + external set headers(List v); + external num get status; + external set status(num v); + external String get statusText; + external set statusText(String v); + external factory HttpRequestRejected( + {dynamic /*dynamic|bool*/ result, + String body, + List headers, + num status, + String statusText}); +} + +/// HttpRequest supports promises. +/// See Google API Client JavaScript Using Promises https://developers.google.com/api-client-library/javascript/features/promises +@JS("gapi.client.HttpRequestPromise") +class HttpRequestPromise { + // @Ignore + HttpRequestPromise.fakeConstructor$(); +} + +@JS("gapi.client.HttpRequestPromise") +abstract class _HttpRequestPromise { + /// Taken and adapted from https://github.com/Microsoft/TypeScript/blob/v2.3.1/lib/lib.es5.d.ts#L1343 + external Promise then/**/( + [dynamic /*TResult1|PromiseLike Function(HttpRequestFulfilled)|dynamic|Null*/ onfulfilled, + dynamic /*TResult2|PromiseLike Function(HttpRequestRejected)|dynamic|Null*/ onrejected, + dynamic opt_context]); +} + +extension HttpRequestPromiseExtensions on HttpRequestPromise { + Future then( + [dynamic /*TResult1|PromiseLike Function(HttpRequestFulfilled)|dynamic|Null*/ onfulfilled, + dynamic /*TResult2|PromiseLike Function(HttpRequestRejected)|dynamic|Null*/ onrejected, + dynamic opt_context]) { + final Object t = this; + final _HttpRequestPromise tt = t; + return promiseToFuture(tt.then(onfulfilled, onrejected, opt_context)); + } +} + +/// An object encapsulating an HTTP request. This object is not instantiated directly, rather it is returned by gapi.client.request. +@JS("gapi.client.HttpRequest") +class HttpRequest extends HttpRequestPromise { + // @Ignore + HttpRequest.fakeConstructor$() : super.fakeConstructor$(); + + /// Executes the request and runs the supplied callback on response. + external void execute( + dynamic callback( + + /// contains the response parsed as JSON. If the response is not JSON, this field will be false. + T jsonResp, + + /// is the HTTP response. It is JSON, and can be parsed to an object + dynamic + /*{ + body: string; + headers: any[]; + status: number; + statusText: string; + }*/ + rawResp)); +} + +/// Represents an HTTP Batch operation. Individual HTTP requests are added with the add method and the batch is executed using execute. +@JS("gapi.client.HttpBatch") +class HttpBatch { + // @Ignore + HttpBatch.fakeConstructor$(); + + /// Adds a gapi.client.HttpRequest to the batch. + external void add(HttpRequest httpRequest, + [dynamic + /*{ + /** + * Identifies the response for this request in the map of batch responses. If one is not provided, the system generates a random ID. + */ + id: string; + callback: ( + /** + * is the response for this request only. Its format is defined by the API method being called. + */ + individualResponse: any, + /** + * is the raw batch ID-response map as a string. It contains all responses to all requests in the batch. + */ + rawBatchResponse: any + ) => any + }*/ + opt_params]); + + /// Executes all requests in the batch. The supplied callback is executed on success or failure. + external void execute( + dynamic callback( + + /// is an ID-response map of each requests response. + dynamic responseMap, + + /// is the same response, but as an unparsed JSON-string. + String rawBatchResponse)); +} + +/// Similar to gapi.client.HttpRequest except this object encapsulates requests generated by registered methods. +@JS("gapi.client.RpcRequest") +class RpcRequest { + // @Ignore + RpcRequest.fakeConstructor$(); + + /// Executes the request and runs the supplied callback with the response. + external void callback( + void callback( + + /// contains the response parsed as JSON. If the response is not JSON, this field will be false. + dynamic jsonResp, + + /// is the same as jsonResp, except it is a raw string that has not been parsed. It is typically used when the response is not JSON. + String rawResp)); +} + +// End module gapi.client +@JS() +abstract class Promise { + external factory Promise( + void executor(void resolve(T result), Function reject)); + external Promise then(void onFulfilled(T result), [Function onRejected]); +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart new file mode 100644 index 000000000000..731d4b2c7b1e --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart @@ -0,0 +1,460 @@ +@JS() +library gapiauth2; + +import "package:js/js.dart"; +import "package:js/js_util.dart" show promiseToFuture; + +/// Type definitions for non-npm package Google Sign-In API 0.0 +/// Project: https://developers.google.com/identity/sign-in/web/ +/// Definitions by: Derek Lawless +/// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/// TypeScript Version: 2.3 + +/// + +// Module gapi.auth2 +/// GoogleAuth is a singleton class that provides methods to allow the user to sign in with a Google account, +/// get the user's current sign-in status, get specific data from the user's Google profile, +/// request additional scopes, and sign out from the current account. +@JS("gapi.auth2.GoogleAuth") +class GoogleAuth { + // @Ignore + GoogleAuth.fakeConstructor$(); + external IsSignedIn get isSignedIn; + external set isSignedIn(IsSignedIn v); + external CurrentUser get currentUser; + external set currentUser(CurrentUser v); + + /// Calls the onInit function when the GoogleAuth object is fully initialized, or calls the onFailure function if + /// initialization fails. + external dynamic then(dynamic onInit(GoogleAuth googleAuth), + [dynamic onFailure(dynamic /*{error: string, details: string}*/ reason)]); + + /// Signs out all accounts from the application. + external dynamic signOut(); + + /// Revokes all of the scopes that the user granted. + external dynamic disconnect(); + + /// Attaches the sign-in flow to the specified container's click handler. + external dynamic attachClickHandler( + dynamic container, + SigninOptions options, + dynamic onsuccess(GoogleUser googleUser), + dynamic onfailure(String reason)); +} + +@anonymous +@JS() +abstract class _GoogleAuth { + external Promise signIn( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]); + external Promise grantOfflineAccess( + [OfflineAccessOptions options]); +} + +extension GoogleAuthExtensions on GoogleAuth { + Future signIn( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]) { + final Object t = this; + final _GoogleAuth tt = t; + return promiseToFuture(tt.signIn(options)); + } + + Future grantOfflineAccess( + [OfflineAccessOptions options]) { + final Object t = this; + final _GoogleAuth tt = t; + return promiseToFuture(tt.grantOfflineAccess(options)); + } +} + +@anonymous +@JS() +abstract class IsSignedIn { + /// Returns whether the current user is currently signed in. + external bool get(); + + /// Listen for changes in the current user's sign-in state. + external void listen(dynamic listener(bool signedIn)); +} + +@anonymous +@JS() +abstract class CurrentUser { + /// Returns a GoogleUser object that represents the current user. Note that in a newly-initialized + /// GoogleAuth instance, the current user has not been set. Use the currentUser.listen() method or the + /// GoogleAuth.then() to get an initialized GoogleAuth instance. + external GoogleUser get(); + + /// Listen for changes in currentUser. + external void listen(dynamic listener(GoogleUser user)); +} + +@anonymous +@JS() +abstract class SigninOptions { + /// The package name of the Android app to install over the air. + /// See Android app installs from your web site: + /// https://developers.google.com/identity/sign-in/web/android-app-installs + external String get app_package_name; + external set app_package_name(String v); + + /// Fetch users' basic profile information when they sign in. + /// Adds 'profile', 'email' and 'openid' to the requested scopes. + /// True if unspecified. + external bool get fetch_basic_profile; + external set fetch_basic_profile(bool v); + + /// Specifies whether to prompt the user for re-authentication. + /// See OpenID Connect Request Parameters: + /// https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters + external String get prompt; + external set prompt(String v); + + /// The scopes to request, as a space-delimited string. + /// Optional if fetch_basic_profile is not set to false. + external String get scope; + external set scope(String v); + + /// The UX mode to use for the sign-in flow. + /// By default, it will open the consent flow in a popup. + external String /*'popup'|'redirect'*/ get ux_mode; + external set ux_mode(String /*'popup'|'redirect'*/ v); + + /// If using ux_mode='redirect', this parameter allows you to override the default redirect_uri that will be used at the end of the consent flow. + /// The default redirect_uri is the current URL stripped of query parameters and hash fragment. + external String get redirect_uri; + external set redirect_uri(String v); + external factory SigninOptions( + {String app_package_name, + bool fetch_basic_profile, + String prompt, + String scope, + String /*'popup'|'redirect'*/ ux_mode, + String redirect_uri}); +} + +/// Definitions by: John +/// Interface that represents the different configuration parameters for the GoogleAuth.grantOfflineAccess(options) method. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2offlineaccessoptions +@anonymous +@JS() +abstract class OfflineAccessOptions { + external String get scope; + external set scope(String v); + external String /*'select_account'|'consent'*/ get prompt; + external set prompt(String /*'select_account'|'consent'*/ v); + external String get app_package_name; + external set app_package_name(String v); + external factory OfflineAccessOptions( + {String scope, + String /*'select_account'|'consent'*/ prompt, + String app_package_name}); +} + +/// Interface that represents the different configuration parameters for the gapi.auth2.init method. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2clientconfig +@anonymous +@JS() +abstract class ClientConfig { + /// The app's client ID, found and created in the Google Developers Console. + external String get client_id; + external set client_id(String v); + + /// The domains for which to create sign-in cookies. Either a URI, single_host_origin, or none. + /// Defaults to single_host_origin if unspecified. + external String get cookie_policy; + external set cookie_policy(String v); + + /// The scopes to request, as a space-delimited string. Optional if fetch_basic_profile is not set to false. + external String get scope; + external set scope(String v); + + /// Fetch users' basic profile information when they sign in. Adds 'profile' and 'email' to the requested scopes. True if unspecified. + external bool get fetch_basic_profile; + external set fetch_basic_profile(bool v); + + /// The Google Apps domain to which users must belong to sign in. This is susceptible to modification by clients, + /// so be sure to verify the hosted domain property of the returned user. Use GoogleUser.getHostedDomain() on the client, + /// and the hd claim in the ID Token on the server to verify the domain is what you expected. + external String get hosted_domain; + external set hosted_domain(String v); + + /// Used only for OpenID 2.0 client migration. Set to the value of the realm that you are currently using for OpenID 2.0, + /// as described in OpenID 2.0 (Migration). + external String get openid_realm; + external set openid_realm(String v); + + /// The UX mode to use for the sign-in flow. + /// By default, it will open the consent flow in a popup. + external String /*'popup'|'redirect'*/ get ux_mode; + external set ux_mode(String /*'popup'|'redirect'*/ v); + + /// If using ux_mode='redirect', this parameter allows you to override the default redirect_uri that will be used at the end of the consent flow. + /// The default redirect_uri is the current URL stripped of query parameters and hash fragment. + external String get redirect_uri; + external set redirect_uri(String v); + external factory ClientConfig( + {String client_id, + String cookie_policy, + String scope, + bool fetch_basic_profile, + String hosted_domain, + String openid_realm, + String /*'popup'|'redirect'*/ ux_mode, + String redirect_uri}); +} + +@JS("gapi.auth2.SigninOptionsBuilder") +class SigninOptionsBuilder { + // @Ignore + SigninOptionsBuilder.fakeConstructor$(); + external dynamic setAppPackageName(String name); + external dynamic setFetchBasicProfile(bool fetch); + external dynamic setPrompt(String prompt); + external dynamic setScope(String scope); +} + +@anonymous +@JS() +abstract class BasicProfile { + external String getId(); + external String getName(); + external String getGivenName(); + external String getFamilyName(); + external String getImageUrl(); + external String getEmail(); +} + +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authresponse +@anonymous +@JS() +abstract class AuthResponse { + external String get access_token; + external set access_token(String v); + external String get id_token; + external set id_token(String v); + external String get login_hint; + external set login_hint(String v); + external String get scope; + external set scope(String v); + external num get expires_in; + external set expires_in(num v); + external num get first_issued_at; + external set first_issued_at(num v); + external num get expires_at; + external set expires_at(num v); + external factory AuthResponse( + {String access_token, + String id_token, + String login_hint, + String scope, + num expires_in, + num first_issued_at, + num expires_at}); +} + +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeconfig +@anonymous +@JS() +abstract class AuthorizeConfig { + external String get client_id; + external set client_id(String v); + external String get scope; + external set scope(String v); + external String get response_type; + external set response_type(String v); + external String get prompt; + external set prompt(String v); + external String get cookie_policy; + external set cookie_policy(String v); + external String get hosted_domain; + external set hosted_domain(String v); + external String get login_hint; + external set login_hint(String v); + external String get app_package_name; + external set app_package_name(String v); + external String get openid_realm; + external set openid_realm(String v); + external bool get include_granted_scopes; + external set include_granted_scopes(bool v); + external factory AuthorizeConfig( + {String client_id, + String scope, + String response_type, + String prompt, + String cookie_policy, + String hosted_domain, + String login_hint, + String app_package_name, + String openid_realm, + bool include_granted_scopes}); +} + +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeresponse +@anonymous +@JS() +abstract class AuthorizeResponse { + external String get access_token; + external set access_token(String v); + external String get id_token; + external set id_token(String v); + external String get code; + external set code(String v); + external String get scope; + external set scope(String v); + external num get expires_in; + external set expires_in(num v); + external num get first_issued_at; + external set first_issued_at(num v); + external num get expires_at; + external set expires_at(num v); + external String get error; + external set error(String v); + external String get error_subtype; + external set error_subtype(String v); + external factory AuthorizeResponse( + {String access_token, + String id_token, + String code, + String scope, + num expires_in, + num first_issued_at, + num expires_at, + String error, + String error_subtype}); +} + +/// A GoogleUser object represents one user account. +@anonymous +@JS() +abstract class GoogleUser { + /// Get the user's unique ID string. + external String getId(); + + /// Returns true if the user is signed in. + external bool isSignedIn(); + + /// Get the user's Google Apps domain if the user signed in with a Google Apps account. + external String getHostedDomain(); + + /// Get the scopes that the user granted as a space-delimited string. + external String getGrantedScopes(); + + /// Get the user's basic profile information. + external BasicProfile getBasicProfile(); + + /// Get the response object from the user's auth session. + external AuthResponse getAuthResponse([bool includeAuthorizationData]); + + /// Returns true if the user granted the specified scopes. + external bool hasGrantedScopes(String scopes); + + /// Signs in the user. Use this method to request additional scopes for incremental + /// authorization or to sign in a user after the user has signed out. + /// When you use GoogleUser.signIn(), the sign-in flow skips the account chooser step. + /// See GoogleAuth.signIn(). + external dynamic signIn( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]); + + /// See GoogleUser.signIn() + external dynamic grant( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]); + + /// Get permission from the user to access the specified scopes offline. + /// When you use GoogleUser.grantOfflineAccess(), the sign-in flow skips the account chooser step. + /// See GoogleUser.grantOfflineAccess(). + external void grantOfflineAccess(String scopes); + + /// Revokes all of the scopes that the user granted. + external void disconnect(); +} + +@anonymous +@JS() +abstract class _GoogleUser { + external Promise reloadAuthResponse(); +} + +extension GoogleUserExtensions on GoogleUser { + Future reloadAuthResponse() { + final Object t = this; + final _GoogleUser tt = t; + return promiseToFuture(tt.reloadAuthResponse()); + } +} + +/// Initializes the GoogleAuth object. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2initparams +@JS("gapi.auth2.init") +external GoogleAuth init(ClientConfig params); + +/// Returns the GoogleAuth object. You must initialize the GoogleAuth object with gapi.auth2.init() before calling this method. +@JS("gapi.auth2.getAuthInstance") +external GoogleAuth getAuthInstance(); + +/// Performs a one time OAuth 2.0 authorization. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeparams-callback +@JS("gapi.auth2.authorize") +external void authorize( + AuthorizeConfig params, void callback(AuthorizeResponse response)); +// End module gapi.auth2 + +// Module gapi.signin2 +@JS("gapi.signin2.render") +external void render( + dynamic id, + dynamic + /*{ + /** + * The auth scope or scopes to authorize. Auth scopes for individual APIs can be found in their documentation. + */ + scope?: string; + + /** + * The width of the button in pixels (default: 120). + */ + width?: number; + + /** + * The height of the button in pixels (default: 36). + */ + height?: number; + + /** + * Display long labels such as "Sign in with Google" rather than "Sign in" (default: false). + */ + longtitle?: boolean; + + /** + * The color theme of the button: either light or dark (default: light). + */ + theme?: string; + + /** + * The callback function to call when a user successfully signs in (default: none). + */ + onsuccess?(user: auth2.GoogleUser): void; + + /** + * The callback function to call when sign-in fails (default: none). + */ + onfailure?(reason: { error: string }): void; + + /** + * The package name of the Android app to install over the air. See + * Android app installs from your web site. + * Optional. (default: none) + */ + app_package_name?: string; + }*/ + options); + +// End module gapi.signin2 +@JS() +abstract class Promise { + external factory Promise( + void executor(void resolve(T result), Function reject)); + external Promise then(void onFulfilled(T result), [Function onRejected]); +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart new file mode 100644 index 000000000000..bb5bb27718ee --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart @@ -0,0 +1,47 @@ +@JS() +library gapi_onload; + +import 'dart:async'; + +import 'package:js/js.dart'; +import 'package:meta/meta.dart'; + +import 'generated/gapi.dart' as gapi; +import 'utils.dart' show injectJSLibraries; + +@JS() +external set gapiOnloadCallback(Function callback); + +// This name must match the external setter above +@visibleForTesting +const String kGapiOnloadCallbackFunctionName = "gapiOnloadCallback"; +String _addOnloadToScript(String url) => url.startsWith('data:') ? url : '$url?onload=$kGapiOnloadCallbackFunctionName'; + +/// Injects the GAPI library by its [url], and other additional [libraries]. +/// +/// GAPI has an onload API where it'll call a callback when it's ready, JSONP style. +Future inject(String url, {List libraries = const []}) { + // Inject the GAPI library, and configure the onload global + final Completer gapiOnLoad = Completer(); + gapiOnloadCallback = allowInterop(() { + // Funnel the GAPI onload to a Dart future + gapiOnLoad.complete(); + }); + + // Attach the onload callback to the main url + final List allLibraries = [_addOnloadToScript(url)]..addAll(libraries); + + return Future.wait(>[injectJSLibraries(allLibraries), gapiOnLoad.future]); +} + +/// Initialize the global gapi object so 'auth2' can be used. +/// Returns a promise that resolves when 'auth2' is ready. +Future init() { + final Completer gapiLoadCompleter = Completer(); + gapi.load('auth2', allowInterop(() { + gapiLoadCompleter.complete(); + })); + + // After this resolves, we can use gapi.auth2! + return gapiLoadCompleter.future; +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart new file mode 100644 index 000000000000..d27f83f2a9f4 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart @@ -0,0 +1,42 @@ +// 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 'dart:html' as html; + +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; + +import 'generated/gapiauth2.dart' as auth2; + +/// Injects a bunch of libraries in the and returns a +/// Future that resolves when all load. +Future injectJSLibraries(List libraries, + {html.HtmlElement target /*, Duration timeout */}) { + final List> loading = >[]; + final List tags = []; + + libraries.forEach((String library) { + final html.ScriptElement script = html.ScriptElement() + ..async = true + ..defer = true + ..src = library; + // TODO add a timeout race to fail this future + loading.add(script.onLoad.first); + tags.add(script); + }); + (target ?? html.querySelector('head')).children.addAll(tags); + return Future.wait(loading); +} + +GoogleSignInUserData gapiUserToPluginUserData(auth2.GoogleUser currentUser) { + assert(currentUser != null); + final auth2.BasicProfile profile = currentUser.getBasicProfile(); + return GoogleSignInUserData( + displayName: profile?.getName(), + email: profile?.getEmail(), + id: profile?.getId(), + photoUrl: profile?.getImageUrl(), + idToken: currentUser.getAuthResponse()?.id_token, + ); +} diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml new file mode 100644 index 000000000000..2935580edbb7 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -0,0 +1,31 @@ +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. +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_web +version: 1.0.0 + +flutter: + plugin: + platforms: + web: + pluginClass: GoogleSignInPlugin + fileName: google_sign_in_web.dart + +dependencies: + google_sign_in_platform_interface: ^1.0.0 + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + meta: ^1.1.7 + js: ^0.6.1 + +dev_dependencies: + flutter_test: + sdk: flutter + google_sign_in: ^4.0.14 + +environment: + sdk: ">=2.6.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart new file mode 100644 index 000000000000..40268408caa4 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart @@ -0,0 +1,20 @@ +@TestOn('browser') + +import 'dart:html' as html; + +import 'package:flutter_test/flutter_test.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); + + test('Plugin is initialized after GAPI fully loads', () async { + expect(html.querySelector('script[src^="data:"]'), isNull, reason: 'Mock script not present before instantiating the plugin'); + final GoogleSignInPlugin plugin = GoogleSignInPlugin(); + expect(html.querySelector('script[src^="data:"]'), isNotNull, reason: 'Mock script should be injected'); + await plugin.isInitializing; + expect(plugin.isInitialized, isTrue, reason:'Plugin is initialized after awaiting the isInitializing future'); + }); +} diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart new file mode 100644 index 000000000000..172854c7d412 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart @@ -0,0 +1,6 @@ +library gapi_mocks; + +import 'package:google_sign_in_web/src/load_gapi.dart' show kGapiOnloadCallbackFunctionName; + +part 'src/gapi_load.dart'; + diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart new file mode 100644 index 000000000000..1604b79a5347 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart @@ -0,0 +1,27 @@ +part of gapi_mocks; + +const String gapiInitSuccess = ''' +(function() { + function Gapi() {}; + Gapi.prototype.load = function (script, callback) { + window.setTimeout(() => { + callback(); + }, 30); + }; + + // Initialize the gapi.auth mock. + // function GapiAuth2() {} + // GapiAuth2.prototype.init = function (initOptions) { + // // Returns the promise of a future GoogleAuth object + // return new Promise((resolve, reject) => { + // window.setTimeout(() => { + // resolve(); + // }, 30); + // }); + // }; + window.gapi = new Gapi(); + // window.gapi.auth2 = new GapiAuth2(); + + window['$kGapiOnloadCallbackFunctionName'](); + })(); +'''; diff --git a/packages/google_sign_in/google_sign_in_web/test/utils.dart b/packages/google_sign_in/google_sign_in_web/test/utils.dart new file mode 100644 index 000000000000..b57180e1ce2f --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/utils.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. 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:html' as html; +import 'dart:convert'; + +String resource(String name) => + Uri.parse(html.document.baseUri).resolve(name).toString(); + +String toBase64Url(String contents) { + // Open the file + return 'data:text/javascript;base64,' + base64.encode(utf8.encode(contents)); +} From 4f26235cf95a7fd265a57e8f023addffcd4f8189 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Fri, 15 Nov 2019 12:44:01 -0800 Subject: [PATCH 2/6] Address PR feedback --- .../google_sign_in_web/README.md | 26 ++++++++++++++----- .../lib/google_sign_in_web.dart | 18 +++++++++---- .../lib/src/generated/gapi.dart | 4 +++ .../lib/src/generated/gapiauth2.dart | 4 +++ .../google_sign_in_web/lib/src/load_gapi.dart | 16 +++++++++--- .../google_sign_in_web/lib/src/utils.dart | 2 +- .../test/gapi_load_test.dart | 22 +++++++++++++--- .../test/gapi_mocks/gapi_mocks.dart | 8 ++++-- .../test/gapi_mocks/src/gapi_load.dart | 15 +++-------- .../google_sign_in_web/test/utils.dart | 10 +++---- 10 files changed, 86 insertions(+), 39 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md index 63f85c45a6e9..d6a2a73eed77 100644 --- a/packages/google_sign_in/google_sign_in_web/README.md +++ b/packages/google_sign_in/google_sign_in_web/README.md @@ -2,7 +2,26 @@ The web implementation of [google_sign_in](https://pub.dev/google_sign_in/google_sign_in) -## Web integration +## Usage + +### Import the package +To use this plugin, follow the [plugin installation instructions](https://pub.dartlang.org/packages/google_sign_in#pub-pkg-tab-installing). + +Remember that for web plugins you need to depend both on the "native" version that provides the Dart interface that you'll use in your app), and the "web" version, that provides the implementation of the plugin for the web platform. + +This is what the above means to your `pubspec.yaml`: + +``` +... +dependencies: + ... + google_sign_in: ^4.0.14 + google_sign_in_web: ^1.0.0 + ... +... +``` + +### Web integration First, go through the instructions [here](https://developers.google.com/identity/sign-in/web/sign-in#before_you_begin) to create your Google Sign-In OAuth client ID. @@ -16,11 +35,6 @@ On your `web/index.html` file, add the following `meta` tag, somewhere in the Read the rest of the instructions if you need to add extra APIs (like Google People API). -## Usage - -### Import the package -To use this plugin, follow the [plugin installation instructions](https://pub.dartlang.org/packages/google_sign_in#pub-pkg-tab-installing). - ### Use the plugin Add the following import to your Dart code: diff --git a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart index ae6d1df9476e..7ab1e586c13d 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart @@ -1,4 +1,4 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2019 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. @@ -29,7 +29,8 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { .querySelector(_kClientIdMetaSelector) ?.getAttribute(_kClientIdAttributeName); - _isGapiInitialized = gapi.inject(gapiUrl).then((_) => gapi.init()).then((_) { + _isGapiInitialized = + gapi.inject(gapiUrl).then((_) => gapi.init()).then((_) { isInitialized = true; }); } @@ -38,8 +39,9 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { @visibleForTesting Future get isInitializing => _isGapiInitialized; + @visibleForTesting - bool isInitialized; + bool isInitialized = false; String _autoDetectedClientId; FutureOr _lastSeenUser; @@ -61,9 +63,14 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { ' tag,' ' or pass clientId when calling init()'); + assert( + !scopes.any((String scope) => scope.contains(' ')), + 'OAuth 2.0 Scopes for Google APIs can\'t contain spaces.' + 'Check https://developers.google.com/identity/protocols/googlescopes ' + 'for a list of valid OAuth 2.0 scopes.'); + await _isGapiInitialized; - // This init returns an user, so let's wait for its future final auth2.GoogleAuth auth = auth2.init(auth2.ClientConfig( hosted_domain: hostedDomain, // The js lib wants a space-separated list of values @@ -71,7 +78,8 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { client_id: appClientId, )); - // Subscribe to changes in the auth user, and cache the latest seen for signInSilently + // Subscribe to changes in the auth instance returned by init, + // and cache the _lastSeenUser as we get notified of new values. final Completer initUserCompleter = Completer(); diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart index 2e5b641bbe46..c64d93be223a 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart @@ -1,3 +1,7 @@ +// Copyright 2019 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. + @JS() library gapi; diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart index 731d4b2c7b1e..5071f93e6e30 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart @@ -1,3 +1,7 @@ +// Copyright 2019 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. + @JS() library gapiauth2; diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart index bb5bb27718ee..8273f39630f9 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart @@ -1,3 +1,7 @@ +// Copyright 2019 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. + @JS() library gapi_onload; @@ -15,10 +19,12 @@ external set gapiOnloadCallback(Function callback); // This name must match the external setter above @visibleForTesting const String kGapiOnloadCallbackFunctionName = "gapiOnloadCallback"; -String _addOnloadToScript(String url) => url.startsWith('data:') ? url : '$url?onload=$kGapiOnloadCallbackFunctionName'; +String _addOnloadToScript(String url) => url.startsWith('data:') + ? url + : '$url?onload=$kGapiOnloadCallbackFunctionName'; /// Injects the GAPI library by its [url], and other additional [libraries]. -/// +/// /// GAPI has an onload API where it'll call a callback when it's ready, JSONP style. Future inject(String url, {List libraries = const []}) { // Inject the GAPI library, and configure the onload global @@ -29,9 +35,11 @@ Future inject(String url, {List libraries = const []}) { }); // Attach the onload callback to the main url - final List allLibraries = [_addOnloadToScript(url)]..addAll(libraries); + final List allLibraries = [_addOnloadToScript(url)] + ..addAll(libraries); - return Future.wait(>[injectJSLibraries(allLibraries), gapiOnLoad.future]); + return Future.wait( + >[injectJSLibraries(allLibraries), gapiOnLoad.future]); } /// Initialize the global gapi object so 'auth2' can be used. diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart index d27f83f2a9f4..c8ca8007c1bb 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart @@ -1,4 +1,4 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2019 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. diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart index 40268408caa4..839d0a1460f5 100644 --- a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart @@ -1,3 +1,7 @@ +// Copyright 2019 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. + @TestOn('browser') import 'dart:html' as html; @@ -11,10 +15,22 @@ void main() { gapiUrl = toBase64Url(gapi_mocks.gapiInitSuccess); test('Plugin is initialized after GAPI fully loads', () async { - expect(html.querySelector('script[src^="data:"]'), isNull, reason: 'Mock script not present before instantiating the plugin'); + expect( + html.querySelector('script[src^="data:"]'), + isNull, + reason: 'Mock script not present before instantiating the plugin', + ); final GoogleSignInPlugin plugin = GoogleSignInPlugin(); - expect(html.querySelector('script[src^="data:"]'), isNotNull, reason: 'Mock script should be injected'); + expect( + html.querySelector('script[src^="data:"]'), + isNotNull, + reason: 'Mock script should be injected', + ); await plugin.isInitializing; - expect(plugin.isInitialized, isTrue, reason:'Plugin is initialized after awaiting the isInitializing future'); + expect( + plugin.isInitialized, + isTrue, + reason: 'Plugin is initialized after awaiting the isInitializing future', + ); }); } diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart index 172854c7d412..1dd4bb8eaf68 100644 --- a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart @@ -1,6 +1,10 @@ +// Copyright 2019 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. + library gapi_mocks; -import 'package:google_sign_in_web/src/load_gapi.dart' show kGapiOnloadCallbackFunctionName; +import 'package:google_sign_in_web/src/load_gapi.dart' + show kGapiOnloadCallbackFunctionName; part 'src/gapi_load.dart'; - diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart index 1604b79a5347..d296179e8ba1 100644 --- a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart @@ -1,3 +1,7 @@ +// Copyright 2019 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. + part of gapi_mocks; const String gapiInitSuccess = ''' @@ -9,18 +13,7 @@ const String gapiInitSuccess = ''' }, 30); }; - // Initialize the gapi.auth mock. - // function GapiAuth2() {} - // GapiAuth2.prototype.init = function (initOptions) { - // // Returns the promise of a future GoogleAuth object - // return new Promise((resolve, reject) => { - // window.setTimeout(() => { - // resolve(); - // }, 30); - // }); - // }; window.gapi = new Gapi(); - // window.gapi.auth2 = new GapiAuth2(); window['$kGapiOnloadCallbackFunctionName'](); })(); diff --git a/packages/google_sign_in/google_sign_in_web/test/utils.dart b/packages/google_sign_in/google_sign_in_web/test/utils.dart index b57180e1ce2f..5a6c8906682c 100644 --- a/packages/google_sign_in/google_sign_in_web/test/utils.dart +++ b/packages/google_sign_in/google_sign_in_web/test/utils.dart @@ -1,13 +1,9 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. +// Copyright 2019 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:html' as html; import 'dart:convert'; -String resource(String name) => - Uri.parse(html.document.baseUri).resolve(name).toString(); - String toBase64Url(String contents) { // Open the file return 'data:text/javascript;base64,' + base64.encode(utf8.encode(contents)); From 5251098ff1d37d76fed9e66832cfde80a194c01e Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Fri, 15 Nov 2019 12:44:21 -0800 Subject: [PATCH 3/6] Ignore generated files --- .../google_sign_in/google_sign_in_web/analysis_options.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/google_sign_in/google_sign_in_web/analysis_options.yaml diff --git a/packages/google_sign_in/google_sign_in_web/analysis_options.yaml b/packages/google_sign_in/google_sign_in_web/analysis_options.yaml new file mode 100644 index 000000000000..6ff44d022af6 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/analysis_options.yaml @@ -0,0 +1,5 @@ +# Exclude auto-generated files from analysis/linting +analyzer: + exclude: + - "lib/src/generated/**" + \ No newline at end of file From bb4fd7d6e9f3d9ba6d91c123e2f6eb2a3623efc9 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Fri, 15 Nov 2019 17:57:33 -0800 Subject: [PATCH 4/6] Add happy case tests for init+signIn+getTokens --- .../google_sign_in_web/test/auth2_test.dart | 77 +++++++++++++++++++ .../test/gapi_load_test.dart | 2 +- .../test/gapi_mocks/gapi_mocks.dart | 8 +- .../test/gapi_mocks/src/auth2_init.dart | 42 ++++++++++ .../test/gapi_mocks/src/gapi.dart | 12 +++ .../test/gapi_mocks/src/gapi_load.dart | 16 +--- .../test/gapi_mocks/src/google_user.dart | 25 ++++++ .../test/gapi_mocks/src/test_iife.dart | 15 ++++ 8 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 packages/google_sign_in/google_sign_in_web/test/auth2_test.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart diff --git a/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart new file mode 100644 index 000000000000..f5fd638ee6e8 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart @@ -0,0 +1,77 @@ +// Copyright 2019 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. + +@TestOn('browser') + +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() { + GoogleSignInTokenData expectedTokenData = + GoogleSignInTokenData(idToken: '70k3n', accessToken: 'access_70k3n'); + + GoogleSignInUserData expectedUserData = GoogleSignInUserData( + displayName: 'Foo Bar', + email: 'foo@example.com', + id: '123', + photoUrl: 'http://example.com/img.jpg', + idToken: expectedTokenData.idToken, + ); + + // The pre-configured use case for the instances of the plugin in this test + gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(expectedUserData)); + + GoogleSignInPlugin plugin; + + setUp(() async { + plugin = GoogleSignInPlugin(); + await plugin.isInitializing; + }); + + test('Init requires clientId', () async { + expect(plugin.init(hostedDomain: ''), throwsAssertionError); + }); + + test('Init doesn\'t accept spaces in scopes', () async { + expect( + plugin.init( + hostedDomain: '', + clientId: '', + scopes: ['scope with spaces'], + ), + throwsAssertionError); + }); + + group('Successful .init, then', () { + setUp(() async { + plugin.init( + hostedDomain: 'foo', + scopes: ['some', 'scope'], + clientId: '1234', + ); + }); + + test('signInSilently', () async { + GoogleSignInUserData actualUser = await plugin.signInSilently(); + + expect(actualUser, expectedUserData); + }); + + test('signIn', () async { + GoogleSignInUserData actualUser = await plugin.signIn(); + + expect(actualUser, expectedUserData); + }); + + test('getTokens', () async { + GoogleSignInTokenData actualToken = + await plugin.getTokens(email: expectedUserData.email); + + expect(actualToken, expectedTokenData); + }); + }); +} diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart index 839d0a1460f5..25044bf341e1 100644 --- a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart @@ -12,7 +12,7 @@ import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks; import 'utils.dart'; void main() { - gapiUrl = toBase64Url(gapi_mocks.gapiInitSuccess); + gapiUrl = toBase64Url(gapi_mocks.gapiInitSuccess()); test('Plugin is initialized after GAPI fully loads', () async { expect( diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart index 1dd4bb8eaf68..d36cd5edf63a 100644 --- a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart @@ -4,7 +4,11 @@ library gapi_mocks; -import 'package:google_sign_in_web/src/load_gapi.dart' - show kGapiOnloadCallbackFunctionName; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; + +import 'src/gapi.dart'; +import 'src/google_user.dart'; +import 'src/test_iife.dart'; part 'src/gapi_load.dart'; +part 'src/auth2_init.dart'; diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart new file mode 100644 index 000000000000..5484dc5b61bc --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart @@ -0,0 +1,42 @@ +// Copyright 2019 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. + +part of gapi_mocks; + +// JS mock of a gapi.auth2, with a successfully identified user +String auth2InitSuccess(GoogleSignInUserData userData) => testIife(''' +${gapi()} + +var mockUser = ${googleUser(userData)}; + +function GapiAuth2() {} +GapiAuth2.prototype.init = function (initOptions) { + return { + currentUser: { + listen: (cb) => { + window.setTimeout(() => { + cb(mockUser); + }, 30); + } + } + } +}; + +GapiAuth2.prototype.getAuthInstance = function () { + return { + signIn: () => { + return new Promise((resolve, reject) => { + window.setTimeout(() => { + resolve(mockUser); + }, 30); + }); + }, + currentUser: { + get: () => mockUser, + }, + } +}; + +window.gapi.auth2 = new GapiAuth2(); +'''); diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart new file mode 100644 index 000000000000..42d9a8be262c --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart @@ -0,0 +1,12 @@ +// Copyright 2019 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. + +// The JS mock of the global gapi object +String gapi() => ''' +function Gapi() {}; +Gapi.prototype.load = function (script, cb) { + window.setTimeout(cb, 30); +}; +window.gapi = new Gapi(); +'''; diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart index d296179e8ba1..f390500f6f4e 100644 --- a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart @@ -4,17 +4,5 @@ part of gapi_mocks; -const String gapiInitSuccess = ''' -(function() { - function Gapi() {}; - Gapi.prototype.load = function (script, callback) { - window.setTimeout(() => { - callback(); - }, 30); - }; - - window.gapi = new Gapi(); - - window['$kGapiOnloadCallbackFunctionName'](); - })(); -'''; +// JS mock of a raw GAPI object. +String gapiInitSuccess() => testIife(gapi()); diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart new file mode 100644 index 000000000000..9f2b7b9bf6fa --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart @@ -0,0 +1,25 @@ +// Copyright 2019 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 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; + +// Creates the JS representation of some user data +String googleUser(GoogleSignInUserData data) => ''' +{ + getBasicProfile: () => { + return { + getName: () => '${data.displayName}', + getEmail: () => '${data.email}', + getId: () => '${data.id}', + getImageUrl: () => '${data.photoUrl}', + }; + }, + getAuthResponse: () => { + return { + id_token: '${data.idToken}', + access_token: 'access_${data.idToken}', + } + }, +} +'''; diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart new file mode 100644 index 000000000000..43a7a044fc1b --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart @@ -0,0 +1,15 @@ +// Copyright 2019 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 'package:google_sign_in_web/src/load_gapi.dart' + show kGapiOnloadCallbackFunctionName; + +// Wraps some JS mock code in an IIFE that ends by calling the onLoad dart callback. +String testIife(String mock) => ''' +(function() { + $mock; + window['$kGapiOnloadCallbackFunctionName'](); +})(); +''' + .replaceAll(RegExp(r'\s{2,}'), ''); From e1ebf7d681908539ce5eb6449ca13d66a8b91467 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Mon, 18 Nov 2019 11:49:12 -0800 Subject: [PATCH 5/6] Call the initial version 0.8.0 --- packages/google_sign_in/google_sign_in_web/CHANGELOG.md | 2 +- packages/google_sign_in/google_sign_in_web/README.md | 2 +- packages/google_sign_in/google_sign_in_web/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md index 989b09e4d9ac..f24bcf58874a 100644 --- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -1,3 +1,3 @@ -## 1.0.0 +## 0.8.0 * Flutter for web initial release \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md index d6a2a73eed77..182b21018aa4 100644 --- a/packages/google_sign_in/google_sign_in_web/README.md +++ b/packages/google_sign_in/google_sign_in_web/README.md @@ -16,7 +16,7 @@ This is what the above means to your `pubspec.yaml`: dependencies: ... google_sign_in: ^4.0.14 - google_sign_in_web: ^1.0.0 + google_sign_in_web: ^0.8.0 ... ... ``` diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml index 2935580edbb7..0c33fe402e83 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android, iOS and Web. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_web -version: 1.0.0 +version: 0.8.0 flutter: plugin: From c3b12daeb19a2326afe354ce98f9fbd397522cea Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Mon, 18 Nov 2019 16:45:54 -0800 Subject: [PATCH 6/6] Address PR feedback --- .../lib/google_sign_in_web.dart | 26 +++++++------------ .../google_sign_in_web/test/auth2_test.dart | 2 +- .../test/gapi_load_test.dart | 8 +++--- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart index 7ab1e586c13d..c7889c9107e4 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart @@ -29,19 +29,13 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { .querySelector(_kClientIdMetaSelector) ?.getAttribute(_kClientIdAttributeName); - _isGapiInitialized = - gapi.inject(gapiUrl).then((_) => gapi.init()).then((_) { - isInitialized = true; - }); + _isGapiInitialized = gapi.inject(gapiUrl).then((_) => gapi.init()); } Future _isGapiInitialized; @visibleForTesting - Future get isInitializing => _isGapiInitialized; - - @visibleForTesting - bool isInitialized = false; + Future get initialized => _isGapiInitialized; String _autoDetectedClientId; FutureOr _lastSeenUser; @@ -69,7 +63,7 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { 'Check https://developers.google.com/identity/protocols/googlescopes ' 'for a list of valid OAuth 2.0 scopes.'); - await _isGapiInitialized; + await initialized; final auth2.GoogleAuth auth = auth2.init(auth2.ClientConfig( hosted_domain: hostedDomain, @@ -97,14 +91,14 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { @override Future signInSilently() async { - await _isGapiInitialized; + await initialized; return gapiUserToPluginUserData(await _lastSeenUser); } @override Future signIn() async { - await _isGapiInitialized; + await initialized; return gapiUserToPluginUserData(await auth2.getAuthInstance().signIn()); } @@ -112,7 +106,7 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { @override Future getTokens( {@required String email, bool shouldRecoverAuth}) async { - await _isGapiInitialized; + await initialized; final auth2.GoogleUser currentUser = auth2.getAuthInstance()?.currentUser?.get(); @@ -124,14 +118,14 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { @override Future signOut() async { - await _isGapiInitialized; + await initialized; return auth2.getAuthInstance().signOut(); } @override Future disconnect() async { - await _isGapiInitialized; + await initialized; final auth2.GoogleUser currentUser = auth2.getAuthInstance()?.currentUser?.get(); @@ -140,7 +134,7 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { @override Future isSignedIn() async { - await _isGapiInitialized; + await initialized; final auth2.GoogleUser currentUser = auth2.getAuthInstance()?.currentUser?.get(); @@ -149,7 +143,7 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { @override Future clearAuthCache({String token}) async { - await _isGapiInitialized; + await initialized; _lastSeenUser = null; return auth2.getAuthInstance().disconnect(); diff --git a/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart index f5fd638ee6e8..b6eb60754b49 100644 --- a/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart +++ b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart @@ -29,7 +29,7 @@ void main() { setUp(() async { plugin = GoogleSignInPlugin(); - await plugin.isInitializing; + await plugin.initialized; }); test('Init requires clientId', () async { diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart index 25044bf341e1..815dcdcfc645 100644 --- a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart @@ -26,11 +26,11 @@ void main() { isNotNull, reason: 'Mock script should be injected', ); - await plugin.isInitializing; + await plugin.initialized; expect( - plugin.isInitialized, - isTrue, - reason: 'Plugin is initialized after awaiting the isInitializing future', + plugin.initialized, + completes, + reason: 'The plugin should complete the future once initialized.', ); }); }