[gis_web] Introduce new GIS SDK JS-Interop package. (#2653)
diff --git a/.ci/scripts/custom_package_tests.sh b/.ci/scripts/custom_package_tests.sh
index 236300e..92d953b 100755
--- a/.ci/scripts/custom_package_tests.sh
+++ b/.ci/scripts/custom_package_tests.sh
@@ -5,9 +5,9 @@
# Exclusions
#
-# cross_file
+# script/configs/linux_only_custom_test.yaml
# Custom tests need Chrome. (They run in linux-custom_package_tests)
dart pub global run flutter_plugin_tools custom-test \
--packages-for-branch --log-timing \
- --exclude=cross_file
+ --exclude=script/configs/linux_only_custom_test.yaml
diff --git a/.cirrus.yml b/.cirrus.yml
index fbd0689..1c059b6 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -261,12 +261,12 @@
local_tests_script:
# flutter_image
# https://github.com/flutter/flutter/issues/100387
- # cross_file
- # Custom tests need Chrome. (They run in linux-custom_package_tests)
+ # script/configs/linux_only_custom_test.yaml
+ # Custom tests need Chrome for these packages. (They run in linux-custom_package_tests)
- if [[ "$CHANNEL" == "master" ]]; then
- - ./script/tool_runner.sh custom-test --exclude=cross_file
+ - ./script/tool_runner.sh custom-test --exclude=script/configs/linux_only_custom_test.yaml
- else
- - ./script/tool_runner.sh custom-test --exclude=cross_file,flutter_image
+ - ./script/tool_runner.sh custom-test --exclude=flutter_image,script/configs/linux_only_custom_test.yaml
- fi
### macOS desktop tasks ###
- name: macos-platform_tests
diff --git a/.github/labeler.yml b/.github/labeler.yml
index e680066..b0553c8 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -37,6 +37,9 @@
'p: go_router_builder':
- packages/go_router_builder/**/*
+'p: google_identity_services':
+ - packages/google_indentity_services_web/**
+
'p: imitation_game':
- packages/imitation_game/**/*
diff --git a/packages/google_identity_services_web/AUTHORS b/packages/google_identity_services_web/AUTHORS
new file mode 100644
index 0000000..eb34297
--- /dev/null
+++ b/packages/google_identity_services_web/AUTHORS
@@ -0,0 +1,7 @@
+# Below is a list of people and organizations that have contributed
+# to the Flutter project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
+The Chromium Authors
diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md
new file mode 100644
index 0000000..6073234
--- /dev/null
+++ b/packages/google_identity_services_web/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1.0
+
+* Initial release.
diff --git a/packages/google_identity_services_web/LICENSE b/packages/google_identity_services_web/LICENSE
new file mode 100644
index 0000000..c6823b8
--- /dev/null
+++ b/packages/google_identity_services_web/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/google_identity_services_web/README.md b/packages/google_identity_services_web/README.md
new file mode 100644
index 0000000..71bbb86
--- /dev/null
+++ b/packages/google_identity_services_web/README.md
@@ -0,0 +1,87 @@
+<?code-excerpt path-base="excerpts/packages/google_identity_services_web_example"?>
+
+# google_identity_services_web
+
+A JS-interop layer for Google Identity's Sign In With Google SDK.
+
+See the original JS SDK reference:
+
+* [Sign In With Google](https://developers.google.com/identity/gsi/web)
+
+## Usage
+
+This package is the Dart JS-interop layer of the new **Sign In With Google**
+SDK. Here's the API references for both of the sub-libraries:
+
+* `id.dart`: [Sign In With Google JavaScript API reference](https://developers.google.com/identity/gsi/web/reference/js-reference)
+* `oauth2.dart`: [Google 3P Authorization JavaScript Library for websites - API reference](https://developers.google.com/identity/oauth2/web/reference/js-reference)
+* `loader.dart`: An (optional) loader mechanism that installs the library and
+resolves a `Future<void>` when it's ready.
+
+### Loading the SDK
+
+There are two ways to load the JS SDK in your app.
+
+#### Modify your index.html (most performant)
+
+The most performant way is to modify your `web/index.html` file to insert a
+script tag [as recommended](https://developers.google.com/identity/gsi/web/guides/client-library).
+Place the `script` tag in the `<head>` of your site, next to the script tag that
+loads `flutter.js`, so the browser can downloaded both in parallel:
+
+<?code-excerpt "../../web/index-with-script-tag.html (script-tag)"?>
+```html
+<head>
+<!-- ··· -->
+ <!-- Include the GSI SDK below -->
+ <script src="https://accounts.google.com/gsi/client" async defer></script>
+ <!-- This script adds the flutter initialization JS code -->
+ <script src="flutter.js" defer></script>
+</head>
+```
+
+#### With the `loadWebSdk` function (on-demand)
+
+An alternative way, that downloads the SDK on demand, is to use the
+**`loadWebSdk`** function provided by the library. A simple location to embed
+this in a Flutter Web only app can be the `main.dart`:
+
+<?code-excerpt "main.dart (use-loader)"?>
+```dart
+import 'package:google_identity_services_web/loader.dart' as gis;
+// ···
+void main() async {
+ await gis.loadWebSdk(); // Load the GIS SDK
+ // The rest of your code...
+// ···
+}
+```
+
+(Note that the above won't compile for mobile apps, so if you're developing a
+cross-platform app, you'll probably need to hide the call to `loadWebSdk`
+behind a [conditional import/export](https://dart.dev/guides/libraries/create-library-packages#conditionally-importing-and-exporting-library-files).)
+
+### Using the SDK
+
+Once the SDK has been loaded, it can be used by importing the correct library:
+
+* `import 'package:google_identity_services/id.dart' as id;` for Authentication
+* `import 'package:google_identity_services/oauth2.dart' as oauth2;` for
+ Authorization.
+
+## Browser compatibility
+
+The new SDK is introducing concepts that are on track for standardization to
+most browsers, and it might not be compatible with older browsers.
+
+Refer to the official documentation site for the latest browser compatibility
+information of the underlying JS SDK:
+
+* **Sign In With Google > [Supported browsers and platforms](https://developers.google.com/identity/gsi/web/guides/supported-browsers)**
+
+## Testing
+
+This web-only package uses `dart:test` to test its features. They can be run
+with `dart test -p chrome`.
+
+_(Look at `test/README.md` and `tool/run_tests.dart` for more info.)_
diff --git a/packages/google_identity_services_web/dart_test.yaml b/packages/google_identity_services_web/dart_test.yaml
new file mode 100644
index 0000000..cdb656d
--- /dev/null
+++ b/packages/google_identity_services_web/dart_test.yaml
@@ -0,0 +1,6 @@
+# See https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#arguments
+override_platforms:
+ chrome:
+ settings:
+ executable: chrome
+ arguments: --no-sandbox
diff --git a/packages/google_identity_services_web/example/.gitignore b/packages/google_identity_services_web/example/.gitignore
new file mode 100644
index 0000000..24476c5
--- /dev/null
+++ b/packages/google_identity_services_web/example/.gitignore
@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/packages/google_identity_services_web/example/.metadata b/packages/google_identity_services_web/example/.metadata
new file mode 100644
index 0000000..9ff5c77
--- /dev/null
+++ b/packages/google_identity_services_web/example/.metadata
@@ -0,0 +1,45 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+ revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ channel: master
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ base_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ - platform: android
+ create_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ base_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ - platform: ios
+ create_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ base_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ - platform: linux
+ create_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ base_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ - platform: macos
+ create_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ base_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ - platform: web
+ create_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ base_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ - platform: windows
+ create_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+ base_revision: 822f6e3595d63f864ae2027ea37fd645b313b71b
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/packages/google_identity_services_web/example/README.md b/packages/google_identity_services_web/example/README.md
new file mode 100644
index 0000000..a348670
--- /dev/null
+++ b/packages/google_identity_services_web/example/README.md
@@ -0,0 +1,4 @@
+# google_identity_services_web_example
+
+* `lib/main.dart`: An example on how to use the google_identiy_services_web `id` library from Dart.
+* `lib/main_oauth.dart`: An example for the `oauth2` library from the same SDK.
diff --git a/packages/google_identity_services_web/example/build.excerpt.yaml b/packages/google_identity_services_web/example/build.excerpt.yaml
new file mode 100644
index 0000000..8b673fd
--- /dev/null
+++ b/packages/google_identity_services_web/example/build.excerpt.yaml
@@ -0,0 +1,19 @@
+targets:
+ $default:
+ sources:
+ include:
+ - lib/**
+ - web/**
+ # Some default includes that aren't really used here but will prevent
+ # false-negative warnings:
+ - $package$
+ - lib/$lib$
+ exclude:
+ - '**/.*/**'
+ - '**/build/**'
+ builders:
+ code_excerpter|code_excerpter:
+ enabled: true
+ generate_for:
+ - '**/*.dart'
+ - '**/*.html'
diff --git a/packages/google_identity_services_web/example/lib/main.dart b/packages/google_identity_services_web/example/lib/main.dart
new file mode 100644
index 0000000..949eb51
--- /dev/null
+++ b/packages/google_identity_services_web/example/lib/main.dart
@@ -0,0 +1,56 @@
+// 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: avoid_print
+
+import 'package:google_identity_services_web/id.dart' as id;
+// #docregion use-loader
+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;
+
+// #docregion use-loader
+void main() async {
+ await gis.loadWebSdk(); // Load the GIS SDK
+ // The rest of your code...
+// #enddocregion use-loader
+ id.setLogLevel('debug');
+
+ final id.IdConfiguration config = id.IdConfiguration(
+ client_id: 'your-client_id.apps.googleusercontent.com',
+ ux_mode: id.UxMode.popup,
+ callback: allowInterop(onCredentialResponse),
+ );
+
+ id.initialize(config);
+ id.prompt(allowInterop(onPromptMoment));
+// #docregion use-loader
+}
+// #enddocregion use-loader
+
+/// Handles the ID token returned from the One Tap prompt.
+/// See: https://developers.google.com/identity/gsi/web/reference/js-reference#callback
+void onCredentialResponse(id.CredentialResponse o) {
+ final Map<String, dynamic>? payload = jwt.JwtDecoder.tryDecode(o.credential);
+ if (payload != null) {
+ print('Hello, ${payload["name"]}');
+ print(o.select_by);
+ print(payload);
+ } else {
+ print('Could not decode ${o.credential}');
+ }
+}
+
+/// Handles Prompt UI status notifications.
+/// See: https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt
+void onPromptMoment(id.PromptMomentNotification o) {
+ final id.MomentType type = o.getMomentType();
+ print(type.runtimeType);
+ print(type);
+ print(type.index);
+ print(o.getDismissedReason());
+ print(o.getNotDisplayedReason());
+ print(o.getSkippedReason());
+}
diff --git a/packages/google_identity_services_web/example/lib/main_oauth.dart b/packages/google_identity_services_web/example/lib/main_oauth.dart
new file mode 100644
index 0000000..041715f
--- /dev/null
+++ b/packages/google_identity_services_web/example/lib/main_oauth.dart
@@ -0,0 +1,79 @@
+// 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: avoid_print
+
+import 'package:google_identity_services_web/id.dart' as id show setLogLevel;
+import 'package:google_identity_services_web/loader.dart' as gis;
+import 'package:google_identity_services_web/oauth2.dart' as oauth2;
+import 'package:http/http.dart' as http;
+import 'package:js/js.dart' show allowInterop;
+
+/// The scopes to be requested
+const List<String> scopes = <String>[
+ 'email',
+ 'profile',
+ 'https://www.googleapis.com/auth/contacts.readonly',
+];
+
+void main() async {
+ await gis.loadWebSdk(); // Load the GIS SDK
+
+ id.setLogLevel('debug');
+
+ final oauth2.TokenClientConfig config = oauth2.TokenClientConfig(
+ client_id: 'your-client_id.apps.googleusercontent.com',
+ scope: scopes.join(' '),
+ callback: allowInterop(onTokenResponse),
+ );
+
+ final oauth2.OverridableTokenClientConfig overridableCfg =
+ oauth2.OverridableTokenClientConfig(
+ prompt: '',
+ );
+
+ final oauth2.TokenClient client = oauth2.initTokenClient(config);
+
+ // Disable the Popup Blocker for this to work, or move this to a Button press.
+ client.requestAccessToken(overridableCfg);
+}
+
+/// Handles the returned (auth) token response.
+/// See: https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
+Future<void> onTokenResponse(oauth2.TokenResponse response) async {
+ if (response.error != null) {
+ print('Authorization error!');
+ print(response.error);
+ print(response.error_description);
+ print(response.error_uri);
+ return;
+ }
+
+ // Has granted all the scopes?
+ if (!oauth2.hasGrantedAllScopes(response, scopes[2])) {
+ print('The user has NOT granted the required scope!');
+ return;
+ }
+
+ // Attempt to do a request to the `people` API
+ final http.Response apiResponse = await http.get(
+ Uri.parse('https://people.googleapis.com/v1/people/me/connections'
+ '?requestMask.includeField=person.names'),
+ headers: <String, String>{
+ 'Authorization': '${response.token_type} ${response.access_token}',
+ },
+ );
+ if (apiResponse.statusCode == 200) {
+ print('People API ${apiResponse.statusCode} OK!');
+ } else {
+ print(
+ 'People API ${apiResponse.statusCode} Oops! Something wrong happened!');
+ }
+ print(apiResponse.body);
+
+ print('Revoking token...');
+ oauth2.revokeToken(response.access_token, allowInterop((String status) {
+ print(status);
+ }));
+}
diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml
new file mode 100644
index 0000000..e7d6429
--- /dev/null
+++ b/packages/google_identity_services_web/example/pubspec.yaml
@@ -0,0 +1,24 @@
+name: google_identity_services_web_example
+description: An example for the google_identity_services_web package, OneTap.
+publish_to: 'none'
+version: 0.0.1
+
+environment:
+ sdk: '>=2.17.0 <3.0.0'
+
+dependencies:
+ flutter:
+ sdk: flutter
+ google_identity_services_web:
+ 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.
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ uses-material-design: true
diff --git a/packages/google_identity_services_web/example/web/favicon.png b/packages/google_identity_services_web/example/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/favicon.png
Binary files differ
diff --git a/packages/google_identity_services_web/example/web/icons/Icon-192.png b/packages/google_identity_services_web/example/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/icons/Icon-192.png
Binary files differ
diff --git a/packages/google_identity_services_web/example/web/icons/Icon-512.png b/packages/google_identity_services_web/example/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/icons/Icon-512.png
Binary files differ
diff --git a/packages/google_identity_services_web/example/web/icons/Icon-maskable-192.png b/packages/google_identity_services_web/example/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/icons/Icon-maskable-192.png
Binary files differ
diff --git a/packages/google_identity_services_web/example/web/icons/Icon-maskable-512.png b/packages/google_identity_services_web/example/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/icons/Icon-maskable-512.png
Binary files differ
diff --git a/packages/google_identity_services_web/example/web/index-with-script-tag.html b/packages/google_identity_services_web/example/web/index-with-script-tag.html
new file mode 100644
index 0000000..07441f7
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/index-with-script-tag.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!-- 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. -->
+<html>
+<!--#docregion script-tag-->
+<head>
+<!--#enddocregion script-tag-->
+<base href="$FLUTTER_BASE_HREF">
+
+ <meta charset="UTF-8">
+ <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+ <meta name="description" content="A new Flutter project.">
+
+ <!-- iOS meta tags & icons -->
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black">
+ <meta name="apple-mobile-web-app-title" content="example">
+ <link rel="apple-touch-icon" href="icons/Icon-192.png">
+
+ <!-- Favicon -->
+ <link rel="icon" type="image/png" href="favicon.png"/>
+
+ <title>Authentication Example</title>
+ <link rel="manifest" href="manifest.json">
+
+ <script>
+ // The value below is injected by flutter build, do not touch.
+ var serviceWorkerVersion = null;
+ </script>
+<!--#docregion script-tag-->
+ <!-- Include the GSI SDK below -->
+ <script src="https://accounts.google.com/gsi/client" async defer></script>
+ <!-- This script adds the flutter initialization JS code -->
+ <script src="flutter.js" defer></script>
+</head>
+<!--#enddocregion script-tag-->
+<body>
+ <script>
+ window.addEventListener('load', function(ev) {
+ // Download main.dart.js
+ _flutter.loader.loadEntrypoint({
+ serviceWorker: {
+ serviceWorkerVersion: serviceWorkerVersion,
+ },
+ onEntrypointLoaded: function(engineInitializer) {
+ engineInitializer.autoStart();
+ }
+ });
+ });
+ </script>
+</body>
+</html>
diff --git a/packages/google_identity_services_web/example/web/index.html b/packages/google_identity_services_web/example/web/index.html
new file mode 100644
index 0000000..5727323
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/index.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!-- 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. -->
+<html>
+<head>
+ <base href="$FLUTTER_BASE_HREF">
+
+ <meta charset="UTF-8">
+ <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+ <meta name="description" content="A new Flutter project.">
+
+ <!-- iOS meta tags & icons -->
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black">
+ <meta name="apple-mobile-web-app-title" content="example">
+ <link rel="apple-touch-icon" href="icons/Icon-192.png">
+
+ <!-- Favicon -->
+ <link rel="icon" type="image/png" href="favicon.png"/>
+
+ <title>Authentication Example</title>
+ <link rel="manifest" href="manifest.json">
+
+ <script>
+ // The value below is injected by flutter build, do not touch.
+ var serviceWorkerVersion = null;
+ </script>
+ <!-- Trusted Types API config (disabled) -->
+ <!-- <meta http-equiv="Content-Security-Policy" content="trusted-types;"> -->
+ <!-- Load the GSI SDK -->
+ <!-- <script src="https://accounts.google.com/gsi/client" async defer></script> -->
+ <!-- This script adds the flutter initialization JS code -->
+ <script src="flutter.js" defer></script>
+</head>
+<body>
+ <script>
+ window.addEventListener('load', function(ev) {
+ // Download main.dart.js
+ _flutter.loader.loadEntrypoint({
+ serviceWorker: {
+ serviceWorkerVersion: serviceWorkerVersion,
+ },
+ onEntrypointLoaded: function(engineInitializer) {
+ engineInitializer.autoStart();
+ }
+ });
+ });
+ </script>
+</body>
+</html>
diff --git a/packages/google_identity_services_web/example/web/manifest.json b/packages/google_identity_services_web/example/web/manifest.json
new file mode 100644
index 0000000..096edf8
--- /dev/null
+++ b/packages/google_identity_services_web/example/web/manifest.json
@@ -0,0 +1,35 @@
+{
+ "name": "example",
+ "short_name": "example",
+ "start_url": ".",
+ "display": "standalone",
+ "background_color": "#0175C2",
+ "theme_color": "#0175C2",
+ "description": "A new Flutter project.",
+ "orientation": "portrait-primary",
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "icons/Icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-maskable-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable"
+ },
+ {
+ "src": "icons/Icon-maskable-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ }
+ ]
+}
diff --git a/packages/google_identity_services_web/lib/google_identity_services_web.dart b/packages/google_identity_services_web/lib/google_identity_services_web.dart
new file mode 100644
index 0000000..e3be3de
--- /dev/null
+++ b/packages/google_identity_services_web/lib/google_identity_services_web.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'loader.dart';
diff --git a/packages/google_identity_services_web/lib/id.dart b/packages/google_identity_services_web/lib/id.dart
new file mode 100644
index 0000000..3486212
--- /dev/null
+++ b/packages/google_identity_services_web/lib/id.dart
@@ -0,0 +1,14 @@
+// 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.
+
+export 'src/js_interop/google_accounts_id.dart';
+export 'src/js_interop/shared.dart'
+ show
+ CredentialSelectBy,
+ MomentDismissedReason,
+ MomentNotDisplayedReason,
+ MomentSkippedReason,
+ MomentType,
+ OneTapContext,
+ UxMode;
diff --git a/packages/google_identity_services_web/lib/loader.dart b/packages/google_identity_services_web/lib/loader.dart
new file mode 100644
index 0000000..906b7d1
--- /dev/null
+++ b/packages/google_identity_services_web/lib/loader.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'src/js_loader.dart' show TrustedTypesException, loadWebSdk;
diff --git a/packages/google_identity_services_web/lib/oauth2.dart b/packages/google_identity_services_web/lib/oauth2.dart
new file mode 100644
index 0000000..63db5bc
--- /dev/null
+++ b/packages/google_identity_services_web/lib/oauth2.dart
@@ -0,0 +1,6 @@
+// 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.
+
+export 'src/js_interop/google_accounts_oauth2.dart';
+export 'src/js_interop/shared.dart' show UxMode;
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
new file mode 100644
index 0000000..2cc121d
--- /dev/null
+++ b/packages/google_identity_services_web/lib/src/js_interop/dom.dart
@@ -0,0 +1,164 @@
+// 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);
+}
+
+/*
+// 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;
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
new file mode 100644
index 0000000..52fd806
--- /dev/null
+++ b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart
@@ -0,0 +1,440 @@
+// 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.
+
+// Authentication. API reference:
+// https://developers.google.com/identity/gsi/web/reference/js-reference
+
+// 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.
+
+@JS('google.accounts.id')
+library id;
+
+import 'package:js/js.dart';
+
+import 'shared.dart';
+
+/// An undocumented method. Try with 'debug'.
+@JS()
+external SetLogLevelFn get setLogLevel;
+
+///
+typedef SetLogLevelFn = void Function(String level);
+
+/*
+// Method: google.accounts.id.initialize
+// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.initialize
+*/
+
+/// Initializes the Sign In With Google client based on [IdConfiguration].
+///
+/// The `initialize` method creates a Sign In With Google client instance that
+/// can be implicitly used by all modules in the same web page.
+///
+/// * You only need to call the `initialize` method once even if you use
+/// multiple modules (like One Tap, Personalized button, revocation, etc.) in
+/// the same web page.
+/// * If you do call the google.accounts.id.initialize method multiple times,
+/// only the configurations in the last call will be remembered and used.
+///
+/// You actually reset the configurations whenever you call the `initialize`
+/// method, and all subsequent methods in the same web page will use the new
+/// configurations immediately.
+///
+/// WARNING: The `initialize` method should be called only once, even if you
+/// use both One Tap and button in the same web page.
+@JS()
+external InitializeFn get initialize;
+
+/// The type of the [initialize] function.
+typedef InitializeFn = void Function(IdConfiguration idConfiguration);
+
+/*
+// Data type: IdConfiguration
+// https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
+*/
+
+/// The configuration object for the [initialize] method.
+@JS()
+@anonymous
+@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({
+ /// Your application's client ID, which is found and created in the Google
+ /// Developers Console.
+ required String client_id,
+
+ /// Determines if an ID token is automatically returned without any user
+ /// interaction when there's only one Google session that has approved your
+ /// app before. The default value is `false`.
+ bool? auto_select,
+
+ /// The function that handles the ID token returned from the One Tap prompt
+ /// or the pop-up window. This attribute is required if Google One Tap or
+ /// the Sign In With Google button `popup` UX mode is used.
+ CallbackFn? callback,
+
+ /// This attribute is the URI of your login endpoint. May be omitted if the
+ /// current page is your login page, in which case the credential is posted
+ /// to this page by default.
+ ///
+ /// The ID token credential response is posted to your login endpoint when
+ /// a user clicks on the Sign In With Google button and `redirect` UX mode
+ /// is used.
+ ///
+ /// Your login endpoint must handle POST requests containing a credential
+ /// key with an ID token value in the body.
+ Uri? login_uri,
+
+ /// The function that handles the password credential returned from the
+ /// browser's native credential manager.
+ NativeCallbackFn? native_callback,
+
+ /// Whether or not to cancel the One Tap request if a user clicks outside
+ /// the prompt. The default value is `true`.
+ bool? cancel_on_tap_outside,
+
+ /// The DOM ID of the container element. If it's not set, the One Tap prompt
+ /// is displayed in the top-right corner of the window.
+ String? prompt_parent_id,
+
+ /// A random string used by the ID token to prevent replay attacks.
+ ///
+ /// Nonce length is limited to the maximum JWT size supported by your
+ /// environment, and individual browser and server HTTP size constraints.
+ String? nonce,
+
+ /// Changes the text of the title and messages in the One Tap prompt.
+ OneTapContext? context,
+
+ /// If you need to display One Tap in the parent domain and its subdomains,
+ /// pass the parent domain to this field so that a single shared-state
+ /// cookie is used.
+ ///
+ /// See: https://developers.google.com/identity/gsi/web/guides/subdomains
+ String? state_cookie_domain,
+
+ /// Set the UX flow used by the Sign In With Google button. The default
+ /// value is `popup`. **This attribute has no impact on the OneTap UX.**
+ UxMode? ux_mode,
+
+ /// The origins that are allowed to embed the intermediate iframe. One Tap
+ /// will run in the intermediate iframe mode if this field presents.
+ ///
+ /// Wildcard prefixes are also supported. Wildcard domains must begin with
+ /// a secure `https://` scheme, otherwise they'll be considered invalid.
+ List<String>? allowed_parent_origin,
+
+ /// Overrides the default intermediate iframe behavior when users manually
+ /// close One Tap by tapping on the 'X' button in the One Tap UI. The
+ /// default behavior is to remove the intermediate iframe from the DOM
+ /// immediately.
+ ///
+ /// The `intermediate_iframe_close_callback` field takes effect only in
+ /// 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,
+
+ /// determines if the upgraded One Tap UX should be enabled on browsers
+ /// that support Intelligent Tracking Prevention (ITP). The default value
+ /// is false.
+ ///
+ /// See: https://developers.google.com/identity/gsi/web/guides/features#upgraded_ux_on_itp_browsers
+ bool? itp_support,
+ });
+}
+
+/*
+// Method: google.accounts.id.prompt
+// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt
+*/
+
+/// The `prompt` method displays the One Tap prompt or the browser native
+/// credential manager after the [initialize] method is invoked.
+///
+/// Normally, the `prompt` method is called on page load. Due to the session
+/// status and user settings on the Google side, the One Tap prompt UI might
+/// not be displayed. To get notified on the UI status for different moments,
+/// pass a [PromptMomentListenerFn] to receive UI status notifications.
+///
+/// Notifications are fired on the following moments:
+///
+/// * Display moment: This occurs after the `prompt` method is called. The
+/// notification contains a boolean value to indicate whether the UI is
+/// displayed or not.
+/// * Skipped moment: This occurs when the One Tap prompt is closed by an auto
+/// cancel, a manual cancel, or when Google fails to issue a credential, such
+/// as when the selected session has signed out of Google.
+/// In these cases, we recommend that you continue on to the next identity
+/// providers, if there are any.
+/// * Dismissed moment: This occurs when Google successfully retrieves a
+/// credential or a user wants to stop the credential retrieval flow. For
+/// example, when the user begins to input their username and password in
+/// your login dialog, you can call the [cancel] method to close the One Tap
+/// prompt and trigger a dismissed moment.
+///
+/// WARNING: When on a dismissed moment, do not try any of the next identity
+/// providers.
+@JS()
+external PromptFn get prompt;
+
+/// The type of the [prompt] function.
+///
+/// The [momentListener] parameter must be manually wrapped in [allowInterop]
+/// before being passed to the [prompt] function.
+typedef PromptFn = void Function(PromptMomentListenerFn? momentListener);
+
+/// The type of the function that can be passed to [prompt] to listen for [PromptMomentNotification]s.
+typedef PromptMomentListenerFn = void Function(PromptMomentNotification moment);
+
+/*
+// Data type: PromptMomentNotification
+// https://developers.google.com/identity/gsi/web/reference/js-reference#PromptMomentNotification
+*/
+
+/// A moment (status) notification from the [prompt] method.
+@JS()
+@staticInterop
+abstract class PromptMomentNotification {}
+
+/// The methods of the [PromptMomentNotification] data type:
+extension PromptMomentNotificationExtension on PromptMomentNotification {
+ /// Is this notification for a display moment?
+ external bool isDisplayMoment();
+
+ /// Is this notification for a display moment, and the UI is displayed?
+ external bool isDisplayed();
+
+ /// Is this notification for a display moment, and the UI isn't displayed?
+ external bool isNotDisplayed();
+
+ /// Is this notification for a skipped moment?
+ external bool 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();
+
+ /// The moment type.
+ MomentType getMomentType() => MomentType.values.byName(_getMomentType());
+
+ /// The detailed reason why the UI isn't displayed.
+ MomentNotDisplayedReason? getNotDisplayedReason() =>
+ maybeEnum(_getNotDisplayedReason(), MomentNotDisplayedReason.values);
+
+ /// The detailed reason for the skipped moment.
+ MomentSkippedReason? getSkippedReason() =>
+ maybeEnum(_getSkippedReason(), MomentSkippedReason.values);
+
+ /// The detailed reason for the dismissal.
+ MomentDismissedReason? getDismissedReason() =>
+ maybeEnum(_getDismissedReason(), MomentDismissedReason.values);
+}
+
+/*
+// Data type: CredentialResponse
+// https://developers.google.com/identity/gsi/web/reference/js-reference#CredentialResponse
+*/
+
+/// The object passed as the parameter of your [CallbackFn].
+@JS()
+@staticInterop
+abstract class CredentialResponse {}
+
+/// The fields that are contained in the credential response object.
+extension CredentialResponseExtension on CredentialResponse {
+ /// 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;
+
+ /// This field sets how the credential was selected.
+ ///
+ /// The type of button used along with the session and consent state are used
+ /// to set the value.
+ ///
+ /// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#select_by
+ CredentialSelectBy get select_by =>
+ CredentialSelectBy.values.byName(_select_by);
+}
+
+/// The type of the `callback` used to create an [IdConfiguration].
+///
+/// Describes a JavaScript function that handles ID tokens from
+/// [CredentialResponse]s.
+///
+/// Google One Tap and the Sign In With Google button popup UX mode use this
+/// attribute.
+typedef CallbackFn = void Function(CredentialResponse credentialResponse);
+
+/*
+// Method: google.accounts.id.renderButton
+// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.renderButton
+//
+// Data type: GsiButtonConfiguration
+// https://developers.google.com/identity/gsi/web/reference/js-reference#GsiButtonConfiguration
+//
+// Question: Do we need to implement renderButton and its options?
+*/
+
+/*
+// Data type: Credential
+// https://developers.google.com/identity/gsi/web/reference/js-reference#type-Credential
+*/
+
+/// The object passed to the [NativeCallbackFn]. Represents a PasswordCredential
+/// that was returned by the Browser.
+///
+/// `Credential` objects can also be programmatically created to be stored
+/// in the browser through the [storeCredential] method.
+///
+/// See also: https://developer.mozilla.org/en-US/docs/Web/API/PasswordCredential/PasswordCredential
+@JS()
+@anonymous
+@staticInterop
+abstract class Credential {
+ ///
+ external factory Credential({
+ required String id,
+ required String password,
+ });
+}
+
+/// The fields that are contained in the [Credential] object.
+extension CredentialExtension on Credential {
+ /// Identifies the user.
+ external String get id;
+
+ /// The password.
+ external String get password;
+}
+
+/// The type of the `native_callback` used to create an [IdConfiguration].
+///
+/// Describes a JavaScript function that handles password [Credential]s coming
+/// from the native Credential manager of the user's browser.
+typedef NativeCallbackFn = void Function(Credential credential);
+
+/*
+// Method: google.accounts.id.disableAutoselect
+// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.disableAutoSelect
+*/
+
+/// When the user signs out of your website, you need to call this method to
+/// record the status in cookies.
+///
+/// This prevents a UX dead loop.
+@JS()
+external VoidFn get disableAutoSelect;
+
+/*
+// Method: google.accounts.id.storeCredential
+// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.storeCredential
+*/
+
+/// This method is a simple wrapper for the `store` method of the browser's
+/// native credential manager API.
+///
+/// It can only be used to store a Password [Credential].
+///
+/// See: https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/store
+@JS()
+external StoreCredentialFn get storeCredential;
+
+/// The type of the [storeCredential] function.
+///
+/// The [callback] parameter must be manually wrapped in [allowInterop]
+/// before being passed to the [storeCredential] function.
+// Question: What's the type of the callback function??? VoidFn?
+typedef StoreCredentialFn = void Function(
+ Credential credential,
+ Function? callback,
+);
+
+/*
+// Method: google.accounts.id.cancel
+// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.cancel
+*/
+
+/// You can cancel the One Tap flow if you remove the prompt from the relying
+/// party DOM. The cancel operation is ignored if a credential is already
+/// selected.
+@JS()
+external VoidFn get cancel;
+
+/*
+// Library load callback: onGoogleLibraryLoad
+// https://developers.google.com/identity/gsi/web/reference/js-reference#onGoogleLibraryLoad
+// See: `load_callback.dart` and `loader.dart`
+*/
+
+/*
+// Method: google.accounts.id.revoke
+// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.revoke
+*/
+
+/// The `revoke` method revokes the OAuth grant used to share the ID token for
+/// the specified user.
+@JS()
+external RevokeFn get revoke;
+
+/// The type of the [revoke] function.
+///
+/// [hint] is the email address or unique ID of the user's Google Account. The
+/// ID is the `sub` property of the [CredentialResponse.credential] payload.
+///
+/// The optional [callback] is a function that gets called to report on the
+/// success of the revocation call.
+///
+/// The [callback] parameter must be manually wrapped in [allowInterop]
+/// before being passed to the [revoke] function.
+typedef RevokeFn = void Function(
+ String hint,
+ RevocationResponseHandlerFn? callback,
+);
+
+/// The type of the `callback` function passed to [revoke], to be notified of
+/// the success of the revocation operation.
+typedef RevocationResponseHandlerFn = void Function(
+ RevocationResponse revocationResponse,
+);
+
+/*
+// Data type: RevocationResponse
+// https://developers.google.com/identity/gsi/web/reference/js-reference#RevocationResponse
+*/
+
+/// The parameter passed to the optional [RevocationResponseHandlerFn]
+/// `callback` of the [revoke] function.
+@JS()
+@staticInterop
+abstract class RevocationResponse {}
+
+/// The fields that are contained in the [RevocationResponse] object.
+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;
+
+ /// 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;
+}
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
new file mode 100644
index 0000000..3986138
--- /dev/null
+++ b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_oauth2.dart
@@ -0,0 +1,339 @@
+// 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.
+
+// Authorization. API reference:
+// https://developers.google.com/identity/oauth2/web/reference/js-reference
+
+// 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.
+
+@JS('google.accounts.oauth2')
+library oauth2;
+
+import 'package:js/js.dart';
+
+import 'shared.dart';
+
+// Code Client
+
+/*
+// Method: google.accounts.oauth2.initCodeClient
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initCodeClient
+*/
+
+/// The initCodeClient method initializes and returns a code client, with the
+/// passed-in [config].
+@JS()
+external InitCodeClientFn get initCodeClient;
+
+/// The type of the [initCodeClient] function.
+typedef InitCodeClientFn = CodeClient Function(CodeClientConfig config);
+
+/*
+// Data type: CodeClientConfig
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig
+*/
+
+/// The configuration object for the [initCodeClient] method.
+@JS()
+@anonymous
+@staticInterop
+abstract class CodeClientConfig {
+ /// Constructs a CodeClientConfig object in JavaScript.
+ ///
+ /// The [callback] property must be wrapped in [allowInterop] before it's
+ /// passed to this constructor.
+ external factory CodeClientConfig({
+ required String client_id,
+ required String scope,
+ String? redirect_uri,
+ bool? auto_select,
+ CodeClientCallbackFn? callback,
+ String? state,
+ bool? enable_serial_consent,
+ String? hint,
+ String? hosted_domain,
+ UxMode? ux_mode,
+ bool? select_account,
+ });
+}
+
+/*
+// Data type: CodeClient
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClient
+*/
+
+/// A client that can start the OAuth 2.0 Code UX flow.
+///
+/// See: https://developers.google.com/identity/oauth2/web/guides/use-code-model
+@JS()
+@staticInterop
+abstract class CodeClient {}
+
+/// The methods available on the [CodeClient].
+extension CodeClientExtension on CodeClient {
+ /// Starts the OAuth 2.0 Code UX flow.
+ external void requestCode();
+}
+
+/*
+// Data type: CodeResponse
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse
+*/
+
+/// The object passed as the parameter of your [CodeClientCallbackFn].
+@JS()
+@staticInterop
+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;
+
+ /// A space-delimited list of scopes that are approved by the user.
+ external String get scope;
+
+ /// The string value that your application uses to maintain state between your
+ /// authorization request and the response.
+ external String get state;
+
+ /// A single ASCII error code.
+ external String? 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;
+
+ /// 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;
+}
+
+/// The type of the `callback` function passed to [CodeClientConfig].
+typedef CodeClientCallbackFn = void Function(CodeResponse response);
+
+// Token Client
+
+/*
+// Method: google.accounts.oauth2.initTokenClient
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient
+*/
+
+/// The initCodeClient method initializes and returns a code client, with the
+/// passed-in [config].
+@JS()
+external InitTokenClientFn get initTokenClient;
+
+/// The type of the [initCodeClient] function.
+typedef InitTokenClientFn = TokenClient Function(TokenClientConfig config);
+
+/*
+// Data type: TokenClientConfig
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClientConfig
+*/
+
+/// The configuration object for the [initTokenClient] method.
+@JS()
+@anonymous
+@staticInterop
+abstract class TokenClientConfig {
+ /// Constructs a TokenClientConfig object in JavaScript.
+ ///
+ /// The [callback] property must be wrapped in [allowInterop] before it's
+ /// passed to this constructor.
+ external factory TokenClientConfig({
+ required String client_id,
+ required TokenClientCallbackFn? callback,
+ required String scope,
+ String? prompt,
+ bool? enable_serial_consent,
+ String? hint,
+ String? hosted_domain,
+ String? state,
+ });
+}
+
+/*
+// Data type: TokenClient
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClient
+*/
+
+/// A client that can start the OAuth 2.0 Token UX flow.
+///
+/// See: https://developers.google.com/identity/oauth2/web/guides/use-token-model
+@JS()
+@staticInterop
+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,
+ ]);
+}
+
+/*
+// Data type: OverridableTokenClientConfig
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#OverridableTokenClientConfig
+*/
+
+/// The overridable configuration object for the
+/// [TokenClientExtension.requestAccessToken] method.
+@JS()
+@anonymous
+@staticInterop
+abstract class OverridableTokenClientConfig {
+ /// Constructs an OverridableTokenClientConfig object in JavaScript.
+ ///
+ /// The [callback] property must be wrapped in [allowInterop] before it's
+ /// passed to this constructor.
+ external factory OverridableTokenClientConfig({
+ /// 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".
+ ///
+ /// This setting has no effect in newer clients.
+ ///
+ /// See: https://developers.googleblog.com/2018/10/more-granular-google-account.html
+ bool? enable_serial_consent,
+
+ /// When your app knows which user it is trying to authenticate, it can
+ /// provide this parameter as a hint to the authentication server. Passing
+ /// this hint suppresses the account chooser and either pre-fills the email
+ /// box on the sign-in form, or selects the proper session (if the user is
+ /// using multiple sign-in), which can help you avoid problems that occur if
+ /// your app logs in the wrong user account.
+ ///
+ /// The value can be either an email address or the `sub` string, which is
+ /// equivalent to the user's Google ID.
+ ///
+ /// About Multiple Sign-in: https://support.google.com/accounts/answer/1721977
+ String? 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,
+ });
+}
+
+/*
+// Data type: TokenResponse
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
+*/
+
+/// The object passed as the parameter of your [TokenClientCallbackFn].
+@JS()
+@staticInterop
+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;
+
+ /// The lifetime in seconds of the access token.
+ external int get expires_in;
+
+ /// The hosted domain the signed-in user belongs to.
+ external String get hd;
+
+ /// The prompt value that was used from the possible list of values specified
+ /// by [TokenClientConfig] or [OverridableTokenClientConfig].
+ external String get prompt;
+
+ /// The type of the token issued.
+ external String get token_type;
+
+ /// A space-delimited list of scopes that are approved by the user.
+ external String get scope;
+
+ /// The string value that your application uses to maintain state between your
+ /// authorization request and the response.
+ external String get state;
+
+ /// A single ASCII error code.
+ external String? 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;
+
+ /// 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;
+}
+
+/// The type of the `callback` function passed to [TokenClientConfig].
+typedef TokenClientCallbackFn = void Function(TokenResponse response);
+
+/*
+// Method: google.accounts.oauth2.hasGrantedAllScopes
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
+*/
+
+/// Checks if the user granted **all** the specified scopes.
+@JS()
+external HasGrantedScopesFn get hasGrantedAllScopes;
+
+/*
+// Method: google.accounts.oauth2.hasGrantedAnyScopes
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAnyScopes
+*/
+
+/// Checks if the user granted **any** of the specified scopes.
+@JS()
+external HasGrantedScopesFn get hasGrantedAnyScopes;
+
+/// The signature for functions that check if any/all scopes have been granted.
+///
+/// Used by [hasGrantedAllScopes] and [hasGrantedAnyScope].
+typedef HasGrantedScopesFn = bool Function(
+ TokenResponse tokenResponse,
+ String firstScope, [
+ String? scope2,
+ String? scope3,
+ String? scope4,
+ String? scope5,
+ String? scope6,
+ String? scope7,
+ String? scope8,
+ String? scope9,
+ String? scope10,
+]);
+
+/*
+// Method: google.accounts.oauth2.revoke
+// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke
+*/
+
+/// The [revokeToken] method revokes all of the scopes that the user granted to
+/// the app. A valid access token is required to revoke the permission.
+///
+/// The `done` callback is called once the revoke action is done.
+@JS('revoke')
+external RevokeTokenFn get revokeToken;
+
+/// The signature of the [revokeToken] function.
+///
+/// The (optional) [done] parameter must be manually wrapped in [allowInterop]
+/// before being passed to the [revokeToken] function.
+typedef RevokeTokenFn = void Function(
+ String accessToken, [
+ RevokeTokenDoneFn? done,
+]);
+
+/// The signature of the `done` function for [revokeToken].
+///
+/// Work in progress here: b/248628502
+typedef RevokeTokenDoneFn = void Function(String jsonError);
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
new file mode 100644
index 0000000..fa7c4ca
--- /dev/null
+++ b/packages/google_identity_services_web/lib/src/js_interop/load_callback.dart
@@ -0,0 +1,26 @@
+// 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.
+
+// Methods here are documented in the Google Identity authentication website,
+// but they don't really belong to either the authentication nor authorization
+// libraries.
+@JS()
+library id_load_callback;
+
+import 'package:js/js.dart';
+
+import 'shared.dart';
+
+/*
+// Library load callback: onGoogleLibraryLoad
+// https://developers.google.com/identity/gsi/web/reference/js-reference#onGoogleLibraryLoad
+*/
+
+/// 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);
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
new file mode 100644
index 0000000..4e35f38
--- /dev/null
+++ b/packages/google_identity_services_web/lib/src/js_interop/shared.dart
@@ -0,0 +1,215 @@
+// 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.
+
+/// Attempts to retrieve an enum value from [haystack] if [needle] is not null.
+T? maybeEnum<T extends Enum>(String? needle, List<T> haystack) {
+ if (needle == null) {
+ return null;
+ }
+ return haystack.byName(needle);
+}
+
+/// The type of several functions from the library, that don't receive
+/// parameters nor return anything.
+typedef VoidFn = void Function();
+
+/*
+// Enum: UX Mode
+// https://developers.google.com/identity/gsi/web/reference/js-reference#ux_mode
+// Used both by `oauth2.initCodeClient` and `id.initialize`.
+*/
+
+/// Use this enum to set the UX flow used by the Sign In With Google button.
+/// The default value is [popup].
+///
+/// This attribute has no impact on the OneTap UX.
+enum UxMode {
+ /// Performs sign-in UX flow in a pop-up window.
+ popup('popup'),
+
+ /// Performs sign-in UX flow by a full page redirection.
+ redirect('redirect');
+
+ ///
+ const UxMode(String uxMode) : _uxMode = uxMode;
+ final String _uxMode;
+
+ @override
+ String toString() => _uxMode;
+}
+
+/// Changes the text of the title and messages in the One Tap prompt.
+enum OneTapContext {
+ /// "Sign in with Google"
+ signin('signin'),
+
+ /// "Sign up with Google"
+ signup('signup'),
+
+ /// "Use with Google"
+ use('use');
+
+ ///
+ const OneTapContext(String context) : _context = context;
+ final String _context;
+
+ @override
+ String toString() => _context;
+}
+
+/// The detailed reason why the OneTap UI isn't displayed.
+enum MomentNotDisplayedReason {
+ /// Browser not supported.
+ ///
+ /// See https://developers.google.com/identity/gsi/web/guides/supported-browsers
+ browser_not_supported('browser_not_supported'),
+
+ /// Invalid Client.
+ ///
+ /// See https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid
+ invalid_client('invalid_client'),
+
+ /// Missing client_id.
+ ///
+ /// See https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid
+ missing_client_id('missing_client_id'),
+
+ /// The user has opted out, or they aren't signed in to a Google account.
+ ///
+ /// https://developers.google.com/identity/gsi/web/guides/features
+ opt_out_or_no_session('opt_out_or_no_session'),
+
+ /// Google One Tap can only be displayed in HTTPS domains.
+ ///
+ /// See https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid
+ secure_http_required('secure_http_required'),
+
+ /// The user has previously closed the OneTap card.
+ ///
+ /// See https://developers.google.com/identity/gsi/web/guides/features#exponential_cooldown
+ suppressed_by_user('suppressed_by_user'),
+
+ /// The current `origin` is not associated with the Client ID.
+ ///
+ /// See https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid
+ unregistered_origin('unregistered_origin'),
+
+ /// Unknown reason
+ unknown_reason('unknown_reason');
+
+ ///
+ const MomentNotDisplayedReason(String reason) : _reason = reason;
+ final String _reason;
+
+ @override
+ String toString() => _reason;
+}
+
+/// The detailed reason for the skipped moment.
+enum MomentSkippedReason {
+ /// auto_cancel
+ auto_cancel('auto_cancel'),
+
+ /// user_cancel
+ user_cancel('user_cancel'),
+
+ /// tap_outside
+ tap_outside('tap_outside'),
+
+ /// issuing_failed
+ issuing_failed('issuing_failed');
+
+ ///
+ const MomentSkippedReason(String reason) : _reason = reason;
+ final String _reason;
+
+ @override
+ String toString() => _reason;
+}
+
+/// The detailed reason for the dismissal.
+enum MomentDismissedReason {
+ /// credential_returned
+ credential_returned('credential_returned'),
+
+ /// cancel_called
+ cancel_called('cancel_called'),
+
+ /// flow_restarted
+ flow_restarted('flow_restarted');
+
+ ///
+ const MomentDismissedReason(String reason) : _reason = reason;
+ final String _reason;
+
+ @override
+ String toString() => _reason;
+}
+
+/// The moment type.
+enum MomentType {
+ /// Display moment
+ display('display'),
+
+ /// Skipped moment
+ skipped('skipped'),
+
+ /// Dismissed moment
+ dismissed('dismissed');
+
+ ///
+ const MomentType(String type) : _type = type;
+ final String _type;
+
+ @override
+ String toString() => _type;
+}
+
+/// Represents how a credential was selected.
+enum CredentialSelectBy {
+ /// Automatic sign-in of a user with an existing session who had previously
+ /// granted consent to share credentials.
+ auto('auto'),
+
+ /// A user with an existing session who had previously granted consent
+ /// pressed the One Tap 'Continue as' button to share credentials.
+ user('user'),
+
+ /// A user with an existing session pressed the One Tap 'Continue as' button
+ /// to grant consent and share credentials. Applies only to Chrome v75 and
+ /// higher.
+ user_1tap('user_1tap'),
+
+ /// A user without an existing session pressed the One Tap 'Continue as'
+ /// button to select an account and then pressed the Confirm button in a
+ /// pop-up window to grant consent and share credentials. Applies to
+ /// non-Chromium based browsers.
+ user_2tap('user_2tap'),
+
+ /// A user with an existing session who previously granted consent pressed
+ /// the Sign In With Google button and selected a Google Account from
+ /// 'Choose an Account' to share credentials.
+ btn('btn'),
+
+ /// A user with an existing session pressed the Sign In With Google button
+ /// and pressed the Confirm button to grant consent and share credentials.
+ btn_confirm('btn_confirm'),
+
+ /// A user without an existing session who previously granted consent
+ /// pressed the Sign In With Google button to select a Google Account and
+ /// share credentials.
+ btn_add_session('btn_add_session'),
+
+ /// 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');
+
+ ///
+ const CredentialSelectBy(String selectBy) : _selectBy = selectBy;
+ final String _selectBy;
+
+ @override
+ String toString() => _selectBy;
+}
diff --git a/packages/google_identity_services_web/lib/src/js_loader.dart b/packages/google_identity_services_web/lib/src/js_loader.dart
new file mode 100644
index 0000000..0f2dc7b
--- /dev/null
+++ b/packages/google_identity_services_web/lib/src/js_loader.dart
@@ -0,0 +1,67 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:js/js.dart' show allowInterop;
+
+import 'js_interop/dom.dart';
+import 'js_interop/load_callback.dart';
+
+// The URL from which the script should be downloaded.
+const String _url = 'https://accounts.google.com/gsi/client';
+
+// The default TrustedPolicy name that will be used to inject the script.
+const String _defaultTrustedPolicyName = 'gis-dart';
+
+/// Loads the GIS SDK for web, using Trusted Types API when available.
+Future<void> loadWebSdk({
+ DomHtmlElement? target,
+ String trustedTypePolicyName = _defaultTrustedPolicyName,
+}) {
+ final Completer<void> completer = Completer<void>();
+ onGoogleLibraryLoad = allowInterop(() => completer.complete());
+
+ // If TrustedTypes are available, prepare a trusted URL.
+ DomTrustedScriptUrl? trustedUrl;
+ if (trustedTypes != null) {
+ console.debug(
+ 'TrustedTypes available. Creating policy:',
+ trustedTypePolicyName,
+ );
+ final DomTrustedTypePolicyFactory factory = trustedTypes!;
+ try {
+ final DomTrustedTypePolicy policy = factory.createPolicy(
+ trustedTypePolicyName,
+ DomTrustedTypePolicyOptions(
+ createScriptURL: allowInterop((String url) => _url),
+ ));
+ trustedUrl = policy.createScriptURL(_url);
+ } catch (e) {
+ throw TrustedTypesException(e.toString());
+ }
+ }
+
+ final DomHtmlScriptElement script =
+ document.createElement('script') as DomHtmlScriptElement
+ ..src = trustedUrl ?? _url
+ ..async = true
+ ..defer = true;
+
+ (target ?? document.head).appendChild(script);
+
+ return completer.future;
+}
+
+/// Exception thrown if the Trusted Types feature is supported, enabled, and it
+/// has prevented this loader from injecting the JS SDK.
+class TrustedTypesException implements Exception {
+ ///
+ TrustedTypesException(this.message);
+
+ /// The message of the exception
+ final String message;
+ @override
+ String toString() => 'TrustedTypesException: $message';
+}
diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml
new file mode 100644
index 0000000..d84a687
--- /dev/null
+++ b/packages/google_identity_services_web/pubspec.yaml
@@ -0,0 +1,16 @@
+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.1.0
+
+environment:
+ sdk: ">=2.17.0 <3.0.0"
+
+dependencies:
+ js: ^0.6.4
+ meta: ^1.3.0
+
+dev_dependencies:
+ path: ^1.8.1
+ test: ^1.21.1
diff --git a/packages/google_identity_services_web/test/README.md b/packages/google_identity_services_web/test/README.md
new file mode 100644
index 0000000..8a783c2
--- /dev/null
+++ b/packages/google_identity_services_web/test/README.md
@@ -0,0 +1,3 @@
+# Tests
+
+Use `dart run tool/run_tests.dart` to run tests in this package.
diff --git a/packages/google_identity_services_web/test/js_loader_test.dart b/packages/google_identity_services_web/test/js_loader_test.dart
new file mode 100644
index 0000000..a9d857b
--- /dev/null
+++ b/packages/google_identity_services_web/test/js_loader_test.dart
@@ -0,0 +1,66 @@
+// 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.
+
+@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:google_identity_services_web/src/js_loader.dart';
+
+import 'package:js/js_util.dart' as js_util;
+
+import 'package:test/test.dart';
+
+// NOTE: This file needs to be separated from the others because Content
+// Security Policies can never be *relaxed* once set.
+//
+// In order to not introduce a dependency in the order of the tests, we split
+// them in different files, depending on the strictness of their CSP:
+//
+// * js_loader_test.dart : default TT configuration (not enforced)
+// * js_loader_tt_custom_test.dart : TT are customized, but allowed
+// * js_loader_tt_forbidden_test.dart: TT are completely disallowed
+
+void main() {
+ group('loadWebSdk (no TrustedTypes)', () {
+ final dom.DomHtmlElement target = dom.document.createElement('div');
+
+ test('Injects script into desired target', () async {
+ loadWebSdk(target: target);
+
+ // Target now should have a child that is a script element
+ final Object children = js_util.getProperty<Object>(target, 'children');
+ final Object injected = js_util.callMethod<Object>(
+ children,
+ 'item',
+ <Object>[0],
+ );
+ expect(injected, isA<dom.DomHtmlScriptElement>());
+
+ final dom.DomHtmlScriptElement script =
+ injected as dom.DomHtmlScriptElement;
+ expect(js_util.getProperty<bool>(script, 'defer'), isTrue);
+ expect(js_util.getProperty<bool>(script, 'async'), isTrue);
+ expect(
+ js_util.getProperty<String>(script, 'src'),
+ 'https://accounts.google.com/gsi/client',
+ );
+ });
+
+ test('Completes when the script loads', () async {
+ final Future<void> loadFuture = loadWebSdk(target: target);
+
+ Future<void>.delayed(const Duration(milliseconds: 100), () {
+ // Simulate the library calling `window.onGoogleLibraryLoad`.
+ js_util.callMethod<void>(
+ js_util.globalThis,
+ 'onGoogleLibraryLoad',
+ <Object>[],
+ );
+ });
+
+ await expectLater(loadFuture, completes);
+ });
+ });
+}
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
new file mode 100644
index 0000000..6668be0
--- /dev/null
+++ b/packages/google_identity_services_web/test/js_loader_tt_custom_test.dart
@@ -0,0 +1,47 @@
+// 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.
+
+@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 'tools.dart';
+
+// NOTE: This file needs to be separated from the others because Content
+// Security Policies can never be *relaxed* once set.
+//
+// In order to not introduce a dependency in the order of the tests, we split
+// them in different files, depending on the strictness of their CSP:
+//
+// * js_loader_test.dart : default TT configuration (not enforced)
+// * js_loader_tt_custom_test.dart : TT are customized, but allowed
+// * js_loader_tt_forbidden_test.dart: TT are completely disallowed
+
+void main() {
+ group('loadWebSdk (TrustedTypes configured)', () {
+ final dom.DomHtmlElement target = dom.document.createElement('div');
+ injectMetaTag(<String, String>{
+ 'http-equiv': 'Content-Security-Policy',
+ 'content': "trusted-types my-custom-policy-name 'allow-duplicates';",
+ });
+
+ test('Wrong policy name: Fail with TrustedTypesException', () {
+ expect(() {
+ loadWebSdk(target: target);
+ }, throwsA(isA<TrustedTypesException>()));
+ });
+
+ test('Correct policy name: Completes', () {
+ final Future<void> done = loadWebSdk(
+ target: target,
+ trustedTypePolicyName: 'my-custom-policy-name',
+ );
+
+ expect(done, isA<Future<void>>());
+ });
+ });
+}
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
new file mode 100644
index 0000000..e95b5f9
--- /dev/null
+++ b/packages/google_identity_services_web/test/js_loader_tt_forbidden_test.dart
@@ -0,0 +1,38 @@
+// 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.
+
+@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 'tools.dart';
+
+// NOTE: This file needs to be separated from the others because Content
+// Security Policies can never be *relaxed* once set.
+//
+// In order to not introduce a dependency in the order of the tests, we split
+// them in different files, depending on the strictness of their CSP:
+//
+// * js_loader_test.dart : default TT configuration (not enforced)
+// * js_loader_tt_custom_test.dart : TT are customized, but allowed
+// * js_loader_tt_forbidden_test.dart: TT are completely disallowed
+
+void main() {
+ group('loadWebSdk (TrustedTypes forbidden)', () {
+ final dom.DomHtmlElement target = dom.document.createElement('div');
+ injectMetaTag(<String, String>{
+ 'http-equiv': 'Content-Security-Policy',
+ 'content': "trusted-types 'none';",
+ });
+
+ test('Fail with TrustedTypesException', () {
+ expect(() {
+ loadWebSdk(target: target);
+ }, throwsA(isA<TrustedTypesException>()));
+ });
+ });
+}
diff --git a/packages/google_identity_services_web/test/only_chrome_tests_here_test.dart b/packages/google_identity_services_web/test/only_chrome_tests_here_test.dart
new file mode 100644
index 0000000..6c33ea9
--- /dev/null
+++ b/packages/google_identity_services_web/test/only_chrome_tests_here_test.dart
@@ -0,0 +1,18 @@
+// 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: avoid_print
+
+@TestOn('vm')
+
+import 'package:test/test.dart';
+
+void main() {
+ test('Tell the user where to find the real tests', () {
+ print('---');
+ print('This package uses `dart test -p chrome` for its tests.');
+ print('See `README.md` for more info.');
+ print('---');
+ });
+}
diff --git a/packages/google_identity_services_web/test/tools.dart b/packages/google_identity_services_web/test/tools.dart
new file mode 100644
index 0000000..dd3d68e
--- /dev/null
+++ b/packages/google_identity_services_web/test/tools.dart
@@ -0,0 +1,19 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:google_identity_services_web/src/js_interop/dom.dart' as dom;
+import 'package:js/js_util.dart' as js_util;
+
+/// Injects a `<meta>` tag with the provided [attributes] into the [dom.document].
+void injectMetaTag(Map<String, String> attributes) {
+ final dom.DomHtmlElement meta = dom.document.createElement('meta');
+ for (final MapEntry<String, String> attribute in attributes.entries) {
+ js_util.callMethod(
+ meta,
+ 'setAttribute',
+ <String>[attribute.key, attribute.value],
+ );
+ }
+ dom.document.head.appendChild(meta);
+}
diff --git a/packages/google_identity_services_web/tool/run_tests.dart b/packages/google_identity_services_web/tool/run_tests.dart
new file mode 100644
index 0000000..1997e46
--- /dev/null
+++ b/packages/google_identity_services_web/tool/run_tests.dart
@@ -0,0 +1,49 @@
+// 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.
+
+// Runs `dart test -p chrome` in the root of the google_identity_services_web package.
+//
+// Called from the custom-tests CI action.
+//
+// usage: dart run tool/run_tests.dart
+// (needs a `chrome` executable in $PATH, or a tweak to dart_test.yaml)
+import 'dart:io';
+import 'package:path/path.dart' as p;
+
+Future<void> main(List<String> args) async {
+ final Directory workingDir =
+ Directory(p.dirname(Platform.script.path)).parent;
+
+ final int status = await _runProcess(
+ 'dart',
+ <String>[
+ 'test',
+ '-p',
+ 'chrome',
+ ],
+ workingDirectory: workingDir.path,
+ );
+
+ exit(status);
+}
+
+Future<Process> _streamOutput(Future<Process> processFuture) async {
+ final Process process = await processFuture;
+ stdout.addStream(process.stdout);
+ stderr.addStream(process.stderr);
+ return process;
+}
+
+Future<int> _runProcess(
+ String command,
+ List<String> arguments, {
+ String? workingDirectory,
+}) async {
+ final Process process = await _streamOutput(Process.start(
+ command,
+ arguments,
+ workingDirectory: workingDirectory,
+ ));
+ return process.exitCode;
+}
diff --git a/script/configs/linux_only_custom_test.yaml b/script/configs/linux_only_custom_test.yaml
new file mode 100644
index 0000000..8313d41
--- /dev/null
+++ b/script/configs/linux_only_custom_test.yaml
@@ -0,0 +1,9 @@
+# Packages that only support `custom-test` in linux, because that's
+# the only place where we install Chrome.
+#
+# This file is used to "--exclude" these packages from the relevant
+# mac/windows CI test runs.
+
+- cross_file
+- google_identity_services_web
+