[gis_web] Adds id.renderButton JS-interop. (#3011)

* [gis_web] Adds id.renderButton API.

* Modernizes JS-interop so it's more compliant with dart2wasm.
* Updates examples, tests and docs.
* Bumps major version.

* Add the GsiButtonDataExtension class.

* Make oauth2 library more dart2wasm friendly.

* Reimplement hasGrantedA[ny|ll]Scopes in Dart.

* Fix oauth example.

* Added troubleshooting section to README.

* Add happy case tests for the oauth flow.

* Fix typo in config constructors.

* dart format

* Add some error handling to the library

* Add previously_granted_scopes field to overridable token config.

Make scopes a List of Strings in the hasGranted[Any|All]Scopes method.
This commit is contained in:
David Iglesias
2023-01-10 14:53:16 -08:00
committed by GitHub
parent b4bce7db3e
commit b6fe67e49f
15 changed files with 916 additions and 455 deletions

View File

@ -1,3 +1,11 @@
## 0.2.0
* Adds `renderButton` API to `id.dart`.
* **Breaking Change:** Makes JS-interop API more `dart2wasm`-friendly.
* Removes external getters for function types
* Introduces an external getter for the whole libraries instead.
* Updates `README.md` with the new way of `import`ing the desired libraries.
## 0.1.1 ## 0.1.1
* Add optional `scope` to `OverridableTokenClientConfig` object. * Add optional `scope` to `OverridableTokenClientConfig` object.

View File

@ -65,9 +65,29 @@ behind a [conditional import/export](https://dart.dev/guides/libraries/create-li
Once the SDK has been loaded, it can be used by importing the correct library: 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/id.dart';` for Authentication.
* `import 'package:google_identity_services/oauth2.dart' as oauth2;` for * This will expose an `id` JSObject that binds to `google.accounts.id`.
Authorization. * `import 'package:google_identity_services/oauth2.dart';` for Authorization.
* This will expose an `oauth2` JSObject that binds to `google.accounts.oauth2`.
### Troubleshooting
Watch the browser's development tools JS console while using this package.
Information about errors during initialization and use of the library will be
displayed there.
Some common issues identified so far:
#### The given origin is not allowed for the given client ID
> When you perform local tests or development, **you must add both**
> `http://localhost` and `http://localhost:<port_number>` to the
> **Authorized JavaScript origins** box.
> The [Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)
> response header must also be set to `no-referrer-when-downgrade` when using
> http and localhost.
* Read more: [Sign In with Google for Web - Setup - Get your Google API client ID](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#get_your_google_api_client_id).
## Browser compatibility ## Browser compatibility

View File

@ -1,101 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_identity_services_web/id.dart' as id;
import 'package:google_identity_services_web/src/js_interop/dom.dart';
import 'package:integration_test/integration_test.dart';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
@JS('window')
external Object get domWindow;
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
// Load web/mock-gis.js in the page
await installGisMock();
});
group('prompt', () {
testWidgets('supports a moment notification callback', (_) async {
id.initialize(id.IdConfiguration(client_id: 'testing_1-2-3'));
final StreamController<id.PromptMomentNotification> controller =
StreamController<id.PromptMomentNotification>();
id.prompt(allowInterop(controller.add));
final id.PromptMomentNotification moment = await controller.stream.first;
// These defaults are set in mock-gis.js
expect(moment.getMomentType(), id.MomentType.skipped);
expect(moment.getSkippedReason(), id.MomentSkippedReason.user_cancel);
});
testWidgets('calls config callback with credential response', (_) async {
const String expected = 'should_be_a_proper_jwt_token';
setMockCredentialResponse(expected);
final StreamController<id.CredentialResponse> controller =
StreamController<id.CredentialResponse>();
id.initialize(id.IdConfiguration(
client_id: 'testing_1-2-3',
callback: allowInterop(controller.add),
));
id.prompt();
final id.CredentialResponse response = await controller.stream.first;
expect(response.credential, expected);
});
});
}
/// Installs mock-gis.js in the page.
/// Returns a future that completes when the 'load' event of the script fires.
Future<void> installGisMock() {
final Completer<void> completer = Completer<void>();
final DomHtmlScriptElement script =
document.createElement('script') as DomHtmlScriptElement;
script.src = 'mock-gis.js';
setProperty(script, 'type', 'module');
callMethod(script, 'addEventListener', <Object>[
'load',
allowInterop((_) {
completer.complete();
})
]);
document.head.appendChild(script);
return completer.future;
}
void setMockCredentialResponse([String value = 'default_value']) {
callMethod(
_getGoogleAccountsId(),
'setMockCredentialResponse',
<Object>[value, 'auto'],
);
}
Object _getGoogleAccountsId() {
return _getDeepProperty<Object>(domWindow, 'google.accounts.id');
}
// Attempts to retrieve a deeply nested property from a jsObject (or die tryin')
T _getDeepProperty<T>(Object jsObject, String deepProperty) {
final List<String> properties = deepProperty.split('.');
return properties.fold(
jsObject,
(Object jsObj, String prop) => getProperty<Object>(jsObj, prop),
) as T;
}

View File

@ -0,0 +1,57 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_identity_services_web/id.dart';
import 'package:integration_test/integration_test.dart';
import 'package:js/js.dart';
import 'utils.dart' as utils;
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
// Load web/mock-gis.js in the page
await utils.installGisMock();
});
group('prompt', () {
testWidgets('supports a moment notification callback', (_) async {
id.initialize(IdConfiguration(client_id: 'testing_1-2-3'));
final StreamController<PromptMomentNotification> controller =
StreamController<PromptMomentNotification>();
id.prompt(allowInterop(controller.add));
final PromptMomentNotification moment = await controller.stream.first;
// These defaults are set in mock-gis.js
expect(moment.getMomentType(), MomentType.skipped);
expect(moment.getSkippedReason(), MomentSkippedReason.user_cancel);
});
testWidgets('calls config callback with credential response', (_) async {
const String expected = 'should_be_a_proper_jwt_token';
utils.setMockCredentialResponse(expected);
final StreamController<CredentialResponse> controller =
StreamController<CredentialResponse>();
id.initialize(IdConfiguration(
client_id: 'testing_1-2-3',
callback: allowInterop(controller.add),
));
id.prompt();
final CredentialResponse response = await controller.stream.first;
expect(response.credential, expected);
});
});
}

View File

@ -0,0 +1,124 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_identity_services_web/oauth2.dart';
import 'package:integration_test/integration_test.dart';
import 'package:js/js.dart';
import 'utils.dart' as utils;
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
// Load web/mock-gis.js in the page
await utils.installGisMock();
});
group('initTokenClient', () {
testWidgets('returns a tokenClient', (_) async {
final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
client_id: 'for-tests',
callback: null,
scope: 'some_scope for_tests not_real',
));
expect(client, isNotNull);
});
});
group('requestAccessToken', () {
testWidgets('passes through configuration', (_) async {
final StreamController<TokenResponse> controller =
StreamController<TokenResponse>();
final List<String> scopes = <String>['some_scope', 'another', 'more'];
final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
client_id: 'for-tests',
callback: allowInterop(controller.add),
scope: scopes.join(' '),
));
utils.setMockTokenResponse(client, 'some-non-null-auth-token-value');
client.requestAccessToken();
final TokenResponse response = await controller.stream.first;
expect(response, isNotNull);
expect(response.error, isNull);
expect(response.scope, scopes.join(' '));
});
testWidgets('configuration can be overridden', (_) async {
final StreamController<TokenResponse> controller =
StreamController<TokenResponse>();
final List<String> scopes = <String>['some_scope', 'another', 'more'];
final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
client_id: 'for-tests',
callback: allowInterop(controller.add),
scope: 'blank',
));
utils.setMockTokenResponse(client, 'some-non-null-auth-token-value');
client.requestAccessToken(OverridableTokenClientConfig(
scope: scopes.join(' '),
));
final TokenResponse response = await controller.stream.first;
expect(response, isNotNull);
expect(response.error, isNull);
expect(response.scope, scopes.join(' '));
});
});
group('hasGranted...Scopes', () {
// mock-gis.js returns false for scopes that start with "not-granted-".
const String notGranted = 'not-granted-scope';
testWidgets('all scopes granted', (_) async {
final List<String> scopes = <String>['some_scope', 'another', 'more'];
final TokenResponse response = await utils.fakeAuthZWithScopes(scopes);
final bool all = oauth2.hasGrantedAllScopes(response, scopes);
final bool any = oauth2.hasGrantedAnyScopes(response, scopes);
expect(all, isTrue);
expect(any, isTrue);
});
testWidgets('some scopes granted', (_) async {
final List<String> scopes = <String>['some_scope', notGranted, 'more'];
final TokenResponse response = await utils.fakeAuthZWithScopes(scopes);
final bool all = oauth2.hasGrantedAllScopes(response, scopes);
final bool any = oauth2.hasGrantedAnyScopes(response, scopes);
expect(all, isFalse, reason: 'Scope: $notGranted should not be granted!');
expect(any, isTrue);
});
testWidgets('no scopes granted', (_) async {
final List<String> scopes = <String>[notGranted, '$notGranted-2'];
final TokenResponse response = await utils.fakeAuthZWithScopes(scopes);
final bool all = oauth2.hasGrantedAllScopes(response, scopes);
final bool any = oauth2.hasGrantedAnyScopes(response, scopes);
expect(all, isFalse);
expect(any, isFalse, reason: 'No scopes were granted.');
});
});
}

View File

@ -0,0 +1,76 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:google_identity_services_web/oauth2.dart';
import 'package:google_identity_services_web/src/js_interop/dom.dart';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
@JS('window')
external Object get domWindow;
/// Installs mock-gis.js in the page.
/// Returns a future that completes when the 'load' event of the script fires.
Future<void> installGisMock() {
final Completer<void> completer = Completer<void>();
final DomHtmlScriptElement script =
document.createElement('script') as DomHtmlScriptElement;
script.src = 'mock-gis.js';
setProperty(script, 'type', 'module');
callMethod(script, 'addEventListener', <Object>[
'load',
allowInterop((_) {
completer.complete();
})
]);
document.head.appendChild(script);
return completer.future;
}
/// Fakes authorization with the given scopes.
Future<TokenResponse> fakeAuthZWithScopes(List<String> scopes) {
final StreamController<TokenResponse> controller =
StreamController<TokenResponse>();
final TokenClient client = oauth2.initTokenClient(TokenClientConfig(
client_id: 'for-tests',
callback: allowInterop(controller.add),
scope: scopes.join(' '),
));
setMockTokenResponse(client, 'some-non-null-auth-token-value');
client.requestAccessToken();
return controller.stream.first;
}
/// Sets a mock TokenResponse value in a [client].
void setMockTokenResponse(TokenClient client, [String? authToken]) {
callMethod(
client,
'setMockTokenResponse',
<Object?>[authToken],
);
}
/// Sets a mock credential response in `google.accounts.id`.
void setMockCredentialResponse([String value = 'default_value']) {
callMethod(
_getGoogleAccountsId(),
'setMockCredentialResponse',
<Object>[value, 'auto'],
);
}
Object _getGoogleAccountsId() {
return _getDeepProperty<Object>(domWindow, 'google.accounts.id');
}
// Attempts to retrieve a deeply nested property from a jsObject (or die tryin')
T _getDeepProperty<T>(Object jsObject, String deepProperty) {
final List<String> properties = deepProperty.split('.');
return properties.fold(
jsObject,
(Object jsObj, String prop) => getProperty<Object>(jsObj, prop),
) as T;
}

View File

@ -4,7 +4,7 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'package:google_identity_services_web/id.dart' as id; import 'package:google_identity_services_web/id.dart';
// #docregion use-loader // #docregion use-loader
import 'package:google_identity_services_web/loader.dart' as gis; import 'package:google_identity_services_web/loader.dart' as gis;
// #enddocregion use-loader // #enddocregion use-loader
@ -18,9 +18,8 @@ void main() async {
// #enddocregion use-loader // #enddocregion use-loader
id.setLogLevel('debug'); id.setLogLevel('debug');
final id.IdConfiguration config = id.IdConfiguration( final IdConfiguration config = IdConfiguration(
client_id: 'your-client_id.apps.googleusercontent.com', client_id: 'your-client_id.apps.googleusercontent.com',
ux_mode: id.UxMode.popup,
callback: allowInterop(onCredentialResponse), callback: allowInterop(onCredentialResponse),
); );
@ -32,8 +31,8 @@ void main() async {
/// Handles the ID token returned from the One Tap prompt. /// Handles the ID token returned from the One Tap prompt.
/// See: https://developers.google.com/identity/gsi/web/reference/js-reference#callback /// See: https://developers.google.com/identity/gsi/web/reference/js-reference#callback
void onCredentialResponse(id.CredentialResponse o) { void onCredentialResponse(CredentialResponse o) {
final Map<String, dynamic>? payload = jwt.JwtDecoder.tryDecode(o.credential); final Map<String, dynamic>? payload = jwt.JwtDecoder.tryDecode(o.credential!);
if (payload != null) { if (payload != null) {
print('Hello, ${payload["name"]}'); print('Hello, ${payload["name"]}');
print(o.select_by); print(o.select_by);
@ -45,8 +44,8 @@ void onCredentialResponse(id.CredentialResponse o) {
/// Handles Prompt UI status notifications. /// Handles Prompt UI status notifications.
/// See: https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt /// See: https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt
void onPromptMoment(id.PromptMomentNotification o) { void onPromptMoment(PromptMomentNotification o) {
final id.MomentType type = o.getMomentType(); final MomentType type = o.getMomentType();
print(type.runtimeType); print(type.runtimeType);
print(type); print(type);
print(type.index); print(type.index);

View File

@ -4,16 +4,31 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'package:google_identity_services_web/id.dart' as id show setLogLevel; import 'dart:convert';
import 'package:google_identity_services_web/id.dart';
import 'package:google_identity_services_web/loader.dart' as gis; import 'package:google_identity_services_web/loader.dart' as gis;
import 'package:google_identity_services_web/oauth2.dart' as oauth2; import 'package:google_identity_services_web/oauth2.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:js/js.dart' show allowInterop; import 'package:js/js.dart' show allowInterop;
import 'package:js/js_util.dart' show getProperty;
/// The scopes to be requested /// People API to return my profile info...
const String MY_PROFILE =
'https://content-people.googleapis.com/v1/people/me?personFields=photos%2Cnames%2CemailAddresses';
/// People API to return all my connections.
const String MY_CONNECTIONS =
'https://people.googleapis.com/v1/people/me/connections?requestMask.includeField=person.names';
/// Basic scopes for self-id
const List<String> scopes = <String>[ const List<String> scopes = <String>[
'email', 'https://www.googleapis.com/auth/userinfo.profile',
'profile', 'https://www.googleapis.com/auth/userinfo.email',
];
/// Scopes for the people API (read contacts)
const List<String> myConnectionsScopes = <String>[
'https://www.googleapis.com/auth/contacts.readonly', 'https://www.googleapis.com/auth/contacts.readonly',
]; ];
@ -22,58 +37,77 @@ void main() async {
id.setLogLevel('debug'); id.setLogLevel('debug');
final oauth2.TokenClientConfig config = oauth2.TokenClientConfig( final TokenClientConfig config = TokenClientConfig(
client_id: 'your-client_id.apps.googleusercontent.com', client_id: 'your-client_id.apps.googleusercontent.com',
scope: scopes.join(' '), scope: scopes.join(' '),
callback: allowInterop(onTokenResponse), callback: allowInterop(onTokenResponse),
error_callback: allowInterop(onError),
); );
final oauth2.OverridableTokenClientConfig overridableCfg = final OverridableTokenClientConfig overridableCfg =
oauth2.OverridableTokenClientConfig( OverridableTokenClientConfig(
prompt: '', scope: (scopes + myConnectionsScopes).join(' '),
); );
final oauth2.TokenClient client = oauth2.initTokenClient(config); final TokenClient client = oauth2.initTokenClient(config);
// Disable the Popup Blocker for this to work, or move this to a Button press. // Disable the Popup Blocker for this to work, or move this to a Button press.
client.requestAccessToken(overridableCfg); client.requestAccessToken(overridableCfg);
} }
/// Triggers when there's an error with the OAuth2 popup.
///
/// We cannot use the proper type for `error` here yet, because of:
/// https://github.com/dart-lang/sdk/issues/50899
Future<void> onError(Object? error) async {
print('Error! ${getProperty(error!, "type")}');
}
/// Handles the returned (auth) token response. /// Handles the returned (auth) token response.
/// See: https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse /// See: https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
Future<void> onTokenResponse(oauth2.TokenResponse response) async { Future<void> onTokenResponse(TokenResponse token) async {
if (response.error != null) { if (token.error != null) {
print('Authorization error!'); print('Authorization error!');
print(response.error); print(token.error);
print(response.error_description); print(token.error_description);
print(response.error_uri); print(token.error_uri);
return;
}
// Has granted all the scopes?
if (!oauth2.hasGrantedAllScopes(response, scopes[2])) {
print('The user has NOT granted the required scope!');
return; return;
} }
// Attempt to do a request to the `people` API // Attempt to do a request to the `people` API
final http.Response apiResponse = await http.get( final Object? profile = await get(token, MY_PROFILE);
Uri.parse('https://people.googleapis.com/v1/people/me/connections' print(profile);
'?requestMask.includeField=person.names'),
headers: <String, String>{ // Has granted all the scopes?
'Authorization': '${response.token_type} ${response.access_token}', if (!oauth2.hasGrantedAllScopes(token, myConnectionsScopes)) {
}, print('The user has NOT granted all the required scopes!');
); print('The next get will probably throw an exception!');
if (apiResponse.statusCode == 200) {
print('People API ${apiResponse.statusCode} OK!');
} else {
print(
'People API ${apiResponse.statusCode} Oops! Something wrong happened!');
} }
print(apiResponse.body);
final Object? contacts = await get(token, MY_CONNECTIONS);
print(contacts);
print('Revoking token...'); print('Revoking token...');
oauth2.revokeToken(response.access_token, allowInterop((String status) { oauth2.revoke(token.access_token,
print(status); allowInterop((TokenRevocationResponse response) {
print(response.successful);
print(response.error);
print(response.error_description);
})); }));
} }
/// Gets from [url] with an authorization header defined by [token].
///
/// Attempts to [jsonDecode] the result.
Future<Object?> get(TokenResponse token, String url) async {
final Uri uri = Uri.parse(url);
final http.Response response = await http.get(uri, headers: <String, String>{
'Authorization': '${token.token_type} ${token.access_token}',
});
if (response.statusCode != 200) {
throw http.ClientException(response.body, uri);
}
return jsonDecode(response.body) as Object?;
}

View File

@ -94,17 +94,24 @@ class TokenClient {
this.config = config; this.config = config;
} }
requestAccessToken(overridableConfig) { requestAccessToken(overridableConfig) {
this.overridableConfig = overridableConfig; this.config = {...this.config, ...overridableConfig};
let callback = this.overridableConfig.callback || this.config.callback; let callback = this.config.callback;
if (!callback) { if (!callback) {
return; return;
} }
callAsync(() => { callAsync(() => {
callback(this.tokenResponse); callback({
...this.tokenResponse,
scope: this.config.scope,
});
}); });
} }
setMockTokenResponse(tokenResponse) { setMockTokenResponse(access_token) {
this.tokenResponse = tokenResponse; this.tokenResponse = {
access_token: access_token,
token_type: access_token != null ? 'Bearer' : null,
error: access_token == null ? 'unauthorized' : null,
};
} }
} }
@ -116,22 +123,24 @@ class Oauth2 {
return new TokenClient(config); return new TokenClient(config);
} }
hasGrantedAllScopes(tokenResponse, scope, ...scopes) { hasGrantedAllScopes(tokenResponse, scope, ...scopes) {
return tokenResponse != null; return tokenResponse != null && !scope.startsWith('not-granted-');
} }
hasGrantedAnyScopes(tokenResponse, scope, ...scopes) { hasGrantedAnyScopes(tokenResponse, scope, ...scopes) {
return tokenResponse != null; return false; // Unused in the lib
} }
revoke(accessToken, done) { revoke(accessToken, done) {
if (!done) { if (!done) {
return; return;
} }
callAsync(() => { callAsync(() => {
done(); done({
success: true,
});
}) })
} }
} }
function mockGis() { (function() {
let goog = { let goog = {
accounts: { accounts: {
id: new Id(), id: new Id(),
@ -139,6 +148,4 @@ function mockGis() {
} }
}; };
globalThis['google'] = goog; globalThis['google'] = goog;
} }());
mockGis();

View File

@ -88,6 +88,29 @@ extension DomHtmlScriptElementExtension on DomHtmlScriptElement {
external set defer(bool defer); 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) // Trusted Types API (TrustedTypePolicy, TrustedScript, TrustedScriptURL)
// https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypesAPI // https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypesAPI

View File

@ -9,54 +9,142 @@
// * non_constant_identifier_names required to be able to use the same parameter // * non_constant_identifier_names required to be able to use the same parameter
// names as the underlying library. // names as the underlying library.
@JS('google.accounts.id') @JS()
library id; library google_accounts_id;
import 'package:js/js.dart'; import 'package:js/js.dart';
import 'dom.dart';
import 'shared.dart'; import 'shared.dart';
/// An undocumented method. Try with 'debug'. /// Binding to the `google.accounts.id` JS global.
///
/// See: https://developers.google.com/identity/gsi/web/reference/js-reference
@JS('google.accounts.id')
external GoogleAccountsId get id;
/// The Dart definition of the `google.accounts.id` global.
@JS() @JS()
external SetLogLevelFn get setLogLevel; @staticInterop
abstract class GoogleAccountsId {}
/// /// The `google.accounts.id` methods
typedef SetLogLevelFn = void Function(String level); extension GoogleAccountsIdExtension on GoogleAccountsId {
/// An undocumented method.
///
/// Try it with 'debug'.
external void setLogLevel(String level);
/* /// Initializes the Sign In With Google client based on [IdConfiguration].
// Method: google.accounts.id.initialize ///
// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.initialize /// The `initialize` method creates a Sign In With Google client instance that
*/ /// can be implicitly used by all modules in the same web page.
///
/// * You only need to call the `initialize` method once even if you use
/// multiple modules (like One Tap, Personalized button, revocation, etc.) in
/// the same web page.
/// * If you do call the google.accounts.id.initialize method multiple times,
/// only the configurations in the last call will be remembered and used.
///
/// You actually reset the configurations whenever you call the `initialize`
/// method, and all subsequent methods in the same web page will use the new
/// configurations immediately.
///
/// WARNING: The `initialize` method should be called only once, even if you
/// use both One Tap and button in the same web page.
///
/// Method: google.accounts.id.initialize
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.initialize
external void initialize(IdConfiguration idConfiguration);
/// Initializes the Sign In With Google client based on [IdConfiguration]. /// The `prompt` method displays the One Tap prompt or the browser native
/// /// credential manager after the [initialize] method is invoked.
/// The `initialize` method creates a Sign In With Google client instance that ///
/// can be implicitly used by all modules in the same web page. /// 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
/// * You only need to call the `initialize` method once even if you use /// not be displayed. To get notified on the UI status for different moments,
/// multiple modules (like One Tap, Personalized button, revocation, etc.) in /// pass a [PromptMomentListenerFn] to receive UI status notifications.
/// the same web page. ///
/// * If you do call the google.accounts.id.initialize method multiple times, /// Notifications are fired on the following moments:
/// only the configurations in the last call will be remembered and used. ///
/// /// * Display moment: This occurs after the `prompt` method is called. The
/// You actually reset the configurations whenever you call the `initialize` /// notification contains a boolean value to indicate whether the UI is
/// method, and all subsequent methods in the same web page will use the new /// displayed or not.
/// configurations immediately. /// * 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
/// WARNING: The `initialize` method should be called only once, even if you /// as when the selected session has signed out of Google.
/// use both One Tap and button in the same web page. /// In these cases, we recommend that you continue on to the next identity
@JS() /// providers, if there are any.
external InitializeFn get initialize; /// * Dismissed moment: This occurs when Google successfully retrieves a
/// credential or a user wants to stop the credential retrieval flow. For
/// example, when the user begins to input their username and password in
/// your login dialog, you can call the [cancel] method to close the One Tap
/// prompt and trigger a dismissed moment.
///
/// WARNING: When on a dismissed moment, do not try any of the next identity
/// providers.
///
/// Method: google.accounts.id.prompt
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt
external void prompt([PromptMomentListenerFn momentListener]);
/// The type of the [initialize] function. /// Renders a Sign In With Google button in your web page.
typedef InitializeFn = void Function(IdConfiguration idConfiguration); ///
/// Method: google.accounts.id.renderButton
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.renderButton
external void renderButton(
DomHtmlElement parent, [
GsiButtonConfiguration options,
]);
/* /// Record when the user signs out of your website in cookies.
// Data type: IdConfiguration ///
// https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration /// This prevents a UX dead loop.
*/ ///
/// Method: google.accounts.id.disableAutoselect
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.disableAutoSelect
external void disableAutoSelect();
/// A wrapper for the `store` method of the browser's native credential manager API.
///
/// It can only be used to store a Password [Credential].
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/store
///
/// Method: google.accounts.id.storeCredential
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.storeCredential
external void storeCredential(Credential credential, [VoidFn fallback]);
/// Cancels the One Tap flow.
///
/// You can cancel the One Tap flow if you remove the prompt from the relying
/// party DOM. The cancel operation is ignored if a credential is already
/// selected.
///
/// Method: google.accounts.id.cancel
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.cancel
external void cancel();
/// Revokes the OAuth grant used to share the ID token for the specified user.
///
/// [hint] is the email address or unique ID of the user's Google Account. The
/// ID is the `sub` property of the [CredentialResponse.credential] payload.
///
/// The optional [callback] is a function that gets called to report on the
/// success of the revocation call.
///
/// The [callback] parameter must be manually wrapped in [allowInterop]
/// before being passed to the [revoke] function.
///
/// Method: google.accounts.id.revoke
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.revoke
external void revoke(String hint, [RevocationResponseHandlerFn callback]);
}
/// The configuration object for the [initialize] method. /// The configuration object for the [initialize] method.
///
/// Data type: IdConfiguration
/// https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
@JS() @JS()
@anonymous @anonymous
@staticInterop @staticInterop
@ -152,55 +240,13 @@ abstract class IdConfiguration {
}); });
} }
/*
// 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. /// The type of the function that can be passed to [prompt] to listen for [PromptMomentNotification]s.
typedef PromptMomentListenerFn = void Function(PromptMomentNotification moment); 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. /// A moment (status) notification from the [prompt] method.
///
/// Data type: PromptMomentNotification
/// https://developers.google.com/identity/gsi/web/reference/js-reference#PromptMomentNotification
@JS() @JS()
@staticInterop @staticInterop
abstract class PromptMomentNotification {} abstract class PromptMomentNotification {}
@ -246,25 +292,32 @@ extension PromptMomentNotificationExtension on PromptMomentNotification {
maybeEnum(_getDismissedReason(), MomentDismissedReason.values); 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]. /// The object passed as the parameter of your [CallbackFn].
///
/// Data type: CredentialResponse
/// https://developers.google.com/identity/gsi/web/reference/js-reference#CredentialResponse
@JS() @JS()
@staticInterop @staticInterop
abstract class CredentialResponse {} abstract class CredentialResponse {}
/// The fields that are contained in the credential response object. /// The fields that are contained in the credential response object.
extension CredentialResponseExtension on CredentialResponse { extension CredentialResponseExtension on CredentialResponse {
/// The ClientID for this Credential.
external String? get client_id;
/// Error while signing in.
external String? get error;
/// Details of the error while signing in.
external String? get error_detail;
/// This field is the ID token as a base64-encoded JSON Web Token (JWT) /// This field is the ID token as a base64-encoded JSON Web Token (JWT)
/// string. /// string.
/// ///
/// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#credential /// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#credential
external String get credential; external String? get credential;
@JS('select_by') @JS('select_by')
external String get _select_by; external String? get _select_by;
/// This field sets how the credential was selected. /// This field sets how the credential was selected.
/// ///
@ -272,8 +325,8 @@ extension CredentialResponseExtension on CredentialResponse {
/// to set the value. /// to set the value.
/// ///
/// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#select_by /// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#select_by
CredentialSelectBy get select_by => CredentialSelectBy? get select_by =>
CredentialSelectBy.values.byName(_select_by); maybeEnum(_select_by, CredentialSelectBy.values);
} }
/// The type of the `callback` used to create an [IdConfiguration]. /// The type of the `callback` used to create an [IdConfiguration].
@ -285,20 +338,69 @@ extension CredentialResponseExtension on CredentialResponse {
/// attribute. /// attribute.
typedef CallbackFn = void Function(CredentialResponse credentialResponse); typedef CallbackFn = void Function(CredentialResponse credentialResponse);
/* /// The configuration object for the [renderButton] method.
// 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
// Data type: GsiButtonConfiguration @JS()
// https://developers.google.com/identity/gsi/web/reference/js-reference#GsiButtonConfiguration @anonymous
// @staticInterop
// Question: Do we need to implement renderButton and its options? abstract class GsiButtonConfiguration {
*/ /// Constructs an options object for the [renderButton] method.
///
/// The following properties need to be manually wrapped in [allowInterop]
/// before being passed to this constructor:
external factory GsiButtonConfiguration({
/// The button type.
ButtonType type,
/* /// The button theme.
// Data type: Credential ButtonTheme theme,
// https://developers.google.com/identity/gsi/web/reference/js-reference#type-Credential
*/ /// The button size.
ButtonSize size,
/// The button text.
ButtonText text,
/// The button shape.
ButtonShape shape,
/// The Google logo alignment in the button.
ButtonLogoAlignment logo_alignment,
/// The minimum button width, in pixels.
///
/// The maximum width is 400 pixels.
double width,
/// The pre-set locale of the button text.
///
/// If not set, the browser's default locale or the Google session user's
/// preference is used.
String locale,
/// A function to be called when the button is clicked.
GsiButtonClickListenerFn click_listener,
});
}
/// The object passed as an optional parameter to `click_listener` function.
@JS()
@staticInterop
abstract class GsiButtonData {}
/// The fields that are contained in the button data.
extension GsiButtonDataExtension on GsiButtonData {
/// Nonce
external String? get nonce;
/// State
external String? get state;
}
/// The type of the [GsiButtonConfiguration] `click_listener` function.
typedef GsiButtonClickListenerFn = void Function(GsiButtonData? gsiButtonData);
/// The object passed to the [NativeCallbackFn]. Represents a PasswordCredential /// The object passed to the [NativeCallbackFn]. Represents a PasswordCredential
/// that was returned by the Browser. /// that was returned by the Browser.
@ -307,6 +409,9 @@ typedef CallbackFn = void Function(CredentialResponse credentialResponse);
/// in the browser through the [storeCredential] method. /// in the browser through the [storeCredential] method.
/// ///
/// See also: https://developer.mozilla.org/en-US/docs/Web/API/PasswordCredential/PasswordCredential /// See also: https://developer.mozilla.org/en-US/docs/Web/API/PasswordCredential/PasswordCredential
///
/// Data type: Credential
/// https://developers.google.com/identity/gsi/web/reference/js-reference#type-Credential
@JS() @JS()
@anonymous @anonymous
@staticInterop @staticInterop
@ -333,95 +438,22 @@ extension CredentialExtension on Credential {
/// from the native Credential manager of the user's browser. /// from the native Credential manager of the user's browser.
typedef NativeCallbackFn = void Function(Credential credential); 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 // Library load callback: onGoogleLibraryLoad
// https://developers.google.com/identity/gsi/web/reference/js-reference#onGoogleLibraryLoad // https://developers.google.com/identity/gsi/web/reference/js-reference#onGoogleLibraryLoad
// See: `load_callback.dart` and `loader.dart` // 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 type of the `callback` function passed to [revoke], to be notified of
/// the success of the revocation operation. /// the success of the revocation operation.
typedef RevocationResponseHandlerFn = void Function( typedef RevocationResponseHandlerFn = void Function(
RevocationResponse revocationResponse, RevocationResponse revocationResponse,
); );
/* /// The parameter passed to the `callback` of the [revoke] function.
// Data type: RevocationResponse ///
// https://developers.google.com/identity/gsi/web/reference/js-reference#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() @JS()
@staticInterop @staticInterop
abstract class RevocationResponse {} abstract class RevocationResponse {}

View File

@ -9,34 +9,84 @@
// * non_constant_identifier_names required to be able to use the same parameter // * non_constant_identifier_names required to be able to use the same parameter
// names as the underlying library. // names as the underlying library.
@JS('google.accounts.oauth2') @JS()
library oauth2; library google_accounts_oauth2;
import 'package:js/js.dart'; import 'package:js/js.dart';
import 'dom.dart';
import 'shared.dart'; import 'shared.dart';
// Code Client /// Binding to the `google.accounts.oauth2` JS global.
///
/// See: https://developers.google.com/identity/oauth2/web/reference/js-reference
@JS('google.accounts.oauth2')
external GoogleAccountsOauth2 get oauth2;
/* /// The Dart definition of the `google.accounts.oauth2` global.
// 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() @JS()
external InitCodeClientFn get initCodeClient; @staticInterop
abstract class GoogleAccountsOauth2 {}
/// The type of the [initCodeClient] function. /// The `google.accounts.oauth2` methods
typedef InitCodeClientFn = CodeClient Function(CodeClientConfig config); extension GoogleAccountsOauth2Extension on GoogleAccountsOauth2 {
/// Initializes and returns a code client, with the passed-in [config].
///
/// Method: google.accounts.oauth2.initCodeClient
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initCodeClient
external CodeClient initCodeClient(CodeClientConfig config);
/* /// Initializes and returns a token client, with the passed-in [config].
// Data type: CodeClientConfig ///
// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig /// Method: google.accounts.oauth2.initTokenClient
*/ /// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient
external TokenClient initTokenClient(TokenClientConfig config);
// Method: google.accounts.oauth2.hasGrantedAllScopes
// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
@JS('hasGrantedAllScopes')
external bool _hasGrantedScope(TokenResponse token, String scope);
/// Checks if hte user has granted **all** the specified [scopes].
///
/// [scopes] is a space-separated list of scope names.
///
/// Method: google.accounts.oauth2.hasGrantedAllScopes
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
bool hasGrantedAllScopes(TokenResponse tokenResponse, List<String> scopes) {
return scopes
.every((String scope) => _hasGrantedScope(tokenResponse, scope));
}
/// Checks if hte user has granted **all** the specified [scopes].
///
/// [scopes] is a space-separated list of scope names.
///
/// Method: google.accounts.oauth2.hasGrantedAllScopes
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes
bool hasGrantedAnyScopes(TokenResponse tokenResponse, List<String> scopes) {
return scopes.any((String scope) => _hasGrantedScope(tokenResponse, scope));
}
/// Revokes all of the scopes that the user granted to the app.
///
/// A valid [accessToken] is required to revoke permissions.
///
/// The [done] callback is called once the revoke action is done. It must be
/// manually wrapped in [allowInterop] before being passed to this method.
///
/// Method: google.accounts.oauth2.revoke
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke
external void revoke(
String accessToken, [
RevokeTokenDoneFn done,
]);
}
/// The configuration object for the [initCodeClient] method. /// The configuration object for the [initCodeClient] method.
///
/// Data type: CodeClientConfig
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig
@JS() @JS()
@anonymous @anonymous
@staticInterop @staticInterop
@ -51,6 +101,7 @@ abstract class CodeClientConfig {
String? redirect_uri, String? redirect_uri,
bool? auto_select, bool? auto_select,
CodeClientCallbackFn? callback, CodeClientCallbackFn? callback,
ErrorCallbackFn? error_callback,
String? state, String? state,
bool? enable_serial_consent, bool? enable_serial_consent,
String? hint, String? hint,
@ -60,14 +111,12 @@ abstract class CodeClientConfig {
}); });
} }
/*
// 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. /// A client that can start the OAuth 2.0 Code UX flow.
/// ///
/// See: https://developers.google.com/identity/oauth2/web/guides/use-code-model /// See: https://developers.google.com/identity/oauth2/web/guides/use-code-model
///
/// Data type: CodeClient
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClient
@JS() @JS()
@staticInterop @staticInterop
abstract class CodeClient {} abstract class CodeClient {}
@ -78,12 +127,10 @@ extension CodeClientExtension on CodeClient {
external void requestCode(); 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]. /// The object passed as the parameter of your [CodeClientCallbackFn].
///
/// Data type: CodeResponse
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse
@JS() @JS()
@staticInterop @staticInterop
abstract class CodeResponse {} abstract class CodeResponse {}
@ -116,27 +163,10 @@ extension CodeResponseExtension on CodeResponse {
/// The type of the `callback` function passed to [CodeClientConfig]. /// The type of the `callback` function passed to [CodeClientConfig].
typedef CodeClientCallbackFn = void Function(CodeResponse response); 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. /// The configuration object for the [initTokenClient] method.
///
/// Data type: TokenClientConfig
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClientConfig
@JS() @JS()
@anonymous @anonymous
@staticInterop @staticInterop
@ -149,6 +179,7 @@ abstract class TokenClientConfig {
required String client_id, required String client_id,
required TokenClientCallbackFn? callback, required TokenClientCallbackFn? callback,
required String scope, required String scope,
ErrorCallbackFn? error_callback,
String? prompt, String? prompt,
bool? enable_serial_consent, bool? enable_serial_consent,
String? hint, String? hint,
@ -157,14 +188,12 @@ abstract class TokenClientConfig {
}); });
} }
/*
// 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. /// A client that can start the OAuth 2.0 Token UX flow.
/// ///
/// See: https://developers.google.com/identity/oauth2/web/guides/use-token-model /// See: https://developers.google.com/identity/oauth2/web/guides/use-token-model
///
/// Data type: TokenClient
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClient
@JS() @JS()
@staticInterop @staticInterop
abstract class TokenClient {} abstract class TokenClient {}
@ -177,13 +206,10 @@ extension TokenClientExtension on TokenClient {
]); ]);
} }
/* /// The overridable configuration object for the [TokenClientExtension.requestAccessToken] method.
// Data type: OverridableTokenClientConfig ///
// https://developers.google.com/identity/oauth2/web/reference/js-reference#OverridableTokenClientConfig /// Data type: OverridableTokenClientConfig
*/ /// https://developers.google.com/identity/oauth2/web/reference/js-reference#OverridableTokenClientConfig
/// The overridable configuration object for the
/// [TokenClientExtension.requestAccessToken] method.
@JS() @JS()
@anonymous @anonymous
@staticInterop @staticInterop
@ -229,15 +255,18 @@ abstract class OverridableTokenClientConfig {
/// uses to maintain state between your authorization request and the /// uses to maintain state between your authorization request and the
/// authorization server's response. /// authorization server's response.
String? state, String? state,
/// Preserves previously requested scopes in this new request.
///
/// (Undocumented)
bool? include_granted_scopes,
}); });
} }
/*
// Data type: TokenResponse
// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
*/
/// The object passed as the parameter of your [TokenClientCallbackFn]. /// The object passed as the parameter of your [TokenClientCallbackFn].
///
/// Data type: TokenResponse
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
@JS() @JS()
@staticInterop @staticInterop
abstract class TokenResponse {} abstract class TokenResponse {}
@ -283,63 +312,55 @@ extension TokenResponseExtension on TokenResponse {
/// The type of the `callback` function passed to [TokenClientConfig]. /// The type of the `callback` function passed to [TokenClientConfig].
typedef TokenClientCallbackFn = void Function(TokenResponse response); typedef TokenClientCallbackFn = void Function(TokenResponse response);
/* /// The type of the `error_callback` in both oauth2 initXClient calls.
// Method: google.accounts.oauth2.hasGrantedAllScopes ///
// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAllScopes /// (Currently undocumented)
*/ ///
/// `error` should be of type [GoogleIdentityServicesError]?, but it cannot be
/// because of this DDC bug: https://github.com/dart-lang/sdk/issues/50899
typedef ErrorCallbackFn = void Function(Object? error);
/// Checks if the user granted **all** the specified scopes. /// An error returned by `initTokenClient` or `initDataClient`.
///
/// Cannot be used: https://github.com/dart-lang/sdk/issues/50899
@JS() @JS()
external HasGrantedScopesFn get hasGrantedAllScopes; @staticInterop
abstract class GoogleIdentityServicesError extends DomError {}
/* /// Methods of the GoogleIdentityServicesError object.
// Method: google.accounts.oauth2.hasGrantedAnyScopes ///
// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAnyScopes /// Cannot be used: https://github.com/dart-lang/sdk/issues/50899
*/ extension GoogleIdentityServicesErrorExtension on GoogleIdentityServicesError {
@JS('type')
external String get _type;
// String get _type => js_util.getProperty<String>(this, 'type');
/// Checks if the user granted **any** of the specified scopes. /// The type of error
GoogleIdentityServicesErrorType get type =>
GoogleIdentityServicesErrorType.values.byName(_type);
}
/// The signature of the `done` function for [revoke].
typedef RevokeTokenDoneFn = void Function(TokenRevocationResponse response);
/// The parameter passed to the `callback` of the [revoke] function.
///
/// Data type: RevocationResponse
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
@JS() @JS()
external HasGrantedScopesFn get hasGrantedAnyScopes; @staticInterop
abstract class TokenRevocationResponse {}
/// The signature for functions that check if any/all scopes have been granted. /// The fields that are contained in the [TokenRevocationResponse] object.
/// extension TokenRevocationResponseExtension on TokenRevocationResponse {
/// Used by [hasGrantedAllScopes] and [hasGrantedAnyScope]. /// This field is a boolean value set to true if the revoke method call
typedef HasGrantedScopesFn = bool Function( /// succeeded or false on failure.
TokenResponse tokenResponse, external bool get successful;
String firstScope, [
String? scope2,
String? scope3,
String? scope4,
String? scope5,
String? scope6,
String? scope7,
String? scope8,
String? scope9,
String? scope10,
]);
/* /// This field is a string value and contains a detailed error message if the
// Method: google.accounts.oauth2.revoke /// revoke method call failed, it is undefined on success.
// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke external String? get error;
*/
/// The [revokeToken] method revokes all of the scopes that the user granted to /// The description of the error.
/// the app. A valid access token is required to revoke the permission. external String? get error_description;
/// }
/// 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);

View File

@ -213,3 +213,159 @@ enum CredentialSelectBy {
@override @override
String toString() => _selectBy; String toString() => _selectBy;
} }
/// The type of button to be rendered.
///
/// https://developers.google.com/identity/gsi/web/reference/js-reference#type
enum ButtonType {
/// A button with text or personalized information.
standard('standard'),
/// An icon button without text.
icon('icon');
///
const ButtonType(String type) : _type = type;
final String _type;
@override
String toString() => _type;
}
/// The theme of the button to be rendered.
///
/// https://developers.google.com/identity/gsi/web/reference/js-reference#theme
enum ButtonTheme {
/// A standard button theme.
outline('outline'),
/// A blue-filled button theme.
filled_blue('filled_blue'),
/// A black-filled button theme.
filled_black('filled_black');
///
const ButtonTheme(String theme) : _theme = theme;
final String _theme;
@override
String toString() => _theme;
}
/// The theme of the button to be rendered.
///
/// https://developers.google.com/identity/gsi/web/reference/js-reference#size
enum ButtonSize {
/// A large button (about 40px tall).
large('large'),
/// A medium-sized button (about 32px tall).
medium('medium'),
/// A small button (about 20px tall).
small('small');
///
const ButtonSize(String size) : _size = size;
final String _size;
@override
String toString() => _size;
}
/// The button text.
///
/// https://developers.google.com/identity/gsi/web/reference/js-reference#text
enum ButtonText {
/// The button text is "Sign in with Google".
signin_with('signin_with'),
/// The button text is "Sign up with Google".
signup_with('signup_with'),
/// The button text is "Continue with Google".
continue_with('continue_with'),
/// The button text is "Sign in".
signin('signin');
///
const ButtonText(String text) : _text = text;
final String _text;
@override
String toString() => _text;
}
/// The button shape.
///
/// https://developers.google.com/identity/gsi/web/reference/js-reference#shape
enum ButtonShape {
/// The rectangular-shaped button.
///
/// If used for the [ButtonType.icon], then it's the same as [square].
rectangular('rectangular'),
/// The pill-shaped button.
///
/// If used for the [ButtonType.icon], then it's the same as [circle].
pill('pill'),
/// The circle-shaped button.
///
/// If used for the [ButtonType.standard], then it's the same as [pill].
circle('circle'),
/// The square-shaped button.
///
/// If used for the [ButtonType.standard], then it's the same as [rectangular].
square('square');
///
const ButtonShape(String shape) : _shape = shape;
final String _shape;
@override
String toString() => _shape;
}
/// The type of button to be rendered.
///
/// https://developers.google.com/identity/gsi/web/reference/js-reference#type
enum ButtonLogoAlignment {
/// Left-aligns the Google logo.
left('left'),
/// Center-aligns the Google logo.
center('center');
///
const ButtonLogoAlignment(String alignment) : _alignment = alignment;
final String _alignment;
@override
String toString() => _alignment;
}
/// The `type` of the error object passed into the `error_callback` function.
enum GoogleIdentityServicesErrorType {
/// Missing required parameter.
missing_required_parameter('missing_required_parameter'),
/// The popup was closed before the flow was completed.
popup_closed('popup_closed'),
/// Popup failed to open.
popup_failed_to_open('popup_failed_to_open'),
/// Unknown error.
unknown('unknown');
///
const GoogleIdentityServicesErrorType(String type) : _type = type;
final String _type;
@override
String toString() => _type;
}

View File

@ -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. 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 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 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_identiy_services_web%22
version: 0.1.1 version: 0.2.0
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"

View File

@ -1,3 +1,8 @@
# Tests # Tests
Use `dart run tool/run_tests.dart` to run tests in this package. Use `dart run tool/run_tests.dart` to run tests in this package.
## Failed to run Chrome: No such file or directory
Ensure the correct path to the Chrome executable is set in `dart_test.yaml`. It
may be other than `chrome` (for example, `google-chrome` in my machine).