[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).