[gis_web] Adds id.renderButton JS-interop. (#3011)

* [gis_web] Adds id.renderButton API.

* Modernizes JS-interop so it's more compliant with dart2wasm.
* Updates examples, tests and docs.
* Bumps major version.

* Add the GsiButtonDataExtension class.

* Make oauth2 library more dart2wasm friendly.

* Reimplement hasGrantedA[ny|ll]Scopes in Dart.

* Fix oauth example.

* Added troubleshooting section to README.

* Add happy case tests for the oauth flow.

* Fix typo in config constructors.

* dart format

* Add some error handling to the library

* Add previously_granted_scopes field to overridable token config.

Make scopes a List of Strings in the hasGranted[Any|All]Scopes method.
diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md
index 0ec9358..d84b822 100644
--- a/packages/google_identity_services_web/CHANGELOG.md
+++ b/packages/google_identity_services_web/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.2.0
+
+* Adds `renderButton` API to `id.dart`.
+* **Breaking Change:** Makes JS-interop API more `dart2wasm`-friendly.
+  * Removes external getters for function types
+  * Introduces an external getter for the whole libraries instead.
+  * Updates `README.md` with the new way of `import`ing the desired libraries.
+
 ## 0.1.1
 
 * Add optional `scope` to `OverridableTokenClientConfig` object.
diff --git a/packages/google_identity_services_web/README.md b/packages/google_identity_services_web/README.md
index 71bbb86..8fc5c34 100644
--- a/packages/google_identity_services_web/README.md
+++ b/packages/google_identity_services_web/README.md
@@ -65,9 +65,29 @@
 
 Once the SDK has been loaded, it can be used by importing the correct library:
 
-* `import 'package:google_identity_services/id.dart' as id;` for Authentication
-* `import 'package:google_identity_services/oauth2.dart' as oauth2;` for
-  Authorization.
+* `import 'package:google_identity_services/id.dart';` for Authentication.
+  * This will expose an `id` JSObject that binds to `google.accounts.id`.
+* `import 'package:google_identity_services/oauth2.dart';` for Authorization.
+  * This will expose an `oauth2` JSObject that binds to `google.accounts.oauth2`.
+
+### Troubleshooting
+
+Watch the browser's development tools JS console while using this package.
+Information about errors during initialization and use of the library will be
+displayed there.
+
+Some common issues identified so far:
+
+#### The given origin is not allowed for the given client ID
+
+> When you perform local tests or development, **you must add both**
+> `http://localhost` and `http://localhost:<port_number>` to the
+> **Authorized JavaScript origins** box.
+> The [Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)
+> response header must also be set to `no-referrer-when-downgrade` when using
+> http and localhost.
+
+* Read more: [Sign In with Google for Web - Setup - Get your Google API client ID](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#get_your_google_api_client_id).
 
 ## Browser compatibility
 
diff --git a/packages/google_identity_services_web/example/integration_test/js_interop_gis_id_test.dart b/packages/google_identity_services_web/example/integration_test/js_interop_gis_id_test.dart
deleted file mode 100644
index b1c1b02..0000000
--- a/packages/google_identity_services_web/example/integration_test/js_interop_gis_id_test.dart
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:google_identity_services_web/id.dart' as id;
-import 'package:google_identity_services_web/src/js_interop/dom.dart';
-
-import 'package:integration_test/integration_test.dart';
-import 'package:js/js.dart';
-import 'package:js/js_util.dart';
-
-@JS('window')
-external Object get domWindow;
-
-void main() async {
-  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-
-  setUpAll(() async {
-    // Load web/mock-gis.js in the page
-    await installGisMock();
-  });
-
-  group('prompt', () {
-    testWidgets('supports a moment notification callback', (_) async {
-      id.initialize(id.IdConfiguration(client_id: 'testing_1-2-3'));
-
-      final StreamController<id.PromptMomentNotification> controller =
-          StreamController<id.PromptMomentNotification>();
-
-      id.prompt(allowInterop(controller.add));
-
-      final id.PromptMomentNotification moment = await controller.stream.first;
-
-      // These defaults are set in mock-gis.js
-      expect(moment.getMomentType(), id.MomentType.skipped);
-      expect(moment.getSkippedReason(), id.MomentSkippedReason.user_cancel);
-    });
-
-    testWidgets('calls config callback with credential response', (_) async {
-      const String expected = 'should_be_a_proper_jwt_token';
-      setMockCredentialResponse(expected);
-
-      final StreamController<id.CredentialResponse> controller =
-          StreamController<id.CredentialResponse>();
-
-      id.initialize(id.IdConfiguration(
-        client_id: 'testing_1-2-3',
-        callback: allowInterop(controller.add),
-      ));
-
-      id.prompt();
-
-      final id.CredentialResponse response = await controller.stream.first;
-
-      expect(response.credential, expected);
-    });
-  });
-}
-
-/// Installs mock-gis.js in the page.
-/// Returns a future that completes when the 'load' event of the script fires.
-Future<void> installGisMock() {
-  final Completer<void> completer = Completer<void>();
-  final DomHtmlScriptElement script =
-      document.createElement('script') as DomHtmlScriptElement;
-  script.src = 'mock-gis.js';
-  setProperty(script, 'type', 'module');
-  callMethod(script, 'addEventListener', <Object>[
-    'load',
-    allowInterop((_) {
-      completer.complete();
-    })
-  ]);
-  document.head.appendChild(script);
-  return completer.future;
-}
-
-void setMockCredentialResponse([String value = 'default_value']) {
-  callMethod(
-    _getGoogleAccountsId(),
-    'setMockCredentialResponse',
-    <Object>[value, 'auto'],
-  );
-}
-
-Object _getGoogleAccountsId() {
-  return _getDeepProperty<Object>(domWindow, 'google.accounts.id');
-}
-
-// Attempts to retrieve a deeply nested property from a jsObject (or die tryin')
-T _getDeepProperty<T>(Object jsObject, String deepProperty) {
-  final List<String> properties = deepProperty.split('.');
-  return properties.fold(
-    jsObject,
-    (Object jsObj, String prop) => getProperty<Object>(jsObj, prop),
-  ) as T;
-}
diff --git a/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart b/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart
new file mode 100644
index 0000000..17ac748
--- /dev/null
+++ b/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart
@@ -0,0 +1,57 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_identity_services_web/id.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:js/js.dart';
+
+import 'utils.dart' as utils;
+
+void main() async {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  setUpAll(() async {
+    // Load web/mock-gis.js in the page
+    await utils.installGisMock();
+  });
+
+  group('prompt', () {
+    testWidgets('supports a moment notification callback', (_) async {
+      id.initialize(IdConfiguration(client_id: 'testing_1-2-3'));
+
+      final StreamController<PromptMomentNotification> controller =
+          StreamController<PromptMomentNotification>();
+
+      id.prompt(allowInterop(controller.add));
+
+      final PromptMomentNotification moment = await controller.stream.first;
+
+      // These defaults are set in mock-gis.js
+      expect(moment.getMomentType(), MomentType.skipped);
+      expect(moment.getSkippedReason(), MomentSkippedReason.user_cancel);
+    });
+
+    testWidgets('calls config callback with credential response', (_) async {
+      const String expected = 'should_be_a_proper_jwt_token';
+      utils.setMockCredentialResponse(expected);
+
+      final StreamController<CredentialResponse> controller =
+          StreamController<CredentialResponse>();
+
+      id.initialize(IdConfiguration(
+        client_id: 'testing_1-2-3',
+        callback: allowInterop(controller.add),
+      ));
+
+      id.prompt();
+
+      final CredentialResponse response = await controller.stream.first;
+
+      expect(response.credential, expected);
+    });
+  });
+}
diff --git a/packages/google_identity_services_web/example/integration_test/js_interop_oauth_test.dart b/packages/google_identity_services_web/example/integration_test/js_interop_oauth_test.dart
new file mode 100644
index 0000000..50a0667
--- /dev/null
+++ b/packages/google_identity_services_web/example/integration_test/js_interop_oauth_test.dart
@@ -0,0 +1,124 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_identity_services_web/oauth2.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:js/js.dart';
+
+import 'utils.dart' as utils;
+
+void main() async {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  setUpAll(() async {
+    // Load web/mock-gis.js in the page
+    await utils.installGisMock();
+  });
+
+  group('initTokenClient', () {
+    testWidgets('returns a tokenClient', (_) async {
+      final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
+        client_id: 'for-tests',
+        callback: null,
+        scope: 'some_scope for_tests not_real',
+      ));
+
+      expect(client, isNotNull);
+    });
+  });
+
+  group('requestAccessToken', () {
+    testWidgets('passes through configuration', (_) async {
+      final StreamController<TokenResponse> controller =
+          StreamController<TokenResponse>();
+
+      final List<String> scopes = <String>['some_scope', 'another', 'more'];
+
+      final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
+        client_id: 'for-tests',
+        callback: allowInterop(controller.add),
+        scope: scopes.join(' '),
+      ));
+
+      utils.setMockTokenResponse(client, 'some-non-null-auth-token-value');
+
+      client.requestAccessToken();
+
+      final TokenResponse response = await controller.stream.first;
+
+      expect(response, isNotNull);
+      expect(response.error, isNull);
+      expect(response.scope, scopes.join(' '));
+    });
+
+    testWidgets('configuration can be overridden', (_) async {
+      final StreamController<TokenResponse> controller =
+          StreamController<TokenResponse>();
+
+      final List<String> scopes = <String>['some_scope', 'another', 'more'];
+
+      final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
+        client_id: 'for-tests',
+        callback: allowInterop(controller.add),
+        scope: 'blank',
+      ));
+
+      utils.setMockTokenResponse(client, 'some-non-null-auth-token-value');
+
+      client.requestAccessToken(OverridableTokenClientConfig(
+        scope: scopes.join(' '),
+      ));
+
+      final TokenResponse response = await controller.stream.first;
+
+      expect(response, isNotNull);
+      expect(response.error, isNull);
+      expect(response.scope, scopes.join(' '));
+    });
+  });
+
+  group('hasGranted...Scopes', () {
+    // mock-gis.js returns false for scopes that start with "not-granted-".
+    const String notGranted = 'not-granted-scope';
+
+    testWidgets('all scopes granted', (_) async {
+      final List<String> scopes = <String>['some_scope', 'another', 'more'];
+
+      final TokenResponse response = await utils.fakeAuthZWithScopes(scopes);
+
+      final bool all = oauth2.hasGrantedAllScopes(response, scopes);
+      final bool any = oauth2.hasGrantedAnyScopes(response, scopes);
+
+      expect(all, isTrue);
+      expect(any, isTrue);
+    });
+
+    testWidgets('some scopes granted', (_) async {
+      final List<String> scopes = <String>['some_scope', notGranted, 'more'];
+
+      final TokenResponse response = await utils.fakeAuthZWithScopes(scopes);
+
+      final bool all = oauth2.hasGrantedAllScopes(response, scopes);
+      final bool any = oauth2.hasGrantedAnyScopes(response, scopes);
+
+      expect(all, isFalse, reason: 'Scope: $notGranted should not be granted!');
+      expect(any, isTrue);
+    });
+
+    testWidgets('no scopes granted', (_) async {
+      final List<String> scopes = <String>[notGranted, '$notGranted-2'];
+
+      final TokenResponse response = await utils.fakeAuthZWithScopes(scopes);
+
+      final bool all = oauth2.hasGrantedAllScopes(response, scopes);
+      final bool any = oauth2.hasGrantedAnyScopes(response, scopes);
+
+      expect(all, isFalse);
+      expect(any, isFalse, reason: 'No scopes were granted.');
+    });
+  });
+}
diff --git a/packages/google_identity_services_web/example/integration_test/utils.dart b/packages/google_identity_services_web/example/integration_test/utils.dart
new file mode 100644
index 0000000..889a0ee
--- /dev/null
+++ b/packages/google_identity_services_web/example/integration_test/utils.dart
@@ -0,0 +1,76 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:google_identity_services_web/oauth2.dart';
+import 'package:google_identity_services_web/src/js_interop/dom.dart';
+import 'package:js/js.dart';
+import 'package:js/js_util.dart';
+
+@JS('window')
+external Object get domWindow;
+
+/// Installs mock-gis.js in the page.
+/// Returns a future that completes when the 'load' event of the script fires.
+Future<void> installGisMock() {
+  final Completer<void> completer = Completer<void>();
+  final DomHtmlScriptElement script =
+      document.createElement('script') as DomHtmlScriptElement;
+  script.src = 'mock-gis.js';
+  setProperty(script, 'type', 'module');
+  callMethod(script, 'addEventListener', <Object>[
+    'load',
+    allowInterop((_) {
+      completer.complete();
+    })
+  ]);
+  document.head.appendChild(script);
+  return completer.future;
+}
+
+/// Fakes authorization with the given scopes.
+Future<TokenResponse> fakeAuthZWithScopes(List<String> scopes) {
+  final StreamController<TokenResponse> controller =
+      StreamController<TokenResponse>();
+  final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
+    client_id: 'for-tests',
+    callback: allowInterop(controller.add),
+    scope: scopes.join(' '),
+  ));
+  setMockTokenResponse(client, 'some-non-null-auth-token-value');
+  client.requestAccessToken();
+  return controller.stream.first;
+}
+
+/// Sets a mock TokenResponse value in a [client].
+void setMockTokenResponse(TokenClient client, [String? authToken]) {
+  callMethod(
+    client,
+    'setMockTokenResponse',
+    <Object?>[authToken],
+  );
+}
+
+/// Sets a mock credential response in `google.accounts.id`.
+void setMockCredentialResponse([String value = 'default_value']) {
+  callMethod(
+    _getGoogleAccountsId(),
+    'setMockCredentialResponse',
+    <Object>[value, 'auto'],
+  );
+}
+
+Object _getGoogleAccountsId() {
+  return _getDeepProperty<Object>(domWindow, 'google.accounts.id');
+}
+
+// Attempts to retrieve a deeply nested property from a jsObject (or die tryin')
+T _getDeepProperty<T>(Object jsObject, String deepProperty) {
+  final List<String> properties = deepProperty.split('.');
+  return properties.fold(
+    jsObject,
+    (Object jsObj, String prop) => getProperty<Object>(jsObj, prop),
+  ) as T;
+}
diff --git a/packages/google_identity_services_web/example/lib/main.dart b/packages/google_identity_services_web/example/lib/main.dart
index 949eb51..3dc8c0e 100644
--- a/packages/google_identity_services_web/example/lib/main.dart
+++ b/packages/google_identity_services_web/example/lib/main.dart
@@ -4,7 +4,7 @@
 
 // ignore_for_file: avoid_print
 
-import 'package:google_identity_services_web/id.dart' as id;
+import 'package:google_identity_services_web/id.dart';
 // #docregion use-loader
 import 'package:google_identity_services_web/loader.dart' as gis;
 // #enddocregion use-loader
@@ -18,9 +18,8 @@
 // #enddocregion use-loader
   id.setLogLevel('debug');
 
-  final id.IdConfiguration config = id.IdConfiguration(
+  final IdConfiguration config = IdConfiguration(
     client_id: 'your-client_id.apps.googleusercontent.com',
-    ux_mode: id.UxMode.popup,
     callback: allowInterop(onCredentialResponse),
   );
 
@@ -32,8 +31,8 @@
 
 /// Handles the ID token returned from the One Tap prompt.
 /// See: https://developers.google.com/identity/gsi/web/reference/js-reference#callback
-void onCredentialResponse(id.CredentialResponse o) {
-  final Map<String, dynamic>? payload = jwt.JwtDecoder.tryDecode(o.credential);
+void onCredentialResponse(CredentialResponse o) {
+  final Map<String, dynamic>? payload = jwt.JwtDecoder.tryDecode(o.credential!);
   if (payload != null) {
     print('Hello, ${payload["name"]}');
     print(o.select_by);
@@ -45,8 +44,8 @@
 
 /// Handles Prompt UI status notifications.
 /// See: https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt
-void onPromptMoment(id.PromptMomentNotification o) {
-  final id.MomentType type = o.getMomentType();
+void onPromptMoment(PromptMomentNotification o) {
+  final MomentType type = o.getMomentType();
   print(type.runtimeType);
   print(type);
   print(type.index);
diff --git a/packages/google_identity_services_web/example/lib/main_oauth.dart b/packages/google_identity_services_web/example/lib/main_oauth.dart
index 041715f..c54ab4c 100644
--- a/packages/google_identity_services_web/example/lib/main_oauth.dart
+++ b/packages/google_identity_services_web/example/lib/main_oauth.dart
@@ -4,16 +4,31 @@
 
 // ignore_for_file: avoid_print
 
-import 'package:google_identity_services_web/id.dart' as id show setLogLevel;
+import 'dart:convert';
+
+import 'package:google_identity_services_web/id.dart';
 import 'package:google_identity_services_web/loader.dart' as gis;
-import 'package:google_identity_services_web/oauth2.dart' as oauth2;
+import 'package:google_identity_services_web/oauth2.dart';
 import 'package:http/http.dart' as http;
 import 'package:js/js.dart' show allowInterop;
+import 'package:js/js_util.dart' show getProperty;
 
-/// The scopes to be requested
+/// People API to return my profile info...
+const String MY_PROFILE =
+    'https://content-people.googleapis.com/v1/people/me?personFields=photos%2Cnames%2CemailAddresses';
+
+/// People API to return all my connections.
+const String MY_CONNECTIONS =
+    'https://people.googleapis.com/v1/people/me/connections?requestMask.includeField=person.names';
+
+/// Basic scopes for self-id
 const List<String> scopes = <String>[
-  'email',
-  'profile',
+  'https://www.googleapis.com/auth/userinfo.profile',
+  'https://www.googleapis.com/auth/userinfo.email',
+];
+
+/// Scopes for the people API (read contacts)
+const List<String> myConnectionsScopes = <String>[
   'https://www.googleapis.com/auth/contacts.readonly',
 ];
 
@@ -22,58 +37,77 @@
 
   id.setLogLevel('debug');
 
-  final oauth2.TokenClientConfig config = oauth2.TokenClientConfig(
+  final TokenClientConfig config = TokenClientConfig(
     client_id: 'your-client_id.apps.googleusercontent.com',
     scope: scopes.join(' '),
     callback: allowInterop(onTokenResponse),
+    error_callback: allowInterop(onError),
   );
 
-  final oauth2.OverridableTokenClientConfig overridableCfg =
-      oauth2.OverridableTokenClientConfig(
-    prompt: '',
+  final OverridableTokenClientConfig overridableCfg =
+      OverridableTokenClientConfig(
+    scope: (scopes + myConnectionsScopes).join(' '),
   );
 
-  final oauth2.TokenClient client = oauth2.initTokenClient(config);
+  final TokenClient client = oauth2.initTokenClient(config);
 
   // Disable the Popup Blocker for this to work, or move this to a Button press.
   client.requestAccessToken(overridableCfg);
 }
 
+/// Triggers when there's an error with the OAuth2 popup.
+///
+/// We cannot use the proper type for `error` here yet, because of:
+/// https://github.com/dart-lang/sdk/issues/50899
+Future<void> onError(Object? error) async {
+  print('Error! ${getProperty(error!, "type")}');
+}
+
 /// Handles the returned (auth) token response.
 /// See: https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
-Future<void> onTokenResponse(oauth2.TokenResponse response) async {
-  if (response.error != null) {
+Future<void> onTokenResponse(TokenResponse token) async {
+  if (token.error != null) {
     print('Authorization error!');
-    print(response.error);
-    print(response.error_description);
-    print(response.error_uri);
-    return;
-  }
-
-  // Has granted all the scopes?
-  if (!oauth2.hasGrantedAllScopes(response, scopes[2])) {
-    print('The user has NOT granted the required scope!');
+    print(token.error);
+    print(token.error_description);
+    print(token.error_uri);
     return;
   }
 
   // Attempt to do a request to the `people` API
-  final http.Response apiResponse = await http.get(
-    Uri.parse('https://people.googleapis.com/v1/people/me/connections'
-        '?requestMask.includeField=person.names'),
-    headers: <String, String>{
-      'Authorization': '${response.token_type} ${response.access_token}',
-    },
-  );
-  if (apiResponse.statusCode == 200) {
-    print('People API ${apiResponse.statusCode} OK!');
-  } else {
-    print(
-        'People API ${apiResponse.statusCode} Oops! Something wrong happened!');
+  final Object? profile = await get(token, MY_PROFILE);
+  print(profile);
+
+  // Has granted all the scopes?
+  if (!oauth2.hasGrantedAllScopes(token, myConnectionsScopes)) {
+    print('The user has NOT granted all the required scopes!');
+    print('The next get will probably throw an exception!');
   }
-  print(apiResponse.body);
+
+  final Object? contacts = await get(token, MY_CONNECTIONS);
+  print(contacts);
 
   print('Revoking token...');
-  oauth2.revokeToken(response.access_token, allowInterop((String status) {
-    print(status);
+  oauth2.revoke(token.access_token,
+      allowInterop((TokenRevocationResponse response) {
+    print(response.successful);
+    print(response.error);
+    print(response.error_description);
   }));
 }
+
+/// Gets from [url] with an authorization header defined by [token].
+///
+/// Attempts to [jsonDecode] the result.
+Future<Object?> get(TokenResponse token, String url) async {
+  final Uri uri = Uri.parse(url);
+  final http.Response response = await http.get(uri, headers: <String, String>{
+    'Authorization': '${token.token_type} ${token.access_token}',
+  });
+
+  if (response.statusCode != 200) {
+    throw http.ClientException(response.body, uri);
+  }
+
+  return jsonDecode(response.body) as Object?;
+}
diff --git a/packages/google_identity_services_web/example/web/mock-gis.js b/packages/google_identity_services_web/example/web/mock-gis.js
index 56701e2..2e13460 100644
--- a/packages/google_identity_services_web/example/web/mock-gis.js
+++ b/packages/google_identity_services_web/example/web/mock-gis.js
@@ -94,17 +94,24 @@
     this.config = config;
   }
   requestAccessToken(overridableConfig) {
-    this.overridableConfig = overridableConfig;
-    let callback = this.overridableConfig.callback || this.config.callback;
+    this.config = {...this.config, ...overridableConfig};
+    let callback = this.config.callback;
     if (!callback) {
       return;
     }
     callAsync(() => {
-      callback(this.tokenResponse);
+      callback({
+        ...this.tokenResponse,
+        scope: this.config.scope,
+      });
     });
   }
-  setMockTokenResponse(tokenResponse) {
-    this.tokenResponse = tokenResponse;
+  setMockTokenResponse(access_token) {
+    this.tokenResponse = {
+      access_token: access_token,
+      token_type: access_token != null ? 'Bearer' : null,
+      error: access_token == null ? 'unauthorized' : null,
+    };
   }
 }
 
@@ -116,22 +123,24 @@
     return new TokenClient(config);
   }
   hasGrantedAllScopes(tokenResponse, scope, ...scopes) {
-    return tokenResponse != null;
+    return tokenResponse != null && !scope.startsWith('not-granted-');
   }
   hasGrantedAnyScopes(tokenResponse, scope, ...scopes) {
-    return tokenResponse != null;
+    return false; // Unused in the lib
   }
   revoke(accessToken, done) {
     if (!done) {
       return;
     }
     callAsync(() => {
-      done();
+      done({
+        success: true,
+      });
     })
   }
 }
 
-function mockGis() {
+(function() {
   let goog = {
     accounts: {
       id: new Id(),
@@ -139,6 +148,4 @@
     }
   };
   globalThis['google'] = goog;
-}
-
-mockGis();
+}());
diff --git a/packages/google_identity_services_web/lib/src/js_interop/dom.dart b/packages/google_identity_services_web/lib/src/js_interop/dom.dart
index 2cc121d..ff5dfc9 100644
--- a/packages/google_identity_services_web/lib/src/js_interop/dom.dart
+++ b/packages/google_identity_services_web/lib/src/js_interop/dom.dart
@@ -88,6 +88,29 @@
   external set defer(bool defer);
 }
 
+/// Error object
+@JS('Error')
+@staticInterop
+abstract class DomError {}
+
+/// Methods on the error object
+extension DomErrorExtension on DomError {
+  /// Error message.
+  external String? get message;
+
+  /// Stack trace.
+  external String? get stack;
+
+  /// Error name. This is determined by the constructor function.
+  external String get name;
+
+  /// Error cause indicating the reason why the current error is thrown.
+  ///
+  /// This is usually another caught error, or the value provided as the `cause`
+  /// property of the Error constructor's second argument.
+  external Object? get cause;
+}
+
 /*
 // Trusted Types API (TrustedTypePolicy, TrustedScript, TrustedScriptURL)
 // https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypesAPI
diff --git a/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart
index 579662d..a790fae 100644
--- a/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart
+++ b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart
@@ -9,54 +9,142 @@
 // * non_constant_identifier_names required to be able to use the same parameter
 //   names as the underlying library.
 
-@JS('google.accounts.id')
-library id;
+@JS()
+library google_accounts_id;
 
 import 'package:js/js.dart';
 
+import 'dom.dart';
 import 'shared.dart';
 
-/// An undocumented method. Try with 'debug'.
+/// Binding to the `google.accounts.id` JS global.
+///
+/// See: https://developers.google.com/identity/gsi/web/reference/js-reference
+@JS('google.accounts.id')
+external GoogleAccountsId get id;
+
+/// The Dart definition of the `google.accounts.id` global.
 @JS()
-external SetLogLevelFn get setLogLevel;
+@staticInterop
+abstract class GoogleAccountsId {}
 
-///
-typedef SetLogLevelFn = void Function(String level);
+/// The `google.accounts.id` methods
+extension GoogleAccountsIdExtension on GoogleAccountsId {
+  /// An undocumented method.
+  ///
+  /// Try it with 'debug'.
+  external void setLogLevel(String level);
 
-/*
-// Method: google.accounts.id.initialize
-// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.initialize
-*/
+  /// Initializes the Sign In With Google client based on [IdConfiguration].
+  ///
+  /// The `initialize` method creates a Sign In With Google client instance that
+  /// can be implicitly used by all modules in the same web page.
+  ///
+  /// * You only need to call the `initialize` method once even if you use
+  ///   multiple modules (like One Tap, Personalized button, revocation, etc.) in
+  ///   the same web page.
+  /// * If you do call the google.accounts.id.initialize method multiple times,
+  ///   only the configurations in the last call will be remembered and used.
+  ///
+  /// You actually reset the configurations whenever you call the `initialize`
+  /// method, and all subsequent methods in the same web page will use the new
+  /// configurations immediately.
+  ///
+  /// WARNING: The `initialize` method should be called only once, even if you
+  /// use both One Tap and button in the same web page.
+  ///
+  /// Method: google.accounts.id.initialize
+  /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.initialize
+  external void initialize(IdConfiguration idConfiguration);
 
-/// Initializes the Sign In With Google client based on [IdConfiguration].
-///
-/// The `initialize` method creates a Sign In With Google client instance that
-/// can be implicitly used by all modules in the same web page.
-///
-/// * You only need to call the `initialize` method once even if you use
-///   multiple modules (like One Tap, Personalized button, revocation, etc.) in
-///   the same web page.
-/// * If you do call the google.accounts.id.initialize method multiple times,
-///   only the configurations in the last call will be remembered and used.
-///
-/// You actually reset the configurations whenever you call the `initialize`
-/// method, and all subsequent methods in the same web page will use the new
-/// configurations immediately.
-///
-/// WARNING: The `initialize` method should be called only once, even if you
-/// use both One Tap and button in the same web page.
-@JS()
-external InitializeFn get initialize;
+  /// The `prompt` method displays the One Tap prompt or the browser native
+  /// credential manager after the [initialize] method is invoked.
+  ///
+  /// Normally, the `prompt` method is called on page load. Due to the session
+  /// status and user settings on the Google side, the One Tap prompt UI might
+  /// not be displayed. To get notified on the UI status for different moments,
+  /// pass a [PromptMomentListenerFn] to receive UI status notifications.
+  ///
+  /// Notifications are fired on the following moments:
+  ///
+  /// * Display moment: This occurs after the `prompt` method is called. The
+  ///   notification contains a boolean value to indicate whether the UI is
+  ///   displayed or not.
+  /// * Skipped moment: This occurs when the One Tap prompt is closed by an auto
+  ///   cancel, a manual cancel, or when Google fails to issue a credential, such
+  ///   as when the selected session has signed out of Google.
+  ///   In these cases, we recommend that you continue on to the next identity
+  ///   providers, if there are any.
+  /// * Dismissed moment: This occurs when Google successfully retrieves a
+  ///   credential or a user wants to stop the credential retrieval flow. For
+  ///   example, when the user begins to input their username and password in
+  ///   your login dialog, you can call the [cancel] method to close the One Tap
+  ///   prompt and trigger a dismissed moment.
+  ///
+  ///   WARNING: When on a dismissed moment, do not try any of the next identity
+  ///   providers.
+  ///
+  /// Method: google.accounts.id.prompt
+  /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt
+  external void prompt([PromptMomentListenerFn momentListener]);
 
-/// The type of the [initialize] function.
-typedef InitializeFn = void Function(IdConfiguration idConfiguration);
+  /// Renders a Sign In With Google button in your web page.
+  ///
+  /// Method: google.accounts.id.renderButton
+  /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.renderButton
+  external void renderButton(
+    DomHtmlElement parent, [
+    GsiButtonConfiguration options,
+  ]);
 
-/*
-// Data type: IdConfiguration
-// https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
-*/
+  /// Record when the user signs out of your website in cookies.
+  ///
+  /// This prevents a UX dead loop.
+  ///
+  /// Method: google.accounts.id.disableAutoselect
+  /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.disableAutoSelect
+  external void disableAutoSelect();
+
+  /// A wrapper for the `store` method of the browser's native credential manager API.
+  ///
+  /// It can only be used to store a Password [Credential].
+  ///
+  /// See: https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/store
+  ///
+  /// Method: google.accounts.id.storeCredential
+  /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.storeCredential
+  external void storeCredential(Credential credential, [VoidFn fallback]);
+
+  /// Cancels the One Tap flow.
+  ///
+  /// You can cancel the One Tap flow if you remove the prompt from the relying
+  /// party DOM. The cancel operation is ignored if a credential is already
+  /// selected.
+  ///
+  /// Method: google.accounts.id.cancel
+  /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.cancel
+  external void cancel();
+
+  /// Revokes the OAuth grant used to share the ID token for the specified user.
+  ///
+  /// [hint] is the email address or unique ID of the user's Google Account. The
+  /// ID is the `sub` property of the [CredentialResponse.credential] payload.
+  ///
+  /// The optional [callback] is a function that gets called to report on the
+  /// success of the revocation call.
+  ///
+  /// The [callback] parameter must be manually wrapped in [allowInterop]
+  /// before being passed to the [revoke] function.
+  ///
+  /// Method: google.accounts.id.revoke
+  /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.revoke
+  external void revoke(String hint, [RevocationResponseHandlerFn callback]);
+}
 
 /// The configuration object for the [initialize] method.
+///
+/// Data type: IdConfiguration
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
 @JS()
 @anonymous
 @staticInterop
@@ -152,55 +240,13 @@
   });
 }
 
-/*
-// Method: google.accounts.id.prompt
-// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt
-*/
-
-/// The `prompt` method displays the One Tap prompt or the browser native
-/// credential manager after the [initialize] method is invoked.
-///
-/// Normally, the `prompt` method is called on page load. Due to the session
-/// status and user settings on the Google side, the One Tap prompt UI might
-/// not be displayed. To get notified on the UI status for different moments,
-/// pass a [PromptMomentListenerFn] to receive UI status notifications.
-///
-/// Notifications are fired on the following moments:
-///
-/// * Display moment: This occurs after the `prompt` method is called. The
-///   notification contains a boolean value to indicate whether the UI is
-///   displayed or not.
-/// * Skipped moment: This occurs when the One Tap prompt is closed by an auto
-///   cancel, a manual cancel, or when Google fails to issue a credential, such
-///   as when the selected session has signed out of Google.
-///   In these cases, we recommend that you continue on to the next identity
-///   providers, if there are any.
-/// * Dismissed moment: This occurs when Google successfully retrieves a
-///   credential or a user wants to stop the credential retrieval flow. For
-///   example, when the user begins to input their username and password in
-///   your login dialog, you can call the [cancel] method to close the One Tap
-///   prompt and trigger a dismissed moment.
-///
-///   WARNING: When on a dismissed moment, do not try any of the next identity
-///   providers.
-@JS()
-external PromptFn get prompt;
-
-/// The type of the [prompt] function.
-///
-/// The [momentListener] parameter must be manually wrapped in [allowInterop]
-/// before being passed to the [prompt] function.
-typedef PromptFn = void Function([PromptMomentListenerFn momentListener]);
-
 /// The type of the function that can be passed to [prompt] to listen for [PromptMomentNotification]s.
 typedef PromptMomentListenerFn = void Function(PromptMomentNotification moment);
 
-/*
-// Data type: PromptMomentNotification
-// https://developers.google.com/identity/gsi/web/reference/js-reference#PromptMomentNotification
-*/
-
 /// A moment (status) notification from the [prompt] method.
+///
+/// Data type: PromptMomentNotification
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#PromptMomentNotification
 @JS()
 @staticInterop
 abstract class PromptMomentNotification {}
@@ -246,25 +292,32 @@
       maybeEnum(_getDismissedReason(), MomentDismissedReason.values);
 }
 
-/*
-// Data type: CredentialResponse
-// https://developers.google.com/identity/gsi/web/reference/js-reference#CredentialResponse
-*/
-
 /// The object passed as the parameter of your [CallbackFn].
+///
+/// Data type: CredentialResponse
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#CredentialResponse
 @JS()
 @staticInterop
 abstract class CredentialResponse {}
 
 /// The fields that are contained in the credential response object.
 extension CredentialResponseExtension on CredentialResponse {
+  /// The ClientID for this Credential.
+  external String? get client_id;
+
+  /// Error while signing in.
+  external String? get error;
+
+  /// Details of the error while signing in.
+  external String? get error_detail;
+
   /// This field is the ID token as a base64-encoded JSON Web Token (JWT)
   /// string.
   ///
   /// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#credential
-  external String get credential;
+  external String? get credential;
   @JS('select_by')
-  external String get _select_by;
+  external String? get _select_by;
 
   /// This field sets how the credential was selected.
   ///
@@ -272,8 +325,8 @@
   /// to set the value.
   ///
   /// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#select_by
-  CredentialSelectBy get select_by =>
-      CredentialSelectBy.values.byName(_select_by);
+  CredentialSelectBy? get select_by =>
+      maybeEnum(_select_by, CredentialSelectBy.values);
 }
 
 /// The type of the `callback` used to create an [IdConfiguration].
@@ -285,20 +338,69 @@
 /// attribute.
 typedef CallbackFn = void Function(CredentialResponse credentialResponse);
 
-/*
-// Method: google.accounts.id.renderButton
-// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.renderButton
-//
-// Data type: GsiButtonConfiguration
-// https://developers.google.com/identity/gsi/web/reference/js-reference#GsiButtonConfiguration
-//
-// Question: Do we need to implement renderButton and its options?
-*/
+/// The configuration object for the [renderButton] method.
+///
+/// Data type: GsiButtonConfiguration
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#GsiButtonConfiguration
+@JS()
+@anonymous
+@staticInterop
+abstract class GsiButtonConfiguration {
+  /// Constructs an options object for the [renderButton] method.
+  ///
+  /// The following properties need to be manually wrapped in [allowInterop]
+  /// before being passed to this constructor:
+  external factory GsiButtonConfiguration({
+    /// The button type.
+    ButtonType type,
 
-/*
-// Data type: Credential
-// https://developers.google.com/identity/gsi/web/reference/js-reference#type-Credential
-*/
+    /// The button theme.
+    ButtonTheme theme,
+
+    /// The button size.
+    ButtonSize size,
+
+    /// The button text.
+    ButtonText text,
+
+    /// The button shape.
+    ButtonShape shape,
+
+    /// The Google logo alignment in the button.
+    ButtonLogoAlignment logo_alignment,
+
+    /// The minimum button width, in pixels.
+    ///
+    /// The maximum width is 400 pixels.
+    double width,
+
+    /// The pre-set locale of the button text.
+    ///
+    /// If not set, the browser's default locale or the Google session user's
+    /// preference is used.
+    String locale,
+
+    /// A function to be called when the button is clicked.
+    GsiButtonClickListenerFn click_listener,
+  });
+}
+
+/// The object passed as an optional parameter to `click_listener` function.
+@JS()
+@staticInterop
+abstract class GsiButtonData {}
+
+/// The fields that are contained in the button data.
+extension GsiButtonDataExtension on GsiButtonData {
+  /// Nonce
+  external String? get nonce;
+
+  /// State
+  external String? get state;
+}
+
+/// The type of the [GsiButtonConfiguration] `click_listener` function.
+typedef GsiButtonClickListenerFn = void Function(GsiButtonData? gsiButtonData);
 
 /// The object passed to the [NativeCallbackFn]. Represents a PasswordCredential
 /// that was returned by the Browser.
@@ -307,6 +409,9 @@
 /// in the browser through the [storeCredential] method.
 ///
 /// See also: https://developer.mozilla.org/en-US/docs/Web/API/PasswordCredential/PasswordCredential
+///
+/// Data type: Credential
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#type-Credential
 @JS()
 @anonymous
 @staticInterop
@@ -334,94 +439,21 @@
 typedef NativeCallbackFn = void Function(Credential credential);
 
 /*
-// Method: google.accounts.id.disableAutoselect
-// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.disableAutoSelect
-*/
-
-/// When the user signs out of your website, you need to call this method to
-/// record the status in cookies.
-///
-/// This prevents a UX dead loop.
-@JS()
-external VoidFn get disableAutoSelect;
-
-/*
-// Method: google.accounts.id.storeCredential
-// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.storeCredential
-*/
-
-/// This method is a simple wrapper for the `store` method of the browser's
-/// native credential manager API.
-///
-/// It can only be used to store a Password [Credential].
-///
-/// See: https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/store
-@JS()
-external StoreCredentialFn get storeCredential;
-
-/// The type of the [storeCredential] function.
-///
-/// The [callback] parameter must be manually wrapped in [allowInterop]
-/// before being passed to the [storeCredential] function.
-// Question: What's the type of the callback function??? VoidFn?
-typedef StoreCredentialFn = void Function(
-  Credential credential,
-  Function? callback,
-);
-
-/*
-// Method: google.accounts.id.cancel
-// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.cancel
-*/
-
-/// You can cancel the One Tap flow if you remove the prompt from the relying
-/// party DOM. The cancel operation is ignored if a credential is already
-/// selected.
-@JS()
-external VoidFn get cancel;
-
-/*
 // Library load callback: onGoogleLibraryLoad
 // https://developers.google.com/identity/gsi/web/reference/js-reference#onGoogleLibraryLoad
 // See: `load_callback.dart` and `loader.dart`
 */
 
-/*
-// Method: google.accounts.id.revoke
-// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.revoke
-*/
-
-/// The `revoke` method revokes the OAuth grant used to share the ID token for
-/// the specified user.
-@JS()
-external RevokeFn get revoke;
-
-/// The type of the [revoke] function.
-///
-/// [hint] is the email address or unique ID of the user's Google Account. The
-/// ID is the `sub` property of the [CredentialResponse.credential] payload.
-///
-/// The optional [callback] is a function that gets called to report on the
-/// success of the revocation call.
-///
-/// The [callback] parameter must be manually wrapped in [allowInterop]
-/// before being passed to the [revoke] function.
-typedef RevokeFn = void Function(String hint,
-    [RevocationResponseHandlerFn callback]);
-
 /// The type of the `callback` function passed to [revoke], to be notified of
 /// the success of the revocation operation.
 typedef RevocationResponseHandlerFn = void Function(
   RevocationResponse revocationResponse,
 );
 
-/*
-// Data type: RevocationResponse
-// https://developers.google.com/identity/gsi/web/reference/js-reference#RevocationResponse
-*/
-
-/// The parameter passed to the optional [RevocationResponseHandlerFn]
-/// `callback` of the [revoke] function.
+/// The parameter passed to the `callback` of the [revoke] function.
+///
+/// Data type: RevocationResponse
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#RevocationResponse
 @JS()
 @staticInterop
 abstract class RevocationResponse {}
diff --git a/packages/google_identity_services_web/lib/src/js_interop/google_accounts_oauth2.dart b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_oauth2.dart
index b320580..83061d0 100644
--- a/packages/google_identity_services_web/lib/src/js_interop/google_accounts_oauth2.dart
+++ b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_oauth2.dart
@@ -9,34 +9,84 @@
 // * non_constant_identifier_names required to be able to use the same parameter
 //   names as the underlying library.
 
-@JS('google.accounts.oauth2')
-library oauth2;
+@JS()
+library google_accounts_oauth2;
 
 import 'package:js/js.dart';
 
+import 'dom.dart';
 import 'shared.dart';
 
-// Code Client
+/// Binding to the `google.accounts.oauth2` JS global.
+///
+/// See: https://developers.google.com/identity/oauth2/web/reference/js-reference
+@JS('google.accounts.oauth2')
+external GoogleAccountsOauth2 get oauth2;
 
-/*
-// Method: google.accounts.oauth2.initCodeClient
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initCodeClient
-*/
-
-/// The initCodeClient method initializes and returns a code client, with the
-/// passed-in [config].
+/// The Dart definition of the `google.accounts.oauth2` global.
 @JS()
-external InitCodeClientFn get initCodeClient;
+@staticInterop
+abstract class GoogleAccountsOauth2 {}
 
-/// The type of the [initCodeClient] function.
-typedef InitCodeClientFn = CodeClient Function(CodeClientConfig config);
+/// The `google.accounts.oauth2` methods
+extension GoogleAccountsOauth2Extension on GoogleAccountsOauth2 {
+  /// Initializes and returns a code client, with the passed-in [config].
+  ///
+  /// Method: google.accounts.oauth2.initCodeClient
+  /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initCodeClient
+  external CodeClient initCodeClient(CodeClientConfig config);
 
-/*
-// Data type: CodeClientConfig
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig
-*/
+  /// Initializes and returns a token client, with the passed-in [config].
+  ///
+  /// Method: google.accounts.oauth2.initTokenClient
+  /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient
+  external TokenClient initTokenClient(TokenClientConfig config);
+
+  // Method: google.accounts.oauth2.hasGrantedAllScopes
+  // https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
+  @JS('hasGrantedAllScopes')
+  external bool _hasGrantedScope(TokenResponse token, String scope);
+
+  /// Checks if hte user has granted **all** the specified [scopes].
+  ///
+  /// [scopes] is a space-separated list of scope names.
+  ///
+  /// Method: google.accounts.oauth2.hasGrantedAllScopes
+  /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
+  bool hasGrantedAllScopes(TokenResponse tokenResponse, List<String> scopes) {
+    return scopes
+        .every((String scope) => _hasGrantedScope(tokenResponse, scope));
+  }
+
+  /// Checks if hte user has granted **all** the specified [scopes].
+  ///
+  /// [scopes] is a space-separated list of scope names.
+  ///
+  /// Method: google.accounts.oauth2.hasGrantedAllScopes
+  /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
+  bool hasGrantedAnyScopes(TokenResponse tokenResponse, List<String> scopes) {
+    return scopes.any((String scope) => _hasGrantedScope(tokenResponse, scope));
+  }
+
+  /// Revokes all of the scopes that the user granted to the app.
+  ///
+  /// A valid [accessToken] is required to revoke permissions.
+  ///
+  /// The [done] callback is called once the revoke action is done. It must be
+  /// manually wrapped in [allowInterop] before being passed to this method.
+  ///
+  /// Method: google.accounts.oauth2.revoke
+  /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke
+  external void revoke(
+    String accessToken, [
+    RevokeTokenDoneFn done,
+  ]);
+}
 
 /// The configuration object for the [initCodeClient] method.
+///
+/// Data type: CodeClientConfig
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig
 @JS()
 @anonymous
 @staticInterop
@@ -51,6 +101,7 @@
     String? redirect_uri,
     bool? auto_select,
     CodeClientCallbackFn? callback,
+    ErrorCallbackFn? error_callback,
     String? state,
     bool? enable_serial_consent,
     String? hint,
@@ -60,14 +111,12 @@
   });
 }
 
-/*
-// Data type: CodeClient
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClient
-*/
-
 /// A client that can start the OAuth 2.0 Code UX flow.
 ///
 /// See: https://developers.google.com/identity/oauth2/web/guides/use-code-model
+///
+/// Data type: CodeClient
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClient
 @JS()
 @staticInterop
 abstract class CodeClient {}
@@ -78,12 +127,10 @@
   external void requestCode();
 }
 
-/*
-// Data type: CodeResponse
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse
-*/
-
 /// The object passed as the parameter of your [CodeClientCallbackFn].
+///
+/// Data type: CodeResponse
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse
 @JS()
 @staticInterop
 abstract class CodeResponse {}
@@ -116,27 +163,10 @@
 /// The type of the `callback` function passed to [CodeClientConfig].
 typedef CodeClientCallbackFn = void Function(CodeResponse response);
 
-// Token Client
-
-/*
-// Method: google.accounts.oauth2.initTokenClient
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient
-*/
-
-/// The initCodeClient method initializes and returns a code client, with the
-/// passed-in [config].
-@JS()
-external InitTokenClientFn get initTokenClient;
-
-/// The type of the [initCodeClient] function.
-typedef InitTokenClientFn = TokenClient Function(TokenClientConfig config);
-
-/*
-// Data type: TokenClientConfig
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClientConfig
-*/
-
 /// The configuration object for the [initTokenClient] method.
+///
+/// Data type: TokenClientConfig
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClientConfig
 @JS()
 @anonymous
 @staticInterop
@@ -149,6 +179,7 @@
     required String client_id,
     required TokenClientCallbackFn? callback,
     required String scope,
+    ErrorCallbackFn? error_callback,
     String? prompt,
     bool? enable_serial_consent,
     String? hint,
@@ -157,14 +188,12 @@
   });
 }
 
-/*
-// Data type: TokenClient
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClient
-*/
-
 /// A client that can start the OAuth 2.0 Token UX flow.
 ///
 /// See: https://developers.google.com/identity/oauth2/web/guides/use-token-model
+///
+/// Data type: TokenClient
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClient
 @JS()
 @staticInterop
 abstract class TokenClient {}
@@ -177,13 +206,10 @@
   ]);
 }
 
-/*
-// Data type: OverridableTokenClientConfig
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#OverridableTokenClientConfig
-*/
-
-/// The overridable configuration object for the
-/// [TokenClientExtension.requestAccessToken] method.
+/// The overridable configuration object for the [TokenClientExtension.requestAccessToken] method.
+///
+/// Data type: OverridableTokenClientConfig
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#OverridableTokenClientConfig
 @JS()
 @anonymous
 @staticInterop
@@ -229,15 +255,18 @@
     /// uses to maintain state between your authorization request and the
     /// authorization server's response.
     String? state,
+
+    /// Preserves previously requested scopes in this new request.
+    ///
+    /// (Undocumented)
+    bool? include_granted_scopes,
   });
 }
 
-/*
-// Data type: TokenResponse
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
-*/
-
 /// The object passed as the parameter of your [TokenClientCallbackFn].
+///
+/// Data type: TokenResponse
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
 @JS()
 @staticInterop
 abstract class TokenResponse {}
@@ -283,63 +312,55 @@
 /// The type of the `callback` function passed to [TokenClientConfig].
 typedef TokenClientCallbackFn = void Function(TokenResponse response);
 
-/*
-// Method: google.accounts.oauth2.hasGrantedAllScopes
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
-*/
+/// The type of the `error_callback` in both oauth2 initXClient calls.
+///
+/// (Currently undocumented)
+///
+/// `error` should be of type [GoogleIdentityServicesError]?, but it cannot be
+/// because of this DDC bug: https://github.com/dart-lang/sdk/issues/50899
+typedef ErrorCallbackFn = void Function(Object? error);
 
-/// Checks if the user granted **all** the specified scopes.
+/// An error returned by `initTokenClient` or `initDataClient`.
+///
+/// Cannot be used: https://github.com/dart-lang/sdk/issues/50899
 @JS()
-external HasGrantedScopesFn get hasGrantedAllScopes;
+@staticInterop
+abstract class GoogleIdentityServicesError extends DomError {}
 
-/*
-// Method: google.accounts.oauth2.hasGrantedAnyScopes
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAnyScopes
-*/
+/// Methods of the GoogleIdentityServicesError object.
+///
+/// Cannot be used: https://github.com/dart-lang/sdk/issues/50899
+extension GoogleIdentityServicesErrorExtension on GoogleIdentityServicesError {
+  @JS('type')
+  external String get _type;
+  // String get _type => js_util.getProperty<String>(this, 'type');
 
-/// Checks if the user granted **any** of the specified scopes.
+  /// The type of error
+  GoogleIdentityServicesErrorType get type =>
+      GoogleIdentityServicesErrorType.values.byName(_type);
+}
+
+/// The signature of the `done` function for [revoke].
+typedef RevokeTokenDoneFn = void Function(TokenRevocationResponse response);
+
+/// The parameter passed to the `callback` of the [revoke] function.
+///
+/// Data type: RevocationResponse
+/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
 @JS()
-external HasGrantedScopesFn get hasGrantedAnyScopes;
+@staticInterop
+abstract class TokenRevocationResponse {}
 
-/// The signature for functions that check if any/all scopes have been granted.
-///
-/// Used by [hasGrantedAllScopes] and [hasGrantedAnyScope].
-typedef HasGrantedScopesFn = bool Function(
-  TokenResponse tokenResponse,
-  String firstScope, [
-  String? scope2,
-  String? scope3,
-  String? scope4,
-  String? scope5,
-  String? scope6,
-  String? scope7,
-  String? scope8,
-  String? scope9,
-  String? scope10,
-]);
+/// The fields that are contained in the [TokenRevocationResponse] object.
+extension TokenRevocationResponseExtension on TokenRevocationResponse {
+  /// This field is a boolean value set to true if the revoke method call
+  /// succeeded or false on failure.
+  external bool get successful;
 
-/*
-// Method: google.accounts.oauth2.revoke
-// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke
-*/
+  /// This field is a string value and contains a detailed error message if the
+  /// revoke method call failed, it is undefined on success.
+  external String? get error;
 
-/// The [revokeToken] method revokes all of the scopes that the user granted to
-/// the app. A valid access token is required to revoke the permission.
-///
-/// The `done` callback is called once the revoke action is done.
-@JS('revoke')
-external RevokeTokenFn get revokeToken;
-
-/// The signature of the [revokeToken] function.
-///
-/// The (optional) [done] parameter must be manually wrapped in [allowInterop]
-/// before being passed to the [revokeToken] function.
-typedef RevokeTokenFn = void Function(
-  String accessToken, [
-  RevokeTokenDoneFn done,
-]);
-
-/// The signature of the `done` function for [revokeToken].
-///
-/// Work in progress here: b/248628502
-typedef RevokeTokenDoneFn = void Function(String jsonError);
+  /// The description of the error.
+  external String? get error_description;
+}
diff --git a/packages/google_identity_services_web/lib/src/js_interop/shared.dart b/packages/google_identity_services_web/lib/src/js_interop/shared.dart
index 4e35f38..0bd842b 100644
--- a/packages/google_identity_services_web/lib/src/js_interop/shared.dart
+++ b/packages/google_identity_services_web/lib/src/js_interop/shared.dart
@@ -213,3 +213,159 @@
   @override
   String toString() => _selectBy;
 }
+
+/// The type of button to be rendered.
+///
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#type
+enum ButtonType {
+  /// A button with text or personalized information.
+  standard('standard'),
+
+  /// An icon button without text.
+  icon('icon');
+
+  ///
+  const ButtonType(String type) : _type = type;
+  final String _type;
+
+  @override
+  String toString() => _type;
+}
+
+/// The theme of the button to be rendered.
+///
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#theme
+enum ButtonTheme {
+  /// A standard button theme.
+  outline('outline'),
+
+  /// A blue-filled button theme.
+  filled_blue('filled_blue'),
+
+  /// A black-filled button theme.
+  filled_black('filled_black');
+
+  ///
+  const ButtonTheme(String theme) : _theme = theme;
+  final String _theme;
+
+  @override
+  String toString() => _theme;
+}
+
+/// The theme of the button to be rendered.
+///
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#size
+enum ButtonSize {
+  /// A large button (about 40px tall).
+  large('large'),
+
+  /// A medium-sized button (about 32px tall).
+  medium('medium'),
+
+  /// A small button (about 20px tall).
+  small('small');
+
+  ///
+  const ButtonSize(String size) : _size = size;
+  final String _size;
+
+  @override
+  String toString() => _size;
+}
+
+/// The button text.
+///
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#text
+enum ButtonText {
+  /// The button text is "Sign in with Google".
+  signin_with('signin_with'),
+
+  /// The button text is "Sign up with Google".
+  signup_with('signup_with'),
+
+  /// The button text is "Continue with Google".
+  continue_with('continue_with'),
+
+  /// The button text is "Sign in".
+  signin('signin');
+
+  ///
+  const ButtonText(String text) : _text = text;
+  final String _text;
+
+  @override
+  String toString() => _text;
+}
+
+/// The button shape.
+///
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#shape
+enum ButtonShape {
+  /// The rectangular-shaped button.
+  ///
+  /// If used for the [ButtonType.icon], then it's the same as [square].
+  rectangular('rectangular'),
+
+  /// The pill-shaped button.
+  ///
+  /// If used for the [ButtonType.icon], then it's the same as [circle].
+  pill('pill'),
+
+  /// The circle-shaped button.
+  ///
+  /// If used for the [ButtonType.standard], then it's the same as [pill].
+  circle('circle'),
+
+  /// The square-shaped button.
+  ///
+  /// If used for the [ButtonType.standard], then it's the same as [rectangular].
+  square('square');
+
+  ///
+  const ButtonShape(String shape) : _shape = shape;
+  final String _shape;
+
+  @override
+  String toString() => _shape;
+}
+
+/// The type of button to be rendered.
+///
+/// https://developers.google.com/identity/gsi/web/reference/js-reference#type
+enum ButtonLogoAlignment {
+  /// Left-aligns the Google logo.
+  left('left'),
+
+  /// Center-aligns the Google logo.
+  center('center');
+
+  ///
+  const ButtonLogoAlignment(String alignment) : _alignment = alignment;
+  final String _alignment;
+
+  @override
+  String toString() => _alignment;
+}
+
+/// The `type` of the error object passed into the `error_callback` function.
+enum GoogleIdentityServicesErrorType {
+  /// Missing required parameter.
+  missing_required_parameter('missing_required_parameter'),
+
+  /// The popup was closed before the flow was completed.
+  popup_closed('popup_closed'),
+
+  /// Popup failed to open.
+  popup_failed_to_open('popup_failed_to_open'),
+
+  /// Unknown error.
+  unknown('unknown');
+
+  ///
+  const GoogleIdentityServicesErrorType(String type) : _type = type;
+  final String _type;
+
+  @override
+  String toString() => _type;
+}
diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml
index 3ba6774..365deda 100644
--- a/packages/google_identity_services_web/pubspec.yaml
+++ b/packages/google_identity_services_web/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Dart JS-interop layer for Google Identity Services. Google's new sign-in SDK for Web that supports multiple types of credentials.
 repository: https://github.com/flutter/packages/tree/main/packages/google_identity_services_web
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_identiy_services_web%22
-version: 0.1.1
+version: 0.2.0
 
 environment:
   sdk: ">=2.17.0 <3.0.0"
diff --git a/packages/google_identity_services_web/test/README.md b/packages/google_identity_services_web/test/README.md
index 8a783c2..38faf11 100644
--- a/packages/google_identity_services_web/test/README.md
+++ b/packages/google_identity_services_web/test/README.md
@@ -1,3 +1,8 @@
 # Tests
 
 Use `dart run tool/run_tests.dart` to run tests in this package.
+
+## Failed to run Chrome: No such file or directory
+
+Ensure the correct path to the Chrome executable is set in `dart_test.yaml`. It
+may be other than `chrome` (for example, `google-chrome` in my machine).