From 8585b5908b50132198e8236915bc8b50cca58a16 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Mon, 4 Dec 2023 17:29:44 -0800 Subject: [PATCH 01/22] [gis_web] Remove bespoke dom.dart files --- .../example/integration_test/src/dom.dart | 31 --- .../lib/src/js_interop/dom.dart | 187 ------------------ 2 files changed, 218 deletions(-) delete mode 100644 packages/google_identity_services_web/example/integration_test/src/dom.dart delete mode 100644 packages/google_identity_services_web/lib/src/js_interop/dom.dart diff --git a/packages/google_identity_services_web/example/integration_test/src/dom.dart b/packages/google_identity_services_web/example/integration_test/src/dom.dart deleted file mode 100644 index fb41b0a9b90..00000000000 --- a/packages/google_identity_services_web/example/integration_test/src/dom.dart +++ /dev/null @@ -1,31 +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. - -// ignore_for_file: public_member_api_docs - -import 'package:js/js.dart'; -import 'package:js/js_util.dart' as js_util; - -@JS() -@staticInterop -class DomDocument {} - -extension DomDocumentExtension on DomDocument { - DomElement createElement(String name, [Object? options]) => - js_util.callMethod(this, 'createElement', - [name, if (options != null) options]) as DomElement; -} - -@JS() -@staticInterop -class DomElement {} - -extension DomElementExtension on DomElement { - external DomElement? querySelector(String selector); -} - -@JS('document') -external DomDocument get domDocument; - -DomElement createDomElement(String tag) => domDocument.createElement(tag); 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 deleted file mode 100644 index ff5dfc96a9a..00000000000 --- a/packages/google_identity_services_web/lib/src/js_interop/dom.dart +++ /dev/null @@ -1,187 +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. - -/* -// DOM shim. This file contains everything we need from the DOM API written as -// @staticInterop, so we don't need dart:html -// https://developer.mozilla.org/en-US/docs/Web/API/ -*/ - -import 'package:js/js.dart'; - -/// Document interface -@JS() -@staticInterop -@anonymous -abstract class DomHtmlDocument {} - -/// Some methods of document -extension DomHtmlDocumentExtension on DomHtmlDocument { - /// document.head - external DomHtmlElement get head; - - /// document.createElement - external DomHtmlElement createElement(String tagName); -} - -/// console interface -@JS() -@staticInterop -@anonymous -abstract class DomConsole {} - -/// The interface of window.console -extension DomConsoleExtension on DomConsole { - /// console.debug - external DomConsoleDumpFn get debug; - - /// console.info - external DomConsoleDumpFn get info; - - /// console.log - external DomConsoleDumpFn get log; - - /// console.warn - external DomConsoleDumpFn get warn; - - /// console.error - external DomConsoleDumpFn get error; -} - -/// Fakey variadic-type for console-dumping methods (like console.log or info). -typedef DomConsoleDumpFn = void Function( - Object? arg, [ - Object? arg2, - Object? arg3, - Object? arg4, - Object? arg5, - Object? arg6, - Object? arg7, - Object? arg8, - Object? arg9, - Object? arg10, -]); - -/// An instance of an HTMLElement -@JS() -@staticInterop -@anonymous -abstract class DomHtmlElement {} - -/// (Some) methods of HtmlElement -extension DomHtmlElementExtension on DomHtmlElement { - /// Node.appendChild - external DomHtmlElement appendChild(DomHtmlElement child); -} - -/// An instance of an HTMLScriptElement -@JS() -@staticInterop -@anonymous -abstract class DomHtmlScriptElement extends DomHtmlElement {} - -/// Some methods exclusive of Script elements -extension DomHtmlScriptElementExtension on DomHtmlScriptElement { - external set src(Object stringOrSafeScriptURL); - external set async(bool async); - 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 -*/ - -/// A factory to create `TrustedTypePolicy` objects. -@JS() -@staticInterop -@anonymous -abstract class DomTrustedTypePolicyFactory {} - -/// (Some) methods of the [DomTrustedTypePolicyFactory]: -extension DomTrustedTypePolicyFactoryExtension on DomTrustedTypePolicyFactory { - /// createPolicy - external DomTrustedTypePolicy createPolicy( - String policyName, - DomTrustedTypePolicyOptions? policyOptions, - ); -} - -/// Options to create a trusted type policy. -@JS() -@staticInterop -@anonymous -abstract class DomTrustedTypePolicyOptions { - /// Constructs a TrustedPolicyOptions object in JavaScript. - /// - /// The following properties need to be manually wrapped in [allowInterop] - /// before being passed to this constructor: [createScriptURL]. - external factory DomTrustedTypePolicyOptions({ - DomCreateScriptUrlOptionFn? createScriptURL, - }); -} - -/// Type of the function to configure createScriptURL -typedef DomCreateScriptUrlOptionFn = String Function(String input); - -/// An instance of a TrustedTypePolicy -@JS() -@staticInterop -@anonymous -abstract class DomTrustedTypePolicy {} - -/// (Some) methods of the [DomTrustedTypePolicy] -extension DomTrustedTypePolicyExtension on DomTrustedTypePolicy { - /// Create a `TrustedScriptURL` for the given [input]. - external DomTrustedScriptUrl createScriptURL(String input); -} - -/// An instance of a DomTrustedScriptUrl -@JS() -@staticInterop -@anonymous -abstract class DomTrustedScriptUrl {} - -// Getters - -/// window.document -@JS() -@staticInterop -@anonymous -external DomHtmlDocument get document; - -/// window.trustedTypes (may or may not be supported by the browser) -@JS() -@staticInterop -@anonymous -external DomTrustedTypePolicyFactory? get trustedTypes; - -/// window.console -@JS() -@staticInterop -@anonymous -external DomConsole get console; From cf6438c536cf79cdd116696da8664d0284ad1591 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 01:47:05 -0800 Subject: [PATCH 02/22] Update package deps. --- packages/google_identity_services_web/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml index 25e99cd199a..66c7c59e41b 100644 --- a/packages/google_identity_services_web/pubspec.yaml +++ b/packages/google_identity_services_web/pubspec.yaml @@ -8,8 +8,8 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - js: ^0.6.4 meta: ^1.3.0 + web: ^0.4.0 dev_dependencies: path: ^1.8.1 From 4ebef20d3605a72cfad1acad7c8293bb7ea415c3 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 02:13:57 -0800 Subject: [PATCH 03/22] Add a file with tweaks to package web. --- .../src/js_interop/package_web_tweaks.dart | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart diff --git a/packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart b/packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart new file mode 100644 index 00000000000..76dc411c103 --- /dev/null +++ b/packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart @@ -0,0 +1,35 @@ +// 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. + +/// Provides some useful tweaks to `package:web`. +library package_web_tweaks; + +import 'dart:js_interop'; +import 'package:web/web.dart' as web; + +/// This extension gives web.window a nullable getter to the `trustedTypes` +/// property, which needs to be used to check for feature support. +extension NullableTrustedTypesGetter on web.Window { + /// + @JS('trustedTypes') + external web.TrustedTypePolicyFactory? get nullableTrustedTypes; +} + +/// This extension allows a trusted type policy to create a script URL without +/// the `args` parameter (which in Chrome currently fails). +extension CreateScriptUrlWithoutArgs on web.TrustedTypePolicy { + /// + @JS('createScriptURL') + external web.TrustedScriptURL createScriptURLNoArgs( + String input, + ); +} + +/// This extension allows setting a TrustedScriptURL as the src of a script element, +/// which currently only accepts a string. +extension TrustedTypeSrcAttribute on web.HTMLScriptElement { + /// + @JS('src') + external set srcTT(web.TrustedScriptURL value); +} From 95c665c9083d40b588ed15270938fbc239ab12f5 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 01:49:15 -0800 Subject: [PATCH 04/22] Migrate js_loader and its related js-interop. --- .../lib/src/js_interop/load_callback.dart | 12 ++++-- .../lib/src/js_loader.dart | 39 ++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/google_identity_services_web/lib/src/js_interop/load_callback.dart b/packages/google_identity_services_web/lib/src/js_interop/load_callback.dart index fa7c4ca96c0..7df615017f7 100644 --- a/packages/google_identity_services_web/lib/src/js_interop/load_callback.dart +++ b/packages/google_identity_services_web/lib/src/js_interop/load_callback.dart @@ -8,7 +8,7 @@ @JS() library id_load_callback; -import 'package:js/js.dart'; +import 'dart:js_interop'; import 'shared.dart'; @@ -17,10 +17,14 @@ import 'shared.dart'; // https://developers.google.com/identity/gsi/web/reference/js-reference#onGoogleLibraryLoad */ +@JS('onGoogleLibraryLoad') +@staticInterop +external set _onGoogleLibraryLoad(JSFunction callback); + /// Method called after the Sign In With Google JavaScript library is loaded. /// /// The [callback] parameter must be manually wrapped in [allowInterop] /// before being set to the [onGoogleLibraryLoad] property. -@JS() -@staticInterop -external set onGoogleLibraryLoad(VoidFn callback); +set onGoogleLibraryLoad(VoidFn function) { + _onGoogleLibraryLoad = function.toJS; +} diff --git a/packages/google_identity_services_web/lib/src/js_loader.dart b/packages/google_identity_services_web/lib/src/js_loader.dart index 0f2dc7b409c..bd876f98c5c 100644 --- a/packages/google_identity_services_web/lib/src/js_loader.dart +++ b/packages/google_identity_services_web/lib/src/js_loader.dart @@ -3,11 +3,12 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:js_interop'; -import 'package:js/js.dart' show allowInterop; +import 'package:web/web.dart' as web; -import 'js_interop/dom.dart'; import 'js_interop/load_callback.dart'; +import 'js_interop/package_web_tweaks.dart'; // The URL from which the script should be downloaded. const String _url = 'https://accounts.google.com/gsi/client'; @@ -17,39 +18,41 @@ const String _defaultTrustedPolicyName = 'gis-dart'; /// Loads the GIS SDK for web, using Trusted Types API when available. Future loadWebSdk({ - DomHtmlElement? target, + web.HTMLElement? target, String trustedTypePolicyName = _defaultTrustedPolicyName, }) { final Completer completer = Completer(); - onGoogleLibraryLoad = allowInterop(() => completer.complete()); + onGoogleLibraryLoad = () => completer.complete(); // If TrustedTypes are available, prepare a trusted URL. - DomTrustedScriptUrl? trustedUrl; - if (trustedTypes != null) { - console.debug( - 'TrustedTypes available. Creating policy:', - trustedTypePolicyName, + web.TrustedScriptURL? trustedUrl; + if (web.window.nullableTrustedTypes != null) { + web.console.debug( + 'TrustedTypes available. Creating policy: $trustedTypePolicyName'.toJS, ); - final DomTrustedTypePolicyFactory factory = trustedTypes!; try { - final DomTrustedTypePolicy policy = factory.createPolicy( + final web.TrustedTypePolicy policy = web.window.trustedTypes.createPolicy( trustedTypePolicyName, - DomTrustedTypePolicyOptions( - createScriptURL: allowInterop((String url) => _url), + web.TrustedTypePolicyOptions( + createScriptURL: ((JSString url) => _url).toJS, )); - trustedUrl = policy.createScriptURL(_url); + trustedUrl = policy.createScriptURLNoArgs(_url); } catch (e) { throw TrustedTypesException(e.toString()); } } - final DomHtmlScriptElement script = - document.createElement('script') as DomHtmlScriptElement - ..src = trustedUrl ?? _url + final web.HTMLScriptElement script = + web.document.createElement('script') as web.HTMLScriptElement ..async = true ..defer = true; + if (trustedUrl != null) { + script.srcTT = trustedUrl; + } else { + script.src = _url; + } - (target ?? document.head).appendChild(script); + (target ?? web.document.head!).appendChild(script); return completer.future; } From 85d9ce2a61d0373f894e235da048eb7f29308cfe Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 01:55:52 -0800 Subject: [PATCH 05/22] Migrate accounts.id to package:web. --- .../src/js_interop/google_accounts_id.dart | 274 +++++++++++++----- .../lib/src/js_interop/shared.dart | 5 +- 2 files changed, 212 insertions(+), 67 deletions(-) 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 1c267045313..36659a187c7 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 @@ -7,12 +7,9 @@ // ignore_for_file: non_constant_identifier_names // * non_constant_identifier_names required to be able to use the same parameter -// names as the underlying library. +// names as the underlying JS library. -@JS() -library google_accounts_id; - -import 'package:js/js.dart'; +import 'dart:js_interop'; import 'shared.dart'; @@ -32,7 +29,9 @@ extension GoogleAccountsIdExtension on GoogleAccountsId { /// An undocumented method. /// /// Try it with 'debug'. - external void setLogLevel(String level); + void setLogLevel(String level) => _setLogLevel(level.toJS); + @JS('setLogLevel') + external void _setLogLevel(JSString level); /// Initializes the Sign In With Google client based on [IdConfiguration]. /// @@ -85,16 +84,40 @@ extension GoogleAccountsIdExtension on GoogleAccountsId { /// /// Method: google.accounts.id.prompt /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt - external void prompt([PromptMomentListenerFn momentListener]); + void prompt([PromptMomentListenerFn? momentListener]) { + if (momentListener == null) { + return _prompt(); + } + return _promptWithListener(momentListener.toJS); + } + + @JS('prompt') + external void _prompt(); + @JS('prompt') + external void _promptWithListener(JSFunction momentListener); /// 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( + void renderButton( Object parent, [ - GsiButtonConfiguration options, - ]); + GsiButtonConfiguration? options, + ]) { + assert(parent is JSObject, + 'parent must be a JSObject. Use package:web to retrieve/create one.'); + parent as JSObject; + if (options == null) { + return _renderButton(parent); + } + return _renderButtonWithOptions(parent, options); + } + + @JS('renderButton') + external void _renderButton(JSObject parent); + @JS('renderButton') + external void _renderButtonWithOptions( + JSObject parent, GsiButtonConfiguration options); /// Record when the user signs out of your website in cookies. /// @@ -112,7 +135,18 @@ extension GoogleAccountsIdExtension on GoogleAccountsId { /// /// 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]); + void storeCredential(Credential credential, [VoidFn? callback]) { + if (callback == null) { + return _jsStoreCredential(credential); + } + return _jsStoreCredentialWithCallback(credential, callback.toJS); + } + + @JS('storeCredential') + external void _jsStoreCredential(Credential credential); + @JS('storeCredential') + external void _jsStoreCredentialWithCallback( + Credential credential, JSFunction callback); /// Cancels the One Tap flow. /// @@ -132,12 +166,19 @@ extension GoogleAccountsIdExtension on GoogleAccountsId { /// 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]); + void revoke(String hint, [RevocationResponseHandlerFn? callback]) { + if (callback == null) { + return _revoke(hint.toJS); + } + return _revokeWithCallback(hint.toJS, callback.toJS); + } + + @JS('revoke') + external void _revoke(JSString hint); + @JS('revoke') + external void _revokeWithCallback(JSString hint, JSFunction callback); } /// The configuration object for the [initialize] method. @@ -149,11 +190,7 @@ extension GoogleAccountsIdExtension on GoogleAccountsId { @staticInterop abstract class IdConfiguration { /// Constructs a IdConfiguration object in JavaScript. - /// - /// The following properties need to be manually wrapped in [allowInterop] - /// before being passed to this constructor: [callback], [native_callback], - /// and [intermediate_iframe_close_callback]. - external factory IdConfiguration({ + factory IdConfiguration({ /// Your application's client ID, which is found and created in the Google /// Developers Console. required String client_id, @@ -228,7 +265,7 @@ abstract class IdConfiguration { /// intermediate iframe mode. And it has impact only to the intermediate /// iframe, instead of the One Tap iframe. The One Tap UI is removed before /// the callback is invoked. - Function? intermediate_iframe_close_callback, + VoidFn? intermediate_iframe_close_callback, /// Determines if the upgraded One Tap UX should be enabled on browsers /// that support Intelligent Tracking Prevention (ITP). The default value @@ -264,6 +301,49 @@ abstract class IdConfiguration { /// Allow the browser to control user sign-in prompts and mediate the /// sign-in flow between your website and Google. Defaults to false. bool? use_fedcm_for_prompt, + }) { + return IdConfiguration._toJS( + client_id: client_id.toJS, + auto_select: auto_select?.toJS, + callback: callback?.toJS, + login_uri: login_uri?.toString().toJS, + native_callback: native_callback?.toJS, + cancel_on_tap_outside: cancel_on_tap_outside?.toJS, + prompt_parent_id: prompt_parent_id?.toJS, + nonce: nonce?.toJS, + context: context?.toString().toJS, + state_cookie_domain: state_cookie_domain?.toJS, + ux_mode: ux_mode?.toString().toJS, + allowed_parent_origin: + allowed_parent_origin?.map((String s) => s.toJS).toList().toJS, + intermediate_iframe_close_callback: + intermediate_iframe_close_callback?.toJS, + itp_support: itp_support?.toJS, + login_hint: login_hint?.toJS, + hd: hd?.toJS, + use_fedcm_for_prompt: use_fedcm_for_prompt?.toJS, + ); + } + + // `IdConfiguration`'s external factory, defined as JSTypes. This is the actual JS-interop bit. + external factory IdConfiguration._toJS({ + JSString? client_id, + JSBoolean? auto_select, + JSFunction? callback, + JSString? login_uri, + JSFunction? native_callback, + JSBoolean? cancel_on_tap_outside, + JSString? prompt_parent_id, + JSString? nonce, + JSString? context, + JSString? state_cookie_domain, + JSString? ux_mode, + JSArray? allowed_parent_origin, + JSFunction? intermediate_iframe_close_callback, + JSBoolean? itp_support, + JSString? login_hint, + JSString? hd, + JSBoolean? use_fedcm_for_prompt, }); } @@ -281,42 +361,53 @@ abstract class PromptMomentNotification {} /// The methods of the [PromptMomentNotification] data type: extension PromptMomentNotificationExtension on PromptMomentNotification { /// Is this notification for a display moment? - external bool isDisplayMoment(); + bool isDisplayMoment() => _isDisplayMoment().toDart; + @JS('isDisplayMoment') + external JSBoolean _isDisplayMoment(); /// Is this notification for a display moment, and the UI is displayed? - external bool isDisplayed(); + bool isDisplayed() => _isDisplayed().toDart; + @JS('isDisplayed') + external JSBoolean _isDisplayed(); /// Is this notification for a display moment, and the UI isn't displayed? - external bool isNotDisplayed(); + bool isNotDisplayed() => _isNotDisplayed().toDart; + @JS('isNotDisplayed') + external JSBoolean _isNotDisplayed(); /// Is this notification for a skipped moment? - external bool isSkippedMoment(); + bool isSkippedMoment() => _isSkippedMoment().toDart; + @JS('isSkippedMoment') + external JSBoolean _isSkippedMoment(); /// Is this notification for a dismissed moment? - external bool isDismissedMoment(); - @JS('getMomentType') - external String _getMomentType(); - @JS('getNotDisplayedReason') - external String? _getNotDisplayedReason(); - @JS('getSkippedReason') - external String? _getSkippedReason(); - @JS('getDismissedReason') - external String? _getDismissedReason(); + bool isDismissedMoment() => _isDismissedMoment().toDart; + @JS('isDismissedMoment') + external JSBoolean _isDismissedMoment(); /// The moment type. - MomentType getMomentType() => MomentType.values.byName(_getMomentType()); + MomentType getMomentType() => + MomentType.values.byName(_getMomentType().toDart); + @JS('getMomentType') + external JSString _getMomentType(); /// The detailed reason why the UI isn't displayed. - MomentNotDisplayedReason? getNotDisplayedReason() => - maybeEnum(_getNotDisplayedReason(), MomentNotDisplayedReason.values); + MomentNotDisplayedReason? getNotDisplayedReason() => maybeEnum( + _getNotDisplayedReason()?.toDart, MomentNotDisplayedReason.values); + @JS('getNotDisplayedReason') + external JSString? _getNotDisplayedReason(); /// The detailed reason for the skipped moment. MomentSkippedReason? getSkippedReason() => - maybeEnum(_getSkippedReason(), MomentSkippedReason.values); + maybeEnum(_getSkippedReason()?.toDart, MomentSkippedReason.values); + @JS('getSkippedReason') + external JSString? _getSkippedReason(); /// The detailed reason for the dismissal. MomentDismissedReason? getDismissedReason() => - maybeEnum(_getDismissedReason(), MomentDismissedReason.values); + maybeEnum(_getDismissedReason()?.toDart, MomentDismissedReason.values); + @JS('getDismissedReason') + external JSString? _getDismissedReason(); } /// The object passed as the parameter of your [CallbackFn]. @@ -330,21 +421,27 @@ 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; + String? get client_id => _client_id?.toDart; + @JS('client_id') + external JSString? get _client_id; /// Error while signing in. - external String? get error; + String? get error => _error?.toDart; + @JS('error') + external JSString? get _error; /// Details of the error while signing in. - external String? get error_detail; + String? get error_detail => _error_detail?.toDart; + @JS('error_detail') + external JSString? 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; - @JS('select_by') - external String? get _select_by; + String? get credential => _credential?.toDart; + @JS('credential') + external JSString? get _credential; /// This field sets how the credential was selected. /// @@ -353,7 +450,9 @@ extension CredentialResponseExtension on CredentialResponse { /// /// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#select_by CredentialSelectBy? get select_by => - maybeEnum(_select_by, CredentialSelectBy.values); + maybeEnum(_select_by?.toDart, CredentialSelectBy.values); + @JS('select_by') + external JSString? get _select_by; } /// The type of the `callback` used to create an [IdConfiguration]. @@ -374,41 +473,63 @@ typedef CallbackFn = void Function(CredentialResponse credentialResponse); @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({ + factory GsiButtonConfiguration({ /// The button type. - ButtonType type, + ButtonType? type, /// The button theme. - ButtonTheme theme, + ButtonTheme? theme, /// The button size. - ButtonSize size, + ButtonSize? size, /// The button text. - ButtonText text, + ButtonText? text, /// The button shape. - ButtonShape shape, + ButtonShape? shape, /// The Google logo alignment in the button. - ButtonLogoAlignment logo_alignment, + ButtonLogoAlignment? logo_alignment, /// The minimum button width, in pixels. /// /// The maximum width is 400 pixels. - double width, + 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, + String? locale, /// A function to be called when the button is clicked. - GsiButtonClickListenerFn click_listener, + GsiButtonClickListenerFn? click_listener, + }) { + return GsiButtonConfiguration._toJS( + type: type.toString().toJS, + theme: theme.toString().toJS, + size: size.toString().toJS, + text: text?.toString().toJS, + shape: shape?.toString().toJS, + logo_alignment: logo_alignment?.toString().toJS, + width: width?.toJS, + locale: locale?.toJS, + click_listener: click_listener?.toJS, + ); + } + + // `GsiButtonConfiguration`'s external factory, defined as JSTypes. + external factory GsiButtonConfiguration._toJS({ + JSString? type, + JSString? theme, + JSString? size, + JSString? text, + JSString? shape, + JSString? logo_alignment, + JSNumber? width, + JSString? locale, + JSFunction? click_listener, }); } @@ -420,10 +541,14 @@ abstract class GsiButtonData {} /// The fields that are contained in the button data. extension GsiButtonDataExtension on GsiButtonData { /// Nonce - external String? get nonce; + String? get nonce => _nonce?.toDart; + @JS('nonce') + external JSString? get _nonce; /// State - external String? get state; + String? get state => _state?.toDart; + @JS('state') + external JSString? get _state; } /// The type of the [GsiButtonConfiguration] `click_listener` function. @@ -444,19 +569,32 @@ typedef GsiButtonClickListenerFn = void Function(GsiButtonData? gsiButtonData); @staticInterop abstract class Credential { /// - external factory Credential({ + factory Credential({ required String id, required String password, + }) => + Credential._toJS( + id: id.toJS, + password: password.toJS, + ); + + external factory Credential._toJS({ + JSString id, + JSString password, }); } /// The fields that are contained in the [Credential] object. extension CredentialExtension on Credential { /// Identifies the user. - external String get id; + String? get id => _id.toDart; + @JS('id') + external JSString get _id; /// The password. - external String get password; + String? get password => _password.toDart; + @JS('password') + external JSString get _password; } /// The type of the `native_callback` used to create an [IdConfiguration]. @@ -489,9 +627,13 @@ abstract class RevocationResponse {} extension RevocationResponseExtension on RevocationResponse { /// This field is a boolean value set to true if the revoke method call /// succeeded or false on failure. - external bool get successful; + bool get successful => _successful.toDart; + @JS('successful') + external JSBoolean get _successful; /// 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; + String? get error => _error?.toDart; + @JS('error') + external JSString? get _error; } 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 0bd842b3e06..a41b123b65d 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 @@ -204,7 +204,10 @@ enum CredentialSelectBy { /// A user without an existing session first pressed the Sign In With Google /// button to select a Google Account and then pressed the Confirm button to /// consent and share credentials. - btn_confirm_add_session('btn_confirm_add_session'); + btn_confirm_add_session('btn_confirm_add_session'), + + /// A user with an existing session used the browser's "FedCM" flow. + fedcm('fedcm'); /// const CredentialSelectBy(String selectBy) : _selectBy = selectBy; From d2c16e9c03ec0d8b8acf8c4233499658d6953d65 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 05:59:00 -0800 Subject: [PATCH 06/22] Migrate oauth2 lib. --- .../js_interop/google_accounts_oauth2.dart | 306 +++++++++++++----- 1 file changed, 230 insertions(+), 76 deletions(-) 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 83061d0b9de..c510068c0ab 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 @@ -12,9 +12,8 @@ @JS() library google_accounts_oauth2; -import 'package:js/js.dart'; +import 'dart:js_interop'; -import 'dom.dart'; import 'shared.dart'; /// Binding to the `google.accounts.oauth2` JS global. @@ -45,7 +44,7 @@ extension GoogleAccountsOauth2Extension on GoogleAccountsOauth2 { // 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); + external bool _hasGrantedScope(TokenResponse token, JSString scope); /// Checks if hte user has granted **all** the specified [scopes]. /// @@ -55,7 +54,7 @@ extension GoogleAccountsOauth2Extension on GoogleAccountsOauth2 { /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes bool hasGrantedAllScopes(TokenResponse tokenResponse, List scopes) { return scopes - .every((String scope) => _hasGrantedScope(tokenResponse, scope)); + .every((String scope) => _hasGrantedScope(tokenResponse, scope.toJS)); } /// Checks if hte user has granted **all** the specified [scopes]. @@ -65,7 +64,8 @@ extension GoogleAccountsOauth2Extension on GoogleAccountsOauth2 { /// Method: google.accounts.oauth2.hasGrantedAllScopes /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes bool hasGrantedAnyScopes(TokenResponse tokenResponse, List scopes) { - return scopes.any((String scope) => _hasGrantedScope(tokenResponse, scope)); + return scopes + .any((String scope) => _hasGrantedScope(tokenResponse, scope.toJS)); } /// Revokes all of the scopes that the user granted to the app. @@ -77,10 +77,20 @@ extension GoogleAccountsOauth2Extension on GoogleAccountsOauth2 { /// /// Method: google.accounts.oauth2.revoke /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke - external void revoke( + void revoke( String accessToken, [ - RevokeTokenDoneFn done, - ]); + RevokeTokenDoneFn? done, + ]) { + if (done == null) { + return _revoke(accessToken.toJS); + } + return _revokeWithDone(accessToken.toJS, done.toJS); + } + + @JS('revoke') + external void _revoke(JSString accessToken); + @JS('revoke') + external void _revokeWithDone(JSString accessToken, JSFunction done); } /// The configuration object for the [initCodeClient] method. @@ -95,19 +105,54 @@ abstract class CodeClientConfig { /// /// The [callback] property must be wrapped in [allowInterop] before it's /// passed to this constructor. - external factory CodeClientConfig({ + factory CodeClientConfig({ required String client_id, - required String scope, - String? redirect_uri, - bool? auto_select, + required List scope, + bool? include_granted_scopes, + Uri? redirect_uri, CodeClientCallbackFn? callback, - ErrorCallbackFn? error_callback, String? state, + bool? enable_granular_consent, + @Deprecated('Use `enable_granular_consent` instead.') bool? enable_serial_consent, - String? hint, - String? hosted_domain, + String? login_hint, + String? hd, UxMode? ux_mode, bool? select_account, + ErrorCallbackFn? error_callback, + }) { + assert(scope.isNotEmpty); + return CodeClientConfig._toJS( + client_id: client_id.toJS, + scope: scope.join(' ').toJS, + include_granted_scopes: include_granted_scopes?.toJS, + redirect_uri: redirect_uri?.toString().toJS, + callback: callback?.toJS, + state: state?.toJS, + enable_granular_consent: enable_granular_consent?.toJS, + enable_serial_consent: enable_serial_consent?.toJS, + login_hint: login_hint?.toJS, + hd: hd?.toJS, + ux_mode: ux_mode.toString().toJS, + select_account: select_account?.toJS, + error_callback: error_callback?.toJS, + ); + } + + external factory CodeClientConfig._toJS({ + JSString? client_id, + JSString? scope, + JSBoolean? include_granted_scopes, + JSString? redirect_uri, + JSFunction? callback, + JSString? state, + JSBoolean? enable_granular_consent, + JSBoolean? enable_serial_consent, + JSString? login_hint, + JSString? hd, + JSString? ux_mode, + JSBoolean? select_account, + JSFunction? error_callback, }); } @@ -138,26 +183,38 @@ abstract class CodeResponse {} /// The fields that are contained in the code response object. extension CodeResponseExtension on CodeResponse { /// The authorization code of a successful token response. - external String get code; + String? get code => _code?.toDart; + @JS('code') + external JSString? get _code; - /// A space-delimited list of scopes that are approved by the user. - external String get scope; + /// A list of scopes that are approved by the user. + List get scope => _scope?.toDart.split(' ') ?? List.empty(); + @JS('scope') + external JSString? get _scope; /// The string value that your application uses to maintain state between your /// authorization request and the response. - external String get state; + String? get state => _state?.toDart; + @JS('state') + external JSString? get _state; /// A single ASCII error code. - external String? get error; + String? get error => _error?.toDart; + @JS('error') + external JSString? get _error; /// Human-readable ASCII text providing additional information, used to assist /// the client developer in understanding the error that occurred. - external String? get error_description; + String? get error_description => _error_description?.toDart; + @JS('error_description') + external JSString? get _error_description; /// A URI identifying a human-readable web page with information about the /// error, used to provide the client developer with additional information /// about the error. - external String? get error_uri; + String? get error_uri => _error_uri?.toDart; + @JS('error_uri') + external JSString? get _error_uri; } /// The type of the `callback` function passed to [CodeClientConfig]. @@ -175,16 +232,47 @@ abstract class TokenClientConfig { /// /// The [callback] property must be wrapped in [allowInterop] before it's /// passed to this constructor. - external factory TokenClientConfig({ + factory TokenClientConfig({ required String client_id, - required TokenClientCallbackFn? callback, - required String scope, - ErrorCallbackFn? error_callback, + required TokenClientCallbackFn callback, + List? scope, + bool? include_granted_scopes, String? prompt, + bool? enable_granular_consent, + @Deprecated('Use `enable_granular_consent` instead.') bool? enable_serial_consent, - String? hint, - String? hosted_domain, + String? login_hint, + String? hd, String? state, + ErrorCallbackFn? error_callback, + }) { + return TokenClientConfig._toJS( + client_id: client_id.toJS, + callback: callback.toJS, + scope: scope?.join(' ').toJS, + include_granted_scopes: include_granted_scopes?.toJS, + prompt: prompt?.toJS, + enable_granular_consent: enable_granular_consent?.toJS, + enable_serial_consent: enable_serial_consent?.toJS, + login_hint: login_hint?.toJS, + hd: hd?.toJS, + state: state?.toJS, + error_callback: error_callback?.toJS, + ); + } + + external factory TokenClientConfig._toJS({ + JSString? client_id, + JSFunction? callback, + JSString? scope, + JSBoolean? include_granted_scopes, + JSString? prompt, + JSBoolean? enable_granular_consent, + JSBoolean? enable_serial_consent, + JSString? login_hint, + JSString? hd, + JSString? state, + JSFunction? error_callback, }); } @@ -201,9 +289,20 @@ abstract class TokenClient {} /// The methods available on the [TokenClient]. extension TokenClientExtension on TokenClient { /// Starts the OAuth 2.0 Code UX flow. - external void requestAccessToken([ - OverridableTokenClientConfig overrideConfig, - ]); + void requestAccessToken([ + OverridableTokenClientConfig? overrideConfig, + ]) { + if (overrideConfig == null) { + return _requestAccessToken(); + } + return _requestAccessTokenWithConfig(overrideConfig); + } + + @JS('requestAccessToken') + external void _requestAccessToken(); + @JS('requestAccessToken') + external void _requestAccessTokenWithConfig( + OverridableTokenClientConfig config); } /// The overridable configuration object for the [TokenClientExtension.requestAccessToken] method. @@ -218,18 +317,42 @@ abstract class OverridableTokenClientConfig { /// /// The [callback] property must be wrapped in [allowInterop] before it's /// passed to this constructor. - external factory OverridableTokenClientConfig({ + factory OverridableTokenClientConfig({ + /// A 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 + List? scope, + + /// Enables applications to use incremental authorization to request access + /// to additional scopes in context. If you set this parameter's value to + /// `false` and the authorization request is granted, then the new access + /// token will only cover any scopes to which the `scope` requested in this + /// [OverridableTokenClientConfig]. + bool? include_granted_scopes, + /// A space-delimited, case-sensitive list of prompts to present the user. /// /// See `prompt` in [TokenClientConfig]. String? prompt, - /// For clients created before 2019, when set to `false`, disables "more - /// granular Google Account permissions". + /// If set to false, "more granular Google Account permissions" would be + /// disabled for OAuth client IDs created before 2019. If both + /// `enable_granular_consent` and `enable_serial_consent` are set, only + /// `enable_granular_consent` value would take effect and + /// `enable_serial_consent` value would be ignored. /// - /// This setting has no effect in newer clients. + /// No effect for newer OAuth client IDs, since more granular permissions is + /// always enabled for them. + bool? enable_granular_consent, + + /// This has the same effect as `enable_granular_consent`. Existing + /// applications that use `enable_serial_consent` can continue to do so, but + /// you are encouraged to update your code to use `enable_granular_consent` + /// in your next application update. /// /// See: https://developers.googleblog.com/2018/10/more-granular-google-account.html + @Deprecated('Use `enable_granular_consent` instead.') bool? enable_serial_consent, /// When your app knows which user it is trying to authenticate, it can @@ -243,23 +366,32 @@ abstract class OverridableTokenClientConfig { /// equivalent to the user's Google ID. /// /// 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, + String? login_hint, /// **Not recommended.** Specifies any string value that your application /// uses to maintain state between your authorization request and the /// authorization server's response. String? state, + }) { + return OverridableTokenClientConfig._toJS( + scope: scope?.join(' ').toJS, + include_granted_scopes: include_granted_scopes?.toJS, + prompt: prompt?.toJS, + enable_granular_consent: enable_granular_consent?.toJS, + enable_serial_consent: enable_serial_consent?.toJS, + login_hint: login_hint?.toJS, + state: state?.toJS, + ); + } - /// Preserves previously requested scopes in this new request. - /// - /// (Undocumented) - bool? include_granted_scopes, + external factory OverridableTokenClientConfig._toJS({ + JSString? scope, + JSBoolean? include_granted_scopes, + JSString? prompt, + JSBoolean? enable_granular_consent, + JSBoolean? enable_serial_consent, + JSString? login_hint, + JSString? state, }); } @@ -274,70 +406,86 @@ abstract class TokenResponse {} /// The fields that are contained in the code response object. extension TokenResponseExtension on TokenResponse { /// The access token of a successful token response. - external String get access_token; + String? get access_token => _access_token?.toDart; + @JS('access_token') + external JSString? get _access_token; /// The lifetime in seconds of the access token. - external int get expires_in; + int? get expires_in => _expires_in?.toDartInt; + @JS('expires_in') + external JSNumber? get _expires_in; /// The hosted domain the signed-in user belongs to. - external String get hd; + String? get hd => _hd?.toDart; + @JS('hd') + external JSString? get _hd; /// The prompt value that was used from the possible list of values specified /// by [TokenClientConfig] or [OverridableTokenClientConfig]. - external String get prompt; + String? get prompt => _prompt?.toDart; + @JS('prompt') + external JSString? get _prompt; /// The type of the token issued. - external String get token_type; + String? get token_type => _token_type?.toDart; + @JS('token_type') + external JSString? get _token_type; - /// A space-delimited list of scopes that are approved by the user. - external String get scope; + /// A list of scopes that are approved by the user. + List get scope => _scope?.toDart.split(' ') ?? List.empty(); + @JS('scope') + external JSString? get _scope; /// The string value that your application uses to maintain state between your /// authorization request and the response. - external String get state; + String? get state => _state?.toDart; + @JS('state') + external JSString? get _state; /// A single ASCII error code. - external String? get error; + String? get error => _error?.toDart; + @JS('error') + external JSString? get _error; /// Human-readable ASCII text providing additional information, used to assist /// the client developer in understanding the error that occurred. - external String? get error_description; + String? get error_description => _error_description?.toDart; + @JS('error_description') + external JSString? get _error_description; /// A URI identifying a human-readable web page with information about the /// error, used to provide the client developer with additional information /// about the error. - external String? get error_uri; + String? get error_uri => _error_uri?.toDart; + @JS('error_uri') + external JSString? get _error_uri; } /// The type of the `callback` function passed to [TokenClientConfig]. typedef TokenClientCallbackFn = void Function(TokenResponse response); /// 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); +typedef ErrorCallbackFn = void Function(GoogleIdentityServicesError? error); /// An error returned by `initTokenClient` or `initDataClient`. -/// -/// Cannot be used: https://github.com/dart-lang/sdk/issues/50899 @JS() @staticInterop -abstract class GoogleIdentityServicesError extends DomError {} +abstract class GoogleIdentityServicesError {} /// 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(this, 'type'); - /// The type of error GoogleIdentityServicesErrorType get type => - GoogleIdentityServicesErrorType.values.byName(_type); + GoogleIdentityServicesErrorType.values.byName(_type.toDart); + @JS('type') + external JSString get _type; + + /// A human-readable description of the error `type`. + /// + /// (Undocumented) + String? get message => _message?.toDart; + @JS('message') + external JSString? get _message; } /// The signature of the `done` function for [revoke]. @@ -355,12 +503,18 @@ abstract class TokenRevocationResponse {} 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; + bool get successful => _successful.toDart; + @JS('successful') + external JSBoolean get _successful; /// 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; + String? get error => _error?.toDart; + @JS('error') + external JSString? get _error; /// The description of the error. - external String? get error_description; + String? get error_description => _error_description?.toDart; + @JS('error_description') + external JSString? get _error_description; } From 2e10ba13ffcc627601514702110bd8427ec6b75a Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 06:02:31 -0800 Subject: [PATCH 07/22] Update example apps --- .../example/lib/main.dart | 8 +++---- .../example/lib/main_oauth.dart | 22 +++++++++---------- .../example/pubspec.yaml | 1 - 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/google_identity_services_web/example/lib/main.dart b/packages/google_identity_services_web/example/lib/main.dart index a0b613ddb17..50243d56d3a 100644 --- a/packages/google_identity_services_web/example/lib/main.dart +++ b/packages/google_identity_services_web/example/lib/main.dart @@ -8,7 +8,6 @@ import 'package:google_identity_services_web/id.dart'; // #docregion use-loader import 'package:google_identity_services_web/loader.dart' as gis; // #enddocregion use-loader -import 'package:js/js.dart' show allowInterop; import 'src/jwt.dart' as jwt; @@ -20,13 +19,14 @@ void main() async { id.setLogLevel('debug'); final IdConfiguration config = IdConfiguration( - client_id: 'your-client_id.apps.googleusercontent.com', - callback: allowInterop(onCredentialResponse), + client_id: + '451137223537-t8qp8tl992tgqj5focnc6mh8cp2psn8g.apps.googleusercontent.com', + callback: onCredentialResponse, use_fedcm_for_prompt: true, ); id.initialize(config); - id.prompt(allowInterop(onPromptMoment)); + id.prompt(onPromptMoment); // #docregion use-loader } // #enddocregion use-loader 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 c54ab4cc606..2ce4e2216a4 100644 --- a/packages/google_identity_services_web/example/lib/main_oauth.dart +++ b/packages/google_identity_services_web/example/lib/main_oauth.dart @@ -10,8 +10,6 @@ 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'; import 'package:http/http.dart' as http; -import 'package:js/js.dart' show allowInterop; -import 'package:js/js_util.dart' show getProperty; /// People API to return my profile info... const String MY_PROFILE = @@ -38,15 +36,16 @@ void main() async { id.setLogLevel('debug'); final TokenClientConfig config = TokenClientConfig( - client_id: 'your-client_id.apps.googleusercontent.com', - scope: scopes.join(' '), - callback: allowInterop(onTokenResponse), - error_callback: allowInterop(onError), + client_id: + '451137223537-t8qp8tl992tgqj5focnc6mh8cp2psn8g.apps.googleusercontent.com', + scope: scopes, + callback: onTokenResponse, + error_callback: onError, ); final OverridableTokenClientConfig overridableCfg = OverridableTokenClientConfig( - scope: (scopes + myConnectionsScopes).join(' '), + scope: scopes + myConnectionsScopes, ); final TokenClient client = oauth2.initTokenClient(config); @@ -59,8 +58,8 @@ void main() async { /// /// We cannot use the proper type for `error` here yet, because of: /// https://github.com/dart-lang/sdk/issues/50899 -Future onError(Object? error) async { - print('Error! ${getProperty(error!, "type")}'); +Future onError(GoogleIdentityServicesError? error) async { + print('Error! ${error?.type} (${error?.message})'); } /// Handles the returned (auth) token response. @@ -88,12 +87,11 @@ Future onTokenResponse(TokenResponse token) async { print(contacts); print('Revoking token...'); - oauth2.revoke(token.access_token, - allowInterop((TokenRevocationResponse response) { + oauth2.revoke(token.access_token!, (TokenRevocationResponse response) { print(response.successful); print(response.error); print(response.error_description); - })); + }); } /// Gets from [url] with an authorization header defined by [token]. diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml index c2b0fd76872..b38a491c4fc 100644 --- a/packages/google_identity_services_web/example/pubspec.yaml +++ b/packages/google_identity_services_web/example/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: google_identity_services_web: path: ../ http: ">=0.13.0 <2.0.0" - js: ^0.6.4 dev_dependencies: build_runner: ^2.1.10 # To extract README excerpts only. From 5c3fb76713cf8c71de4df1de0f3f1146d608d1eb Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 06:26:28 -0800 Subject: [PATCH 08/22] Migrate dart test tests. --- .../test/js_loader_test.dart | 47 ++++++++----------- .../test/js_loader_tt_custom_test.dart | 6 +-- .../test/js_loader_tt_forbidden_test.dart | 6 +-- .../test/tools.dart | 16 +++---- 4 files changed, 32 insertions(+), 43 deletions(-) diff --git a/packages/google_identity_services_web/test/js_loader_test.dart b/packages/google_identity_services_web/test/js_loader_test.dart index 028f5fd602a..ec2c07428a5 100644 --- a/packages/google_identity_services_web/test/js_loader_test.dart +++ b/packages/google_identity_services_web/test/js_loader_test.dart @@ -2,17 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -@TestOn('browser') // Uses package:js +@TestOn('browser') // Uses package:web import 'dart:async'; +import 'dart:js_interop'; import 'package:google_identity_services_web/loader.dart'; -import 'package:google_identity_services_web/src/js_interop/dom.dart' as dom; import 'package:google_identity_services_web/src/js_loader.dart'; - -import 'package:js/js_util.dart' as js_util; - import 'package:test/test.dart'; +import 'package:web/web.dart' as web; // NOTE: This file needs to be separated from the others because Content // Security Policies can never be *relaxed* once set. @@ -26,7 +24,8 @@ import 'package:test/test.dart'; void main() { group('loadWebSdk (no TrustedTypes)', () { - final dom.DomHtmlElement target = dom.document.createElement('div'); + final web.HTMLDivElement target = + web.document.createElement('div') as web.HTMLDivElement; test('Injects script into desired target', () async { // This test doesn't simulate the callback that completes the future, and @@ -34,22 +33,14 @@ void main() { unawaited(loadWebSdk(target: target)); // Target now should have a child that is a script element - final Object children = js_util.getProperty(target, 'children'); - final Object injected = js_util.callMethod( - children, - 'item', - [0], - ); - expect(injected, isA()); - - final dom.DomHtmlScriptElement script = - injected as dom.DomHtmlScriptElement; - expect(js_util.getProperty(script, 'defer'), isTrue); - expect(js_util.getProperty(script, 'async'), isTrue); - expect( - js_util.getProperty(script, 'src'), - 'https://accounts.google.com/gsi/client', - ); + final web.Node? injected = target.firstChild; + expect(injected, isNotNull); + expect(injected, isA()); + + final web.HTMLScriptElement script = injected! as web.HTMLScriptElement; + expect(script.defer, isTrue); + expect(script.async, isTrue); + expect(script.src, 'https://accounts.google.com/gsi/client'); }); test('Completes when the script loads', () async { @@ -57,14 +48,16 @@ void main() { Future.delayed(const Duration(milliseconds: 100), () { // Simulate the library calling `window.onGoogleLibraryLoad`. - js_util.callMethod( - js_util.globalThis, - 'onGoogleLibraryLoad', - [], - ); + web.window.onGoogleLibraryLoad(); }); await expectLater(loadFuture, completes); }); }); } + +extension on web.Window { + void onGoogleLibraryLoad() => _onGoogleLibraryLoad(); + @JS('onGoogleLibraryLoad') + external JSFunction? _onGoogleLibraryLoad(); +} diff --git a/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart b/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart index 6668be09d02..545f8d71cf9 100644 --- a/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart +++ b/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart @@ -5,9 +5,8 @@ @TestOn('browser') // Uses package:js import 'package:google_identity_services_web/loader.dart'; -import 'package:google_identity_services_web/src/js_interop/dom.dart' as dom; - import 'package:test/test.dart'; +import 'package:web/web.dart' as web; import 'tools.dart'; @@ -23,7 +22,8 @@ import 'tools.dart'; void main() { group('loadWebSdk (TrustedTypes configured)', () { - final dom.DomHtmlElement target = dom.document.createElement('div'); + final web.HTMLDivElement target = + web.document.createElement('div') as web.HTMLDivElement; injectMetaTag({ 'http-equiv': 'Content-Security-Policy', 'content': "trusted-types my-custom-policy-name 'allow-duplicates';", diff --git a/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart b/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart index e95b5f9e298..36d60961975 100644 --- a/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart +++ b/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart @@ -5,9 +5,8 @@ @TestOn('browser') // Uses package:js import 'package:google_identity_services_web/loader.dart'; -import 'package:google_identity_services_web/src/js_interop/dom.dart' as dom; - import 'package:test/test.dart'; +import 'package:web/web.dart' as web; import 'tools.dart'; @@ -23,7 +22,8 @@ import 'tools.dart'; void main() { group('loadWebSdk (TrustedTypes forbidden)', () { - final dom.DomHtmlElement target = dom.document.createElement('div'); + final web.HTMLDivElement target = + web.document.createElement('div') as web.HTMLDivElement; injectMetaTag({ 'http-equiv': 'Content-Security-Policy', 'content': "trusted-types 'none';", diff --git a/packages/google_identity_services_web/test/tools.dart b/packages/google_identity_services_web/test/tools.dart index dd3d68e34ef..3ae00be0fdb 100644 --- a/packages/google_identity_services_web/test/tools.dart +++ b/packages/google_identity_services_web/test/tools.dart @@ -2,18 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:google_identity_services_web/src/js_interop/dom.dart' as dom; -import 'package:js/js_util.dart' as js_util; +import 'package:web/web.dart' as web; -/// Injects a `` tag with the provided [attributes] into the [dom.document]. +/// Injects a `` tag with the provided [attributes] into the [web.document]. void injectMetaTag(Map attributes) { - final dom.DomHtmlElement meta = dom.document.createElement('meta'); + final web.HTMLMetaElement meta = + web.document.createElement('meta') as web.HTMLMetaElement; for (final MapEntry attribute in attributes.entries) { - js_util.callMethod( - meta, - 'setAttribute', - [attribute.key, attribute.value], - ); + meta.setAttribute(attribute.key, attribute.value); } - dom.document.head.appendChild(meta); + web.document.head!.appendChild(meta); } From f9446db32a12c6dc62b5e6c230225a4856579171 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 16:06:58 -0800 Subject: [PATCH 09/22] Migrate integration tests to new APIs --- .../integration_test/js_interop_id_test.dart | 53 ++++----- .../js_interop_oauth_test.dart | 104 ++++++++++++++++-- .../example/integration_test/utils.dart | 82 ++++++++------ .../example/pubspec.yaml | 1 + 4 files changed, 173 insertions(+), 67 deletions(-) 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 index b7e911f60fa..f676a226e25 100644 --- 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 @@ -3,14 +3,13 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:js_interop'; 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 'package:js/js_util.dart' as js_util show getProperty; +import 'package:web/web.dart' as web; -import 'src/dom.dart'; import 'utils.dart' as utils; void main() async { @@ -23,11 +22,12 @@ void main() async { group('renderButton', () { testWidgets('supports a js-interop target from any library', (_) async { - final DomElement target = createDomElement('div'); + final web.HTMLDivElement target = + web.document.createElement('div') as web.HTMLDivElement; id.renderButton(target); - final DomElement? button = target.querySelector('button'); + final web.Element? button = target.querySelector('button'); expect(button, isNotNull); }); }); @@ -37,9 +37,9 @@ void main() async { final IdConfiguration config = IdConfiguration( client_id: 'testing_1-2-3', auto_select: false, - callback: allowInterop((_) {}), + callback: (_) {}, login_uri: Uri.parse('https://www.example.com/login'), - native_callback: allowInterop((_) {}), + native_callback: (_) {}, cancel_on_tap_outside: false, prompt_parent_id: 'some_dom_id', nonce: 's0m3_r4ndOM_vALu3', @@ -47,35 +47,36 @@ void main() async { state_cookie_domain: 'subdomain.example.com', ux_mode: UxMode.popup, allowed_parent_origin: ['allowed', 'another'], - intermediate_iframe_close_callback: allowInterop((_) {}), + intermediate_iframe_close_callback: () {}, itp_support: true, login_hint: 'login-hint@example.com', hd: 'hd_value', use_fedcm_for_prompt: true, ); - // Save some keystrokes below by partially applying to the 'config' above. - void expectConfigValue(String name, Object? matcher) { - expect(js_util.getProperty(config, name), matcher, reason: name); - } + final utils.ExpectConfigValueFn expectConfigValue = + utils.createExpectConfigValue(config as JSObject); - expectConfigValue('allowed_parent_origin', hasLength(2)); + expectConfigValue('client_id', 'testing_1-2-3'); expectConfigValue('auto_select', isFalse); - expectConfigValue('callback', isA()); + expectConfigValue('callback', isA()); + expectConfigValue('login_uri', 'https://www.example.com/login'); + expectConfigValue('native_callback', isA()); expectConfigValue('cancel_on_tap_outside', isFalse); - expectConfigValue('client_id', 'testing_1-2-3'); - expectConfigValue('context', isA()); - expectConfigValue('hd', 'hd_value'); - expectConfigValue('intermediate_iframe_close_callback', isA()); - expectConfigValue('itp_support', isTrue); - expectConfigValue('login_hint', 'login-hint@example.com'); - expectConfigValue('login_uri', isA()); - expectConfigValue('native_callback', isA()); - expectConfigValue('nonce', 's0m3_r4ndOM_vALu3'); + expectConfigValue('allowed_parent_origin', isA()); expectConfigValue('prompt_parent_id', 'some_dom_id'); + expectConfigValue('nonce', 's0m3_r4ndOM_vALu3'); + expectConfigValue('context', 'signin'); expectConfigValue('state_cookie_domain', 'subdomain.example.com'); + expectConfigValue('ux_mode', 'popup'); + expectConfigValue( + 'allowed_parent_origin', ['allowed', 'another']); + expectConfigValue( + 'intermediate_iframe_close_callback', isA()); + expectConfigValue('itp_support', isTrue); + expectConfigValue('login_hint', 'login-hint@example.com'); + expectConfigValue('hd', 'hd_value'); expectConfigValue('use_fedcm_for_prompt', isTrue); - expectConfigValue('ux_mode', isA()); }); }); @@ -86,7 +87,7 @@ void main() async { final StreamController controller = StreamController(); - id.prompt(allowInterop(controller.add)); + id.prompt(controller.add); final PromptMomentNotification moment = await controller.stream.first; @@ -104,7 +105,7 @@ void main() async { id.initialize(IdConfiguration( client_id: 'testing_1-2-3', - callback: allowInterop(controller.add), + callback: controller.add, )); id.prompt(); 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 index 50a06676e48..3098a68cb7f 100644 --- 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 @@ -3,11 +3,11 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:js_interop'; 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; @@ -19,12 +19,96 @@ void main() async { await utils.installGisMock(); }); + group('Config objects pass values from Dart to JS - ', () { + testWidgets('TokenClientConfig', (_) async { + final TokenClientConfig config = TokenClientConfig( + client_id: 'testing_1-2-3', + callback: (_) {}, + scope: ['one', 'two', 'three'], + include_granted_scopes: true, + prompt: 'some-prompt', + enable_granular_consent: true, + login_hint: 'login-hint@example.com', + hd: 'hd_value', + state: 'some-state', + error_callback: (_) {}, + ); + + final utils.ExpectConfigValueFn expectConfigValue = + utils.createExpectConfigValue(config as JSObject); + + expectConfigValue('client_id', 'testing_1-2-3'); + expectConfigValue('callback', isA()); + expectConfigValue('scope', 'one two three'); + expectConfigValue('include_granted_scopes', isTrue); + expectConfigValue('prompt', 'some-prompt'); + expectConfigValue('enable_granular_consent', isTrue); + expectConfigValue('login_hint', 'login-hint@example.com'); + expectConfigValue('hd', 'hd_value'); + expectConfigValue('state', 'some-state'); + expectConfigValue('error_callback', isA()); + }); + + testWidgets('OverridableTokenClientConfig', (_) async { + final OverridableTokenClientConfig config = OverridableTokenClientConfig( + scope: ['one', 'two', 'three'], + include_granted_scopes: true, + prompt: 'some-prompt', + enable_granular_consent: true, + login_hint: 'login-hint@example.com', + state: 'some-state', + ); + + final utils.ExpectConfigValueFn expectConfigValue = + utils.createExpectConfigValue(config as JSObject); + + expectConfigValue('scope', 'one two three'); + expectConfigValue('include_granted_scopes', isTrue); + expectConfigValue('prompt', 'some-prompt'); + expectConfigValue('enable_granular_consent', isTrue); + expectConfigValue('login_hint', 'login-hint@example.com'); + expectConfigValue('state', 'some-state'); + }); + + testWidgets('CodeClientConfig', (_) async { + final CodeClientConfig config = CodeClientConfig( + client_id: 'testing_1-2-3', + scope: ['one', 'two', 'three'], + include_granted_scopes: true, + redirect_uri: Uri.parse('https://www.example.com/login'), + callback: (_) {}, + state: 'some-state', + enable_granular_consent: true, + login_hint: 'login-hint@example.com', + hd: 'hd_value', + ux_mode: UxMode.popup, + select_account: true, + error_callback: (_) {}, + ); + + final utils.ExpectConfigValueFn expectConfigValue = + utils.createExpectConfigValue(config as JSObject); + + expectConfigValue('scope', 'one two three'); + expectConfigValue('include_granted_scopes', isTrue); + expectConfigValue('redirect_uri', 'https://www.example.com/login'); + expectConfigValue('callback', isA()); + expectConfigValue('state', 'some-state'); + expectConfigValue('enable_granular_consent', isTrue); + expectConfigValue('login_hint', 'login-hint@example.com'); + expectConfigValue('hd', 'hd_value'); + expectConfigValue('ux_mode', 'popup'); + expectConfigValue('select_account', isTrue); + expectConfigValue('error_callback', isA()); + }); + }); + 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', + callback: (_) {}, + scope: ['some_scope', 'for_tests', 'not_real'], )); expect(client, isNotNull); @@ -40,8 +124,8 @@ void main() async { final TokenClient client = oauth2.initTokenClient(TokenClientConfig( client_id: 'for-tests', - callback: allowInterop(controller.add), - scope: scopes.join(' '), + callback: controller.add, + scope: scopes, )); utils.setMockTokenResponse(client, 'some-non-null-auth-token-value'); @@ -52,7 +136,7 @@ void main() async { expect(response, isNotNull); expect(response.error, isNull); - expect(response.scope, scopes.join(' ')); + expect(response.scope, scopes); }); testWidgets('configuration can be overridden', (_) async { @@ -63,21 +147,21 @@ void main() async { final TokenClient client = oauth2.initTokenClient(TokenClientConfig( client_id: 'for-tests', - callback: allowInterop(controller.add), - scope: 'blank', + callback: controller.add, + scope: ['blank'], )); utils.setMockTokenResponse(client, 'some-non-null-auth-token-value'); client.requestAccessToken(OverridableTokenClientConfig( - scope: scopes.join(' '), + scope: scopes, )); final TokenResponse response = await controller.stream.first; expect(response, isNotNull); expect(response.error, isNull); - expect(response.scope, scopes.join(' ')); + expect(response.scope, scopes); }); }); diff --git a/packages/google_identity_services_web/example/integration_test/utils.dart b/packages/google_identity_services_web/example/integration_test/utils.dart index 889a0eee328..bdfb70fc6d0 100644 --- a/packages/google_identity_services_web/example/integration_test/utils.dart +++ b/packages/google_identity_services_web/example/integration_test/utils.dart @@ -3,30 +3,43 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_identity_services_web/id.dart'; 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'; +import 'package:web/web.dart' as web; -@JS('window') -external Object get domWindow; +/// Function that lets us expect that a JSObject has a [name] property that matches [matcher]. +/// +/// Use [createExpectConfigValue] to create one of this functions associated with +/// a specific [JSObject]. +typedef ExpectConfigValueFn = void Function(String name, Object? matcher); + +/// Creates a [ExpectConfigValueFn] for the `config` [JSObject]. +ExpectConfigValueFn createExpectConfigValue(JSObject config) { + return (String name, Object? matcher) { + expect(config.getProperty(name.toJS), matcher, reason: name); + }; +} /// Installs mock-gis.js in the page. /// Returns a future that completes when the 'load' event of the script fires. Future installGisMock() { final Completer completer = Completer(); - final DomHtmlScriptElement script = - document.createElement('script') as DomHtmlScriptElement; + + final web.HTMLScriptElement script = + web.document.createElement('script') as web.HTMLScriptElement; script.src = 'mock-gis.js'; - setProperty(script, 'type', 'module'); - callMethod(script, 'addEventListener', [ - 'load', - allowInterop((_) { - completer.complete(); - }) - ]); - document.head.appendChild(script); + script.type = 'module'; + script.addEventListener( + 'load', + (JSAny? _) { + completer.complete(); + }.toJS); + + web.document.head!.appendChild(script); return completer.future; } @@ -36,41 +49,48 @@ Future fakeAuthZWithScopes(List scopes) { StreamController(); final TokenClient client = oauth2.initTokenClient(TokenClientConfig( client_id: 'for-tests', - callback: allowInterop(controller.add), - scope: scopes.join(' '), + callback: controller.add, + scope: scopes, )); setMockTokenResponse(client, 'some-non-null-auth-token-value'); client.requestAccessToken(); return controller.stream.first; } +/// Allows calling a `setMockTokenResponse` method (added by mock-gis.js) +extension on TokenClient { + external void setMockTokenResponse(JSString? token); +} + /// Sets a mock TokenResponse value in a [client]. void setMockTokenResponse(TokenClient client, [String? authToken]) { - callMethod( - client, - 'setMockTokenResponse', - [authToken], + client.setMockTokenResponse(authToken?.toJS); +} + +/// Allows calling a `setMockCredentialResponse` method (set by mock-gis.js) +extension on GoogleAccountsId { + external void setMockCredentialResponse( + JSString credential, + JSString select_by, //ignore: non_constant_identifier_names ); } /// Sets a mock credential response in `google.accounts.id`. void setMockCredentialResponse([String value = 'default_value']) { - callMethod( - _getGoogleAccountsId(), - 'setMockCredentialResponse', - [value, 'auto'], - ); + _getGoogleAccountsId().setMockCredentialResponse(value.toJS, 'auto'.toJS); } -Object _getGoogleAccountsId() { - return _getDeepProperty(domWindow, 'google.accounts.id'); +GoogleAccountsId _getGoogleAccountsId() { + return _getDeepProperty( + web.window as JSObject, 'google.accounts.id'); } // Attempts to retrieve a deeply nested property from a jsObject (or die tryin') -T _getDeepProperty(Object jsObject, String deepProperty) { +T _getDeepProperty(JSObject jsObject, String deepProperty) { final List properties = deepProperty.split('.'); - return properties.fold( + return properties.fold( jsObject, - (Object jsObj, String prop) => getProperty(jsObj, prop), + (JSObject? jsObj, String prop) => + jsObj?.getProperty(prop.toJS) as JSObject?, ) as T; } diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml index b38a491c4fc..e7ddb7f1ac5 100644 --- a/packages/google_identity_services_web/example/pubspec.yaml +++ b/packages/google_identity_services_web/example/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: google_identity_services_web: path: ../ http: ">=0.13.0 <2.0.0" + web: ^0.4.0 dev_dependencies: build_runner: ^2.1.10 # To extract README excerpts only. From f6969c4abdaf2764a21d83e68e5d3eacd79d05b6 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 16:08:11 -0800 Subject: [PATCH 10/22] Update changelog and pubspec. v0.3.0. --- .../google_identity_services_web/CHANGELOG.md | 33 ++++++++++++++++++- .../google_identity_services_web/pubspec.yaml | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md index 4079f59280c..450f445d1b1 100644 --- a/packages/google_identity_services_web/CHANGELOG.md +++ b/packages/google_identity_services_web/CHANGELOG.md @@ -1,6 +1,37 @@ -## NEXT +## 0.3.0 * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. +* Migrated from to `package:web` so this package can compile to WASM. +* **Breaking API changes** and fixes to align with the GIS SDK: + * **Removed the need to explicitly `allowInterop` in all callbacks.** + * `id`: + * **Changed type:** + * `IdConfiguration.intermediate_iframe_close_callback` to + `VoidFn?`. + * Added: `fedcm` to `CredentialSelectBy` enum. + * Fixed typo in `storeCredential` `callback` positional parameter name. + * `oauth2`: + * **Removed:** + * `CodeClientConfig.auto_select`, `hint` (now `login_hint`), and `hosted_domain` (now `hd`). + * `TokenClientConfig.hint` (now `login_hint`) and `hosted_domain` (now `hd`). + * `OverridableTokenClientConfig.hint` (now `login_hint`). + * **Changed types:** + * `CodeClientConfig.redirect_uri` to `Uri?`. + * `scope` in `CodeClientConfig` and `CodeResponse` to `List`. + * `CodeResponse.code` and `state` to `String?` (now nullable). + * `scope` in `TokenClientConfig`, `OverridableTokenClientConfig`, and `TokenResponse` to `List`. + * Made the following `TokenResponse` getters nullable: `access_token`, + `expires_in`, `hd`, `prompt`, `token_type`, and `state`. + * The `error_callback` functions now receive a `GoogleIdentityServicesError` parameter, instead of `Object`. + * Added: + * `include_granted_scopes` and `enable_granular_consent` to `CodeClientConfig`. + * `include_granted_scopes` and `enable_granular_consent` to `TokenClientConfig`. + * `enable_granular_consent` to `OverridableTokenClientConfig`. + * `message` to `GoogleIdentityServicesError`. + * Fixed: + * Assert that `CodeClientConfig.scope` is not empty when creating an instance. + * `TokenClientConfig.scope` is no longer `required`. + * Deprecated `enable_serial_consent`. ## 0.2.2 diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml index 66c7c59e41b..56d17fed082 100644 --- a/packages/google_identity_services_web/pubspec.yaml +++ b/packages/google_identity_services_web/pubspec.yaml @@ -2,7 +2,7 @@ name: google_identity_services_web 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.2.2 +version: 0.3.0 environment: sdk: ">=3.0.0 <4.0.0" From a1feea08b2bb19269b85b06172b278477a084234 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 16:17:53 -0800 Subject: [PATCH 11/22] Specify deps identically to other package:web migrations. --- packages/google_identity_services_web/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml index 56d17fed082..ed2a12a37fd 100644 --- a/packages/google_identity_services_web/pubspec.yaml +++ b/packages/google_identity_services_web/pubspec.yaml @@ -5,11 +5,11 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.3.0 environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ^3.2.0 dependencies: meta: ^1.3.0 - web: ^0.4.0 + web: '>=0.3.0 <0.5.0' dev_dependencies: path: ^1.8.1 From 7c2059ed20cf8e1562132b38150a6a98e8835fe8 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 16:22:14 -0800 Subject: [PATCH 12/22] CHANGELOG style --- .../google_identity_services_web/CHANGELOG.md | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md index 450f445d1b1..2e661f5e72d 100644 --- a/packages/google_identity_services_web/CHANGELOG.md +++ b/packages/google_identity_services_web/CHANGELOG.md @@ -1,34 +1,36 @@ ## 0.3.0 * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. -* Migrated from to `package:web` so this package can compile to WASM. -* **Breaking API changes** and fixes to align with the GIS SDK: - * **Removed the need to explicitly `allowInterop` in all callbacks.** +* Migrates from `package:js`/`dart:html` to `package:web` so this package can + compile to WASM. +* Performs the following **breaking API changes (in bold)** and other fixes to + align with the published GIS SDK: + * **Removes the need to explicitly `allowInterop` in all callbacks.** * `id`: - * **Changed type:** + * **Changes type:** * `IdConfiguration.intermediate_iframe_close_callback` to `VoidFn?`. - * Added: `fedcm` to `CredentialSelectBy` enum. - * Fixed typo in `storeCredential` `callback` positional parameter name. + * Adds: `fedcm` to `CredentialSelectBy` enum. + * Fixes typo in `storeCredential` `callback` positional parameter name. * `oauth2`: - * **Removed:** + * **Removes:** * `CodeClientConfig.auto_select`, `hint` (now `login_hint`), and `hosted_domain` (now `hd`). * `TokenClientConfig.hint` (now `login_hint`) and `hosted_domain` (now `hd`). * `OverridableTokenClientConfig.hint` (now `login_hint`). - * **Changed types:** + * **Changes types:** * `CodeClientConfig.redirect_uri` to `Uri?`. * `scope` in `CodeClientConfig` and `CodeResponse` to `List`. * `CodeResponse.code` and `state` to `String?` (now nullable). * `scope` in `TokenClientConfig`, `OverridableTokenClientConfig`, and `TokenResponse` to `List`. - * Made the following `TokenResponse` getters nullable: `access_token`, + * The following `TokenResponse` getters are now nullable: `access_token`, `expires_in`, `hd`, `prompt`, `token_type`, and `state`. * The `error_callback` functions now receive a `GoogleIdentityServicesError` parameter, instead of `Object`. - * Added: + * Adds: * `include_granted_scopes` and `enable_granular_consent` to `CodeClientConfig`. * `include_granted_scopes` and `enable_granular_consent` to `TokenClientConfig`. * `enable_granular_consent` to `OverridableTokenClientConfig`. * `message` to `GoogleIdentityServicesError`. - * Fixed: + * Fixes: * Assert that `CodeClientConfig.scope` is not empty when creating an instance. * `TokenClientConfig.scope` is no longer `required`. * Deprecated `enable_serial_consent`. From 949ba0ce05641c9ff48b082bd92a12d076544fec Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 16:40:07 -0800 Subject: [PATCH 13/22] Fix example app dep on package:web. --- packages/google_identity_services_web/example/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml index e7ddb7f1ac5..5a5850bfdfd 100644 --- a/packages/google_identity_services_web/example/pubspec.yaml +++ b/packages/google_identity_services_web/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 0.0.1 environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ^3.2.0 dependencies: flutter: @@ -12,7 +12,7 @@ dependencies: google_identity_services_web: path: ../ http: ">=0.13.0 <2.0.0" - web: ^0.4.0 + web: '>=0.3.0 <0.5.0' dev_dependencies: build_runner: ^2.1.10 # To extract README excerpts only. From 6aaf828781be232c20d0eb1687880a5b2683d8d8 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 5 Dec 2023 16:45:48 -0800 Subject: [PATCH 14/22] Don't lie on some comments. Co-authored-by: Kevin Moore --- .../test/js_loader_tt_custom_test.dart | 2 +- .../test/js_loader_tt_forbidden_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart b/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart index 545f8d71cf9..0ee856f68f0 100644 --- a/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart +++ b/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -@TestOn('browser') // Uses package:js +@TestOn('browser') // Uses package:web import 'package:google_identity_services_web/loader.dart'; import 'package:test/test.dart'; diff --git a/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart b/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart index 36d60961975..3806e9087da 100644 --- a/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart +++ b/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -@TestOn('browser') // Uses package:js +@TestOn('browser') // Uses package:web import 'package:google_identity_services_web/loader.dart'; import 'package:test/test.dart'; From 40370747701e6c28bc7477674d2b9084f82447f4 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 16:52:25 -0800 Subject: [PATCH 15/22] Add ignores for downgraded analysis. --- .../example/integration_test/js_interop_id_test.dart | 3 +++ .../example/integration_test/js_interop_oauth_test.dart | 3 +++ .../example/integration_test/utils.dart | 3 +++ 3 files changed, 9 insertions(+) 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 index f676a226e25..9c0fbcea250 100644 --- 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 @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// the following ignore is needed for downgraded analyzer (casts to JSObject). +// ignore_for_file: unnecessary_cast + import 'dart:async'; import 'dart:js_interop'; 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 index 3098a68cb7f..8bf99762432 100644 --- 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 @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// the following ignore is needed for downgraded analyzer (casts to JSObject). +// ignore_for_file: unnecessary_cast + import 'dart:async'; import 'dart:js_interop'; diff --git a/packages/google_identity_services_web/example/integration_test/utils.dart b/packages/google_identity_services_web/example/integration_test/utils.dart index bdfb70fc6d0..397d87968c4 100644 --- a/packages/google_identity_services_web/example/integration_test/utils.dart +++ b/packages/google_identity_services_web/example/integration_test/utils.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// the following ignore is needed for downgraded analyzer (casts to JSObject). +// ignore_for_file: unnecessary_cast + import 'dart:async'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; From 5a66e9d7c9ee3657051409f1a475d47aa8881f09 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 5 Dec 2023 16:58:38 -0800 Subject: [PATCH 16/22] Remove test client ids. --- packages/google_identity_services_web/example/lib/main.dart | 3 +-- .../google_identity_services_web/example/lib/main_oauth.dart | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/google_identity_services_web/example/lib/main.dart b/packages/google_identity_services_web/example/lib/main.dart index 50243d56d3a..a8f6239dbff 100644 --- a/packages/google_identity_services_web/example/lib/main.dart +++ b/packages/google_identity_services_web/example/lib/main.dart @@ -19,8 +19,7 @@ void main() async { id.setLogLevel('debug'); final IdConfiguration config = IdConfiguration( - client_id: - '451137223537-t8qp8tl992tgqj5focnc6mh8cp2psn8g.apps.googleusercontent.com', + client_id: 'your-google-client-id-goes-here.apps.googleusercontent.com', callback: onCredentialResponse, use_fedcm_for_prompt: true, ); 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 2ce4e2216a4..05fed3ba5f4 100644 --- a/packages/google_identity_services_web/example/lib/main_oauth.dart +++ b/packages/google_identity_services_web/example/lib/main_oauth.dart @@ -36,8 +36,7 @@ void main() async { id.setLogLevel('debug'); final TokenClientConfig config = TokenClientConfig( - client_id: - '451137223537-t8qp8tl992tgqj5focnc6mh8cp2psn8g.apps.googleusercontent.com', + client_id: 'your-google-client-id-goes-here.apps.googleusercontent.com', scope: scopes, callback: onTokenResponse, error_callback: onError, From a2e39746f0ff0a703135cc6e8a2d72b5e73b9718 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 6 Dec 2023 15:04:04 -0800 Subject: [PATCH 17/22] Exclude from all_packages_app, until google_sign_in_web takes the latest version. --- script/configs/exclude_all_packages_app.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script/configs/exclude_all_packages_app.yaml b/script/configs/exclude_all_packages_app.yaml index 040087f10f5..0a43bb89882 100644 --- a/script/configs/exclude_all_packages_app.yaml +++ b/script/configs/exclude_all_packages_app.yaml @@ -9,5 +9,8 @@ # NOTE: camera_android is semi-excluded via special casing in the repo tools. # See create_all_packages_app_command.dart. +# Excluded until the google_sign_in_web package updates +- google_identity_services_web + # This is a permament entry, as it should never be a direct app dependency. - plugin_platform_interface From f88491f1136d8409df49cd0acd1ae9f943464348 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 6 Dec 2023 17:16:51 -0800 Subject: [PATCH 18/22] Make not optional, and assert it is not empty. --- .../lib/src/js_interop/google_accounts_oauth2.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 c510068c0ab..71bc79a1ac5 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 @@ -235,7 +235,7 @@ abstract class TokenClientConfig { factory TokenClientConfig({ required String client_id, required TokenClientCallbackFn callback, - List? scope, + required List scope, bool? include_granted_scopes, String? prompt, bool? enable_granular_consent, @@ -246,6 +246,7 @@ abstract class TokenClientConfig { String? state, ErrorCallbackFn? error_callback, }) { + assert(scope.isNotEmpty); return TokenClientConfig._toJS( client_id: client_id.toJS, callback: callback.toJS, @@ -373,6 +374,7 @@ abstract class OverridableTokenClientConfig { /// authorization server's response. String? state, }) { + assert(scope == null || scope.isNotEmpty); return OverridableTokenClientConfig._toJS( scope: scope?.join(' ').toJS, include_granted_scopes: include_granted_scopes?.toJS, From 0c1e1c31e511667bdea525cc8f68453ad920d39b Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 6 Dec 2023 17:19:19 -0800 Subject: [PATCH 19/22] Make scope required again, otherwise the SDK crashes at init. --- packages/google_identity_services_web/CHANGELOG.md | 4 ++-- .../lib/src/js_interop/google_accounts_oauth2.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md index 2e661f5e72d..b113d4065ac 100644 --- a/packages/google_identity_services_web/CHANGELOG.md +++ b/packages/google_identity_services_web/CHANGELOG.md @@ -31,8 +31,8 @@ * `enable_granular_consent` to `OverridableTokenClientConfig`. * `message` to `GoogleIdentityServicesError`. * Fixes: - * Assert that `CodeClientConfig.scope` is not empty when creating an instance. - * `TokenClientConfig.scope` is no longer `required`. + * Assert that `scope` is not empty when used to create `CodeClientConfig`, + `TokenClientConfig`, and `OverridableTokenClientConfig` instances. * Deprecated `enable_serial_consent`. ## 0.2.2 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 71bc79a1ac5..9c97ba43b6a 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 @@ -250,7 +250,7 @@ abstract class TokenClientConfig { return TokenClientConfig._toJS( client_id: client_id.toJS, callback: callback.toJS, - scope: scope?.join(' ').toJS, + scope: scope.join(' ').toJS, include_granted_scopes: include_granted_scopes?.toJS, prompt: prompt?.toJS, enable_granular_consent: enable_granular_consent?.toJS, From 046662bf1f70be9e4589a693fc79caac8f0aee11 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 6 Dec 2023 18:36:27 -0800 Subject: [PATCH 20/22] Address some more PR comments. --- .../example/integration_test/js_interop_id_test.dart | 6 +++--- .../integration_test/js_interop_oauth_test.dart | 8 ++++---- .../example/integration_test/utils.dart | 11 ++++++++--- 3 files changed, 15 insertions(+), 10 deletions(-) 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 index 9c0fbcea250..6120bf3f113 100644 --- 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 @@ -62,9 +62,9 @@ void main() async { expectConfigValue('client_id', 'testing_1-2-3'); expectConfigValue('auto_select', isFalse); - expectConfigValue('callback', isA()); + expectConfigValue('callback', utils.isAJs('function')); expectConfigValue('login_uri', 'https://www.example.com/login'); - expectConfigValue('native_callback', isA()); + expectConfigValue('native_callback', utils.isAJs('function')); expectConfigValue('cancel_on_tap_outside', isFalse); expectConfigValue('allowed_parent_origin', isA()); expectConfigValue('prompt_parent_id', 'some_dom_id'); @@ -75,7 +75,7 @@ void main() async { expectConfigValue( 'allowed_parent_origin', ['allowed', 'another']); expectConfigValue( - 'intermediate_iframe_close_callback', isA()); + 'intermediate_iframe_close_callback', utils.isAJs('function')); expectConfigValue('itp_support', isTrue); expectConfigValue('login_hint', 'login-hint@example.com'); expectConfigValue('hd', 'hd_value'); 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 index 8bf99762432..c197b1116dc 100644 --- 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 @@ -41,7 +41,7 @@ void main() async { utils.createExpectConfigValue(config as JSObject); expectConfigValue('client_id', 'testing_1-2-3'); - expectConfigValue('callback', isA()); + expectConfigValue('callback', utils.isAJs('function')); expectConfigValue('scope', 'one two three'); expectConfigValue('include_granted_scopes', isTrue); expectConfigValue('prompt', 'some-prompt'); @@ -49,7 +49,7 @@ void main() async { expectConfigValue('login_hint', 'login-hint@example.com'); expectConfigValue('hd', 'hd_value'); expectConfigValue('state', 'some-state'); - expectConfigValue('error_callback', isA()); + expectConfigValue('error_callback', utils.isAJs('function')); }); testWidgets('OverridableTokenClientConfig', (_) async { @@ -95,14 +95,14 @@ void main() async { expectConfigValue('scope', 'one two three'); expectConfigValue('include_granted_scopes', isTrue); expectConfigValue('redirect_uri', 'https://www.example.com/login'); - expectConfigValue('callback', isA()); + expectConfigValue('callback', utils.isAJs('function')); expectConfigValue('state', 'some-state'); expectConfigValue('enable_granular_consent', isTrue); expectConfigValue('login_hint', 'login-hint@example.com'); expectConfigValue('hd', 'hd_value'); expectConfigValue('ux_mode', 'popup'); expectConfigValue('select_account', isTrue); - expectConfigValue('error_callback', isA()); + expectConfigValue('error_callback', utils.isAJs('function')); }); }); diff --git a/packages/google_identity_services_web/example/integration_test/utils.dart b/packages/google_identity_services_web/example/integration_test/utils.dart index 397d87968c4..2d5c3f3fab8 100644 --- a/packages/google_identity_services_web/example/integration_test/utils.dart +++ b/packages/google_identity_services_web/example/integration_test/utils.dart @@ -23,10 +23,16 @@ typedef ExpectConfigValueFn = void Function(String name, Object? matcher); /// Creates a [ExpectConfigValueFn] for the `config` [JSObject]. ExpectConfigValueFn createExpectConfigValue(JSObject config) { return (String name, Object? matcher) { - expect(config.getProperty(name.toJS), matcher, reason: name); + expect(config[name], matcher, reason: name); }; } +/// A matcher that checks if: value typeof [thing] == true (in JS). +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof +Matcher isAJs(String thing) => isA() + .having((JSAny? p0) => p0.typeofEquals(thing), 'typeof "$thing"', isTrue); + /// Installs mock-gis.js in the page. /// Returns a future that completes when the 'load' event of the script fires. Future installGisMock() { @@ -93,7 +99,6 @@ T _getDeepProperty(JSObject jsObject, String deepProperty) { final List properties = deepProperty.split('.'); return properties.fold( jsObject, - (JSObject? jsObj, String prop) => - jsObj?.getProperty(prop.toJS) as JSObject?, + (JSObject? jsObj, String prop) => jsObj?[prop] as JSObject?, ) as T; } From 329a098515ed40973552d9f1e180dd952ffeef83 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 6 Dec 2023 20:27:51 -0800 Subject: [PATCH 21/22] Unify deps. Use http compatible with wasm. --- packages/google_identity_services_web/example/pubspec.yaml | 4 ++-- packages/google_identity_services_web/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml index 5a5850bfdfd..b47e80c0fe5 100644 --- a/packages/google_identity_services_web/example/pubspec.yaml +++ b/packages/google_identity_services_web/example/pubspec.yaml @@ -11,8 +11,8 @@ dependencies: sdk: flutter google_identity_services_web: path: ../ - http: ">=0.13.0 <2.0.0" - web: '>=0.3.0 <0.5.0' + http: ^1.1.2 + web: ">=0.3.0 <0.5.0" dev_dependencies: build_runner: ^2.1.10 # To extract README excerpts only. diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml index ed2a12a37fd..3c67b63a247 100644 --- a/packages/google_identity_services_web/pubspec.yaml +++ b/packages/google_identity_services_web/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: meta: ^1.3.0 - web: '>=0.3.0 <0.5.0' + web: ">=0.3.0 <0.5.0" dev_dependencies: path: ^1.8.1 From 114601a832277c1774479b2da2e014c3068c459b Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 6 Dec 2023 20:30:12 -0800 Subject: [PATCH 22/22] Specify minimum flutter version. --- packages/google_identity_services_web/example/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml index b47e80c0fe5..87f30785e4e 100644 --- a/packages/google_identity_services_web/example/pubspec.yaml +++ b/packages/google_identity_services_web/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: 'none' version: 0.0.1 environment: + flutter: ^3.16.0 sdk: ^3.2.0 dependencies: