diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md index d84b8228789..966e8125719 100644 --- a/packages/google_identity_services_web/CHANGELOG.md +++ b/packages/google_identity_services_web/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.1 + +* Relaxes the `renderButton` API so any JS-Interop Object can be its `target`. +* Exposes the `Button*` configuration enums, so the rendered button can be configured. + ## 0.2.0 * Adds `renderButton` API to `id.dart`. 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 17ac7484b8c..78a469435e2 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 @@ -9,6 +9,7 @@ import 'package:google_identity_services_web/id.dart'; import 'package:integration_test/integration_test.dart'; import 'package:js/js.dart'; +import 'src/dom.dart'; import 'utils.dart' as utils; void main() async { @@ -19,6 +20,17 @@ void main() async { await utils.installGisMock(); }); + group('renderButton', () { + testWidgets('supports a js-interop target from any library', (_) async { + final DomElement target = createDomElement('div'); + + id.renderButton(target); + + final DomElement? button = target.querySelector('button'); + expect(button, isNotNull); + }); + }); + group('prompt', () { testWidgets('supports a moment notification callback', (_) async { id.initialize(IdConfiguration(client_id: 'testing_1-2-3')); 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 new file mode 100644 index 00000000000..fb41b0a9b90 --- /dev/null +++ b/packages/google_identity_services_web/example/integration_test/src/dom.dart @@ -0,0 +1,31 @@ +// 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/example/lib/main.dart b/packages/google_identity_services_web/example/lib/main.dart index 3dc8c0e26fe..1046826641a 100644 --- a/packages/google_identity_services_web/example/lib/main.dart +++ b/packages/google_identity_services_web/example/lib/main.dart @@ -9,7 +9,8 @@ import 'package:google_identity_services_web/id.dart'; import 'package:google_identity_services_web/loader.dart' as gis; // #enddocregion use-loader import 'package:js/js.dart' show allowInterop; -import 'package:jwt_decoder/jwt_decoder.dart' as jwt; + +import 'src/jwt.dart' as jwt; // #docregion use-loader void main() async { @@ -32,7 +33,7 @@ void main() async { /// Handles the ID token returned from the One Tap prompt. /// See: https://developers.google.com/identity/gsi/web/reference/js-reference#callback void onCredentialResponse(CredentialResponse o) { - final Map? payload = jwt.JwtDecoder.tryDecode(o.credential!); + final Map? payload = jwt.decodePayload(o.credential); if (payload != null) { print('Hello, ${payload["name"]}'); print(o.select_by); diff --git a/packages/google_identity_services_web/example/lib/src/jwt.dart b/packages/google_identity_services_web/example/lib/src/jwt.dart new file mode 100644 index 00000000000..bc17f49a784 --- /dev/null +++ b/packages/google_identity_services_web/example/lib/src/jwt.dart @@ -0,0 +1,50 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; + +/// A codec that can encode/decode JWT payloads. +/// +/// See https://www.rfc-editor.org/rfc/rfc7519#section-3 +final Codec _jwtCodec = json.fuse(utf8).fuse(base64); + +/// A RegExp that can match, and extract parts from a JWT Token. +/// +/// A JWT token consists of 3 base-64 encoded parts of data separated by periods: +/// +/// header.payload.signature +/// +/// More info: https://regexr.com/789qc +final RegExp _jwtTokenRegexp = RegExp( + r'^(?
[^\.\s]+)\.(?[^\.\s]+)\.(?[^\.\s]+)$'); + +/// Decodes the `claims` of a JWT token and returns them as a Map. +/// +/// JWT `claims` are stored as a JSON object in the `payload` part of the token. +/// +/// (This method does not validate the signature of the token.) +/// +/// See https://www.rfc-editor.org/rfc/rfc7519#section-3 +Map? decodePayload(String? token) { + if (token != null) { + final RegExpMatch? match = _jwtTokenRegexp.firstMatch(token); + if (match != null) { + return _decodeJwtPayload(match.namedGroup('payload')); + } + } + + return null; +} + +/// Decodes a JWT payload using the [_jwtCodec]. +Map? _decodeJwtPayload(String? payload) { + try { + // Payload must be normalized before passing it to the codec + return _jwtCodec.decode(base64.normalize(payload!)) + as Map?; + } catch (_) { + // Do nothing, we always return null for any failure. + } + return null; +} diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml index 4b87b49f815..b305694d5c7 100644 --- a/packages/google_identity_services_web/example/pubspec.yaml +++ b/packages/google_identity_services_web/example/pubspec.yaml @@ -13,7 +13,6 @@ dependencies: path: ../ http: ^0.13.5 js: ^0.6.4 - jwt_decoder: 2.0.1 dev_dependencies: build_runner: ^2.1.10 # To extract README excerpts only. diff --git a/packages/google_identity_services_web/example/web/mock-gis.js b/packages/google_identity_services_web/example/web/mock-gis.js index 2e1346051fd..5921c5eb89f 100644 --- a/packages/google_identity_services_web/example/web/mock-gis.js +++ b/packages/google_identity_services_web/example/web/mock-gis.js @@ -33,6 +33,13 @@ class Id { initialize(config) { this.config = config; } + renderButton(target, config) { + // Simulate rendering a button. + target.replaceChildren(); + target.dataset.buttonConfig = config; + let button = document.createElement('button'); + target.append(button); + } prompt(momentListener) { callAsync(() => { if (this.mockCredentialResponse) { diff --git a/packages/google_identity_services_web/lib/id.dart b/packages/google_identity_services_web/lib/id.dart index 34862128e6c..922ef0f8dc1 100644 --- a/packages/google_identity_services_web/lib/id.dart +++ b/packages/google_identity_services_web/lib/id.dart @@ -5,7 +5,14 @@ export 'src/js_interop/google_accounts_id.dart'; export 'src/js_interop/shared.dart' show + ButtonLogoAlignment, + ButtonShape, + ButtonSize, + ButtonText, + ButtonTheme, + ButtonType, CredentialSelectBy, + GoogleIdentityServicesErrorType, MomentDismissedReason, MomentNotDisplayedReason, MomentSkippedReason, diff --git a/packages/google_identity_services_web/lib/oauth2.dart b/packages/google_identity_services_web/lib/oauth2.dart index 63db5bc5496..e0cb2369e20 100644 --- a/packages/google_identity_services_web/lib/oauth2.dart +++ b/packages/google_identity_services_web/lib/oauth2.dart @@ -3,4 +3,5 @@ // found in the LICENSE file. export 'src/js_interop/google_accounts_oauth2.dart'; -export 'src/js_interop/shared.dart' show UxMode; +export 'src/js_interop/shared.dart' + show GoogleIdentityServicesErrorType, UxMode; 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 a790fae78f7..45a8e51a69c 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 @@ -14,7 +14,6 @@ library google_accounts_id; import 'package:js/js.dart'; -import 'dom.dart'; import 'shared.dart'; /// Binding to the `google.accounts.id` JS global. @@ -93,7 +92,7 @@ extension GoogleAccountsIdExtension on GoogleAccountsId { /// Method: google.accounts.id.renderButton /// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.renderButton external void renderButton( - DomHtmlElement parent, [ + Object parent, [ GsiButtonConfiguration options, ]); diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml index e979dfa9708..8d4b6c53eb5 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.0 +version: 0.2.1 environment: sdk: ">=2.17.0 <4.0.0" diff --git a/script/configs/allowed_pinned_deps.yaml b/script/configs/allowed_pinned_deps.yaml index 67886a9634f..e1d7f549e03 100644 --- a/script/configs/allowed_pinned_deps.yaml +++ b/script/configs/allowed_pinned_deps.yaml @@ -10,7 +10,6 @@ # Legacy allowances, for dependencies that predate the tooling. # TODO(stuartmorgan): Audit these. See # https://github.com/flutter/flutter/issues/122713 -- jwt_decoder - lcov_parser - adaptive_dialog - provider