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..f24bcf58874a
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 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/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..182b21018aa4
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/README.md
@@ -0,0 +1,80 @@
+# google_sign_in_web
+
+The web implementation of [google_sign_in](https://pub.dev/google_sign_in/google_sign_in)
+
+## 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: ^0.8.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.
+
+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).
+
+
+### 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/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
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..c7889c9107e4
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
@@ -0,0 +1,151 @@
+// 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: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());
+ }
+
+ Future _isGapiInitialized;
+
+ @visibleForTesting
+ Future get initialized => _isGapiInitialized;
+
+ 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()');
+
+ 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 initialized;
+
+ 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 instance returned by init,
+ // and cache the _lastSeenUser as we get notified of new values.
+ 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 initialized;
+
+ return gapiUserToPluginUserData(await _lastSeenUser);
+ }
+
+ @override
+ Future signIn() async {
+ await initialized;
+
+ return gapiUserToPluginUserData(await auth2.getAuthInstance().signIn());
+ }
+
+ @override
+ Future getTokens(
+ {@required String email, bool shouldRecoverAuth}) async {
+ await initialized;
+
+ 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 initialized;
+
+ return auth2.getAuthInstance().signOut();
+ }
+
+ @override
+ Future disconnect() async {
+ await initialized;
+
+ final auth2.GoogleUser currentUser =
+ auth2.getAuthInstance()?.currentUser?.get();
+ return currentUser.disconnect();
+ }
+
+ @override
+ Future isSignedIn() async {
+ await initialized;
+
+ final auth2.GoogleUser currentUser =
+ auth2.getAuthInstance()?.currentUser?.get();
+ return currentUser.isSignedIn();
+ }
+
+ @override
+ Future clearAuthCache({String token}) async {
+ await initialized;
+
+ _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..c64d93be223a
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart
@@ -0,0 +1,459 @@
+// 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;
+
+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..5071f93e6e30
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart
@@ -0,0 +1,464 @@
+// 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;
+
+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..8273f39630f9
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart
@@ -0,0 +1,55 @@
+// 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;
+
+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..c8ca8007c1bb
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/lib/src/utils.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.
+
+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..0c33fe402e83
--- /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: 0.8.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/auth2_test.dart b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart
new file mode 100644
index 000000000000..b6eb60754b49
--- /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.initialized;
+ });
+
+ 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
new file mode 100644
index 000000000000..815dcdcfc645
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart
@@ -0,0 +1,36 @@
+// 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;
+
+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.initialized;
+ expect(
+ plugin.initialized,
+ completes,
+ reason: 'The plugin should complete the future once initialized.',
+ );
+ });
+}
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..d36cd5edf63a
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart
@@ -0,0 +1,14 @@
+// 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_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
new file mode 100644
index 000000000000..f390500f6f4e
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart
@@ -0,0 +1,8 @@
+// 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 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,}'), '');
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..5a6c8906682c
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/utils.dart
@@ -0,0 +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.
+
+import 'dart:convert';
+
+String toBase64Url(String contents) {
+ // Open the file
+ return 'data:text/javascript;base64,' + base64.encode(utf8.encode(contents));
+}