[gis_web] Properly define optional args. Add scope to config. (#2688)
diff --git a/.cirrus.yml b/.cirrus.yml
index 9067fbb..c59853a 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -233,7 +233,12 @@
build_script:
- ./script/tool_runner.sh build-examples --web
drive_script:
- - ./script/tool_runner.sh drive-examples --web
+ # google_identity_services_web tests need Dart >=2.17, which is not available in stable.
+ - if [[ "$CHANNEL" == "master" ]]; then
+ - ./script/tool_runner.sh drive-examples --web
+ - else
+ - ./script/tool_runner.sh drive-examples --web --exclude=google_identity_services_web
+ - fi
- name: web_benchmarks_test
env:
matrix:
diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md
index 6073234..0ec9358 100644
--- a/packages/google_identity_services_web/CHANGELOG.md
+++ b/packages/google_identity_services_web/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.1.1
+
+* Add optional `scope` to `OverridableTokenClientConfig` object.
+* Mark some callbacks as optional properly.
+
## 0.1.0
* Initial release.
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
new file mode 100644
index 0000000..b1c1b02
--- /dev/null
+++ b/packages/google_identity_services_web/example/integration_test/js_interop_gis_id_test.dart
@@ -0,0 +1,101 @@
+// 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/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml
index e7d6429..a3dcd80 100644
--- a/packages/google_identity_services_web/example/pubspec.yaml
+++ b/packages/google_identity_services_web/example/pubspec.yaml
@@ -17,8 +17,12 @@
dev_dependencies:
build_runner: ^2.1.10 # To extract README excerpts only.
+ flutter_driver:
+ sdk: flutter
flutter_test:
sdk: flutter
+ integration_test:
+ sdk: flutter
flutter:
uses-material-design: true
diff --git a/packages/google_identity_services_web/example/test_driver/integration_test.dart b/packages/google_identity_services_web/example/test_driver/integration_test.dart
new file mode 100644
index 0000000..4f10f2a
--- /dev/null
+++ b/packages/google_identity_services_web/example/test_driver/integration_test.dart
@@ -0,0 +1,7 @@
+// 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 'package:integration_test/integration_test_driver.dart';
+
+Future<void> main() => integrationDriver();
diff --git a/packages/google_identity_services_web/example/web/mock-gis.js b/packages/google_identity_services_web/example/web/mock-gis.js
new file mode 100644
index 0000000..56701e2
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/mock-gis.js
@@ -0,0 +1,144 @@
+// 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.
+
+// This file is used to mock the GIS library for integration tests under:
+// example/integration_test
+
+class PromptMomentNotification {
+ constructor(momentType, reason) {
+ this.momentType = momentType;
+ this.reason = reason;
+ this.getNotDisplayedReason = this._getReason;
+ this.getSkippedReason = this._getReason;
+ this.getDismissedReason = this._getReason;
+ }
+ getMomentType() { return this.momentType; }
+ _getReason() { return this.reason; }
+ isDismissedMoment() { return this.momentType === "dismissed" }
+ isDisplayMoment() { return this.momentType === "display" }
+ isSkippedMoment() { return this.momentType === "skipped" }
+ isDisplayed() { return this.isDisplayMoment() && !this.reason; }
+ isNotDisplayed() { return this.isDisplayMoment() && this.reason; }
+}
+
+const CREDENTIAL_RETURNED = new PromptMomentNotification("dismissed", "credential_returned");
+const USER_CANCEL = new PromptMomentNotification("skipped", "user_cancel");
+
+function callAsync(func, timeout = 100) {
+ window.setTimeout(func, timeout)
+}
+
+class Id {
+ initialize(config) {
+ this.config = config;
+ }
+ prompt(momentListener) {
+ callAsync(() => {
+ if (this.mockCredentialResponse) {
+ let callback = this.config.callback;
+ if (callback) {
+ callback(this.mockCredentialResponse);
+ }
+ if (momentListener) {
+ momentListener(CREDENTIAL_RETURNED);
+ }
+ } else if (momentListener) {
+ momentListener(USER_CANCEL);
+ }
+ });
+ }
+ setMockCredentialResponse(credential, select_by) {
+ this.mockCredentialResponse = {
+ credential: credential,
+ select_by: select_by,
+ };
+ }
+ disableAutoSelect() {}
+ storeCredential() {}
+ cancel() {}
+ revoke(hint, callback) {
+ this.mockCredentialResponse = null;
+ if (!callback) {
+ return;
+ }
+ callAsync(() => {
+ callback({
+ successful: true,
+ error: 'Revoked ' + hint,
+ });
+ })
+ }
+}
+
+class CodeClient {
+ constructor(config) {
+ this.config = config;
+ }
+ requestCode() {
+ let callback = this.config.callback;
+ if (!callback) {
+ return;
+ }
+ callAsync(() => {
+ callback(this.codeResponse);
+ });
+ }
+ setMockCodeResponse(codeResponse) {
+ this.codeResponse = codeResponse;
+ }
+}
+
+class TokenClient {
+ constructor(config) {
+ this.config = config;
+ }
+ requestAccessToken(overridableConfig) {
+ this.overridableConfig = overridableConfig;
+ let callback = this.overridableConfig.callback || this.config.callback;
+ if (!callback) {
+ return;
+ }
+ callAsync(() => {
+ callback(this.tokenResponse);
+ });
+ }
+ setMockTokenResponse(tokenResponse) {
+ this.tokenResponse = tokenResponse;
+ }
+}
+
+class Oauth2 {
+ initCodeClient(config) {
+ return new CodeClient(config);
+ }
+ initTokenClient(config) {
+ return new TokenClient(config);
+ }
+ hasGrantedAllScopes(tokenResponse, scope, ...scopes) {
+ return tokenResponse != null;
+ }
+ hasGrantedAnyScopes(tokenResponse, scope, ...scopes) {
+ return tokenResponse != null;
+ }
+ revoke(accessToken, done) {
+ if (!done) {
+ return;
+ }
+ callAsync(() => {
+ done();
+ })
+ }
+}
+
+function mockGis() {
+ let goog = {
+ accounts: {
+ id: new Id(),
+ oauth2: new Oauth2(),
+ }
+ };
+ globalThis['google'] = goog;
+}
+
+mockGis();
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 52fd806..579662d 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
@@ -190,7 +190,7 @@
///
/// The [momentListener] parameter must be manually wrapped in [allowInterop]
/// before being passed to the [prompt] function.
-typedef PromptFn = void Function(PromptMomentListenerFn? momentListener);
+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);
@@ -406,10 +406,8 @@
///
/// The [callback] parameter must be manually wrapped in [allowInterop]
/// before being passed to the [revoke] function.
-typedef RevokeFn = void Function(
- String hint,
- RevocationResponseHandlerFn? callback,
-);
+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.
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 3986138..b320580 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
@@ -219,6 +219,12 @@
/// About Multiple Sign-in: https://support.google.com/accounts/answer/1721977
String? hint,
+ /// A space-delimited list of scopes that identify the resources that your
+ /// application could access on the user's behalf. These values inform the
+ /// consent screen that Google displays to the user.
+ // b/251971390
+ String? scope,
+
/// **Not recommended.** Specifies any string value that your application
/// uses to maintain state between your authorization request and the
/// authorization server's response.
@@ -330,7 +336,7 @@
/// before being passed to the [revokeToken] function.
typedef RevokeTokenFn = void Function(
String accessToken, [
- RevokeTokenDoneFn? done,
+ RevokeTokenDoneFn done,
]);
/// The signature of the `done` function for [revokeToken].
diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml
index d84a687..3ba6774 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.0
+version: 0.1.1
environment:
sdk: ">=2.17.0 <3.0.0"