mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 23:03:11 +08:00
[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:
@ -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
|
||||
|
||||
* Add optional `scope` to `OverridableTokenClientConfig` object.
|
||||
|
@ -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:
|
||||
|
||||
* `import 'package:google_identity_services/id.dart' as id;` for Authentication
|
||||
* `import 'package:google_identity_services/oauth2.dart' as oauth2;` for
|
||||
Authorization.
|
||||
* `import 'package:google_identity_services/id.dart';` for Authentication.
|
||||
* This will expose an `id` JSObject that binds to `google.accounts.id`.
|
||||
* `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
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
@ -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.');
|
||||
});
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
// 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
|
||||
import 'package:google_identity_services_web/loader.dart' as gis;
|
||||
// #enddocregion use-loader
|
||||
@ -18,9 +18,8 @@ void main() async {
|
||||
// #enddocregion use-loader
|
||||
id.setLogLevel('debug');
|
||||
|
||||
final id.IdConfiguration config = id.IdConfiguration(
|
||||
final IdConfiguration config = IdConfiguration(
|
||||
client_id: 'your-client_id.apps.googleusercontent.com',
|
||||
ux_mode: id.UxMode.popup,
|
||||
callback: allowInterop(onCredentialResponse),
|
||||
);
|
||||
|
||||
@ -32,8 +31,8 @@ void main() async {
|
||||
|
||||
/// Handles the ID token returned from the One Tap prompt.
|
||||
/// See: https://developers.google.com/identity/gsi/web/reference/js-reference#callback
|
||||
void onCredentialResponse(id.CredentialResponse o) {
|
||||
final Map<String, dynamic>? payload = jwt.JwtDecoder.tryDecode(o.credential);
|
||||
void onCredentialResponse(CredentialResponse o) {
|
||||
final Map<String, dynamic>? payload = jwt.JwtDecoder.tryDecode(o.credential!);
|
||||
if (payload != null) {
|
||||
print('Hello, ${payload["name"]}');
|
||||
print(o.select_by);
|
||||
@ -45,8 +44,8 @@ void onCredentialResponse(id.CredentialResponse o) {
|
||||
|
||||
/// 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();
|
||||
void onPromptMoment(PromptMomentNotification o) {
|
||||
final MomentType type = o.getMomentType();
|
||||
print(type.runtimeType);
|
||||
print(type);
|
||||
print(type.index);
|
||||
|
@ -4,16 +4,31 @@
|
||||
|
||||
// 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/oauth2.dart' as oauth2;
|
||||
import 'package:google_identity_services_web/oauth2.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:js/js.dart' show allowInterop;
|
||||
import 'package:js/js_util.dart' show getProperty;
|
||||
|
||||
/// 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>[
|
||||
'email',
|
||||
'profile',
|
||||
'https://www.googleapis.com/auth/userinfo.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',
|
||||
];
|
||||
|
||||
@ -22,58 +37,77 @@ void main() async {
|
||||
|
||||
id.setLogLevel('debug');
|
||||
|
||||
final oauth2.TokenClientConfig config = oauth2.TokenClientConfig(
|
||||
final TokenClientConfig config = TokenClientConfig(
|
||||
client_id: 'your-client_id.apps.googleusercontent.com',
|
||||
scope: scopes.join(' '),
|
||||
callback: allowInterop(onTokenResponse),
|
||||
error_callback: allowInterop(onError),
|
||||
);
|
||||
|
||||
final oauth2.OverridableTokenClientConfig overridableCfg =
|
||||
oauth2.OverridableTokenClientConfig(
|
||||
prompt: '',
|
||||
final OverridableTokenClientConfig overridableCfg =
|
||||
OverridableTokenClientConfig(
|
||||
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.
|
||||
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.
|
||||
/// See: https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
|
||||
Future<void> onTokenResponse(oauth2.TokenResponse response) async {
|
||||
if (response.error != null) {
|
||||
Future<void> onTokenResponse(TokenResponse token) async {
|
||||
if (token.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!');
|
||||
print(token.error);
|
||||
print(token.error_description);
|
||||
print(token.error_uri);
|
||||
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!');
|
||||
final Object? profile = await get(token, MY_PROFILE);
|
||||
print(profile);
|
||||
|
||||
// Has granted all the scopes?
|
||||
if (!oauth2.hasGrantedAllScopes(token, myConnectionsScopes)) {
|
||||
print('The user has NOT granted all the required scopes!');
|
||||
print('The next get will probably throw an exception!');
|
||||
}
|
||||
print(apiResponse.body);
|
||||
|
||||
final Object? contacts = await get(token, MY_CONNECTIONS);
|
||||
print(contacts);
|
||||
|
||||
print('Revoking token...');
|
||||
oauth2.revokeToken(response.access_token, allowInterop((String status) {
|
||||
print(status);
|
||||
oauth2.revoke(token.access_token,
|
||||
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?;
|
||||
}
|
||||
|
@ -94,17 +94,24 @@ class TokenClient {
|
||||
this.config = config;
|
||||
}
|
||||
requestAccessToken(overridableConfig) {
|
||||
this.overridableConfig = overridableConfig;
|
||||
let callback = this.overridableConfig.callback || this.config.callback;
|
||||
this.config = {...this.config, ...overridableConfig};
|
||||
let callback = this.config.callback;
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
callAsync(() => {
|
||||
callback(this.tokenResponse);
|
||||
callback({
|
||||
...this.tokenResponse,
|
||||
scope: this.config.scope,
|
||||
});
|
||||
});
|
||||
}
|
||||
setMockTokenResponse(tokenResponse) {
|
||||
this.tokenResponse = tokenResponse;
|
||||
setMockTokenResponse(access_token) {
|
||||
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);
|
||||
}
|
||||
hasGrantedAllScopes(tokenResponse, scope, ...scopes) {
|
||||
return tokenResponse != null;
|
||||
return tokenResponse != null && !scope.startsWith('not-granted-');
|
||||
}
|
||||
hasGrantedAnyScopes(tokenResponse, scope, ...scopes) {
|
||||
return tokenResponse != null;
|
||||
return false; // Unused in the lib
|
||||
}
|
||||
revoke(accessToken, done) {
|
||||
if (!done) {
|
||||
return;
|
||||
}
|
||||
callAsync(() => {
|
||||
done();
|
||||
done({
|
||||
success: true,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function mockGis() {
|
||||
(function() {
|
||||
let goog = {
|
||||
accounts: {
|
||||
id: new Id(),
|
||||
@ -139,6 +148,4 @@ function mockGis() {
|
||||
}
|
||||
};
|
||||
globalThis['google'] = goog;
|
||||
}
|
||||
|
||||
mockGis();
|
||||
}());
|
||||
|
@ -88,6 +88,29 @@ extension DomHtmlScriptElementExtension on DomHtmlScriptElement {
|
||||
external set defer(bool defer);
|
||||
}
|
||||
|
||||
/// Error object
|
||||
@JS('Error')
|
||||
@staticInterop
|
||||
abstract class DomError {}
|
||||
|
||||
/// Methods on the error object
|
||||
extension DomErrorExtension on DomError {
|
||||
/// Error message.
|
||||
external String? get message;
|
||||
|
||||
/// Stack trace.
|
||||
external String? get stack;
|
||||
|
||||
/// Error name. This is determined by the constructor function.
|
||||
external String get name;
|
||||
|
||||
/// Error cause indicating the reason why the current error is thrown.
|
||||
///
|
||||
/// This is usually another caught error, or the value provided as the `cause`
|
||||
/// property of the Error constructor's second argument.
|
||||
external Object? get cause;
|
||||
}
|
||||
|
||||
/*
|
||||
// Trusted Types API (TrustedTypePolicy, TrustedScript, TrustedScriptURL)
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypesAPI
|
||||
|
@ -9,54 +9,142 @@
|
||||
// * non_constant_identifier_names required to be able to use the same parameter
|
||||
// names as the underlying library.
|
||||
|
||||
@JS('google.accounts.id')
|
||||
library id;
|
||||
@JS()
|
||||
library google_accounts_id;
|
||||
|
||||
import 'package:js/js.dart';
|
||||
|
||||
import 'dom.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()
|
||||
external SetLogLevelFn get setLogLevel;
|
||||
@staticInterop
|
||||
abstract class GoogleAccountsId {}
|
||||
|
||||
///
|
||||
typedef SetLogLevelFn = void Function(String level);
|
||||
/// The `google.accounts.id` methods
|
||||
extension GoogleAccountsIdExtension on GoogleAccountsId {
|
||||
/// An undocumented method.
|
||||
///
|
||||
/// Try it with 'debug'.
|
||||
external void setLogLevel(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.
|
||||
///
|
||||
/// 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 `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 `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.
|
||||
///
|
||||
/// 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.
|
||||
typedef InitializeFn = void Function(IdConfiguration idConfiguration);
|
||||
/// Renders a Sign In With Google button in your web page.
|
||||
///
|
||||
/// Method: google.accounts.id.renderButton
|
||||
/// https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.renderButton
|
||||
external void renderButton(
|
||||
DomHtmlElement parent, [
|
||||
GsiButtonConfiguration options,
|
||||
]);
|
||||
|
||||
/*
|
||||
// Data type: IdConfiguration
|
||||
// https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
|
||||
*/
|
||||
/// Record when the user signs out of your website in cookies.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Data type: IdConfiguration
|
||||
/// https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
|
||||
@JS()
|
||||
@anonymous
|
||||
@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.
|
||||
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.
|
||||
///
|
||||
/// Data type: PromptMomentNotification
|
||||
/// https://developers.google.com/identity/gsi/web/reference/js-reference#PromptMomentNotification
|
||||
@JS()
|
||||
@staticInterop
|
||||
abstract class PromptMomentNotification {}
|
||||
@ -246,25 +292,32 @@ extension PromptMomentNotificationExtension on PromptMomentNotification {
|
||||
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].
|
||||
///
|
||||
/// Data type: CredentialResponse
|
||||
/// https://developers.google.com/identity/gsi/web/reference/js-reference#CredentialResponse
|
||||
@JS()
|
||||
@staticInterop
|
||||
abstract class CredentialResponse {}
|
||||
|
||||
/// The fields that are contained in the credential response object.
|
||||
extension CredentialResponseExtension on CredentialResponse {
|
||||
/// The ClientID for this Credential.
|
||||
external String? get client_id;
|
||||
|
||||
/// 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)
|
||||
/// string.
|
||||
///
|
||||
/// See more: https://developers.google.com/identity/gsi/web/reference/js-reference#credential
|
||||
external String get credential;
|
||||
external String? get credential;
|
||||
@JS('select_by')
|
||||
external String get _select_by;
|
||||
external String? get _select_by;
|
||||
|
||||
/// This field sets how the credential was selected.
|
||||
///
|
||||
@ -272,8 +325,8 @@ extension CredentialResponseExtension on CredentialResponse {
|
||||
/// 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);
|
||||
CredentialSelectBy? get select_by =>
|
||||
maybeEnum(_select_by, CredentialSelectBy.values);
|
||||
}
|
||||
|
||||
/// The type of the `callback` used to create an [IdConfiguration].
|
||||
@ -285,20 +338,69 @@ extension CredentialResponseExtension on CredentialResponse {
|
||||
/// 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?
|
||||
*/
|
||||
/// The configuration object for the [renderButton] method.
|
||||
///
|
||||
/// Data type: GsiButtonConfiguration
|
||||
/// https://developers.google.com/identity/gsi/web/reference/js-reference#GsiButtonConfiguration
|
||||
@JS()
|
||||
@anonymous
|
||||
@staticInterop
|
||||
abstract class GsiButtonConfiguration {
|
||||
/// Constructs an options object for the [renderButton] method.
|
||||
///
|
||||
/// The following properties need to be manually wrapped in [allowInterop]
|
||||
/// before being passed to this constructor:
|
||||
external factory GsiButtonConfiguration({
|
||||
/// The button type.
|
||||
ButtonType type,
|
||||
|
||||
/*
|
||||
// Data type: Credential
|
||||
// https://developers.google.com/identity/gsi/web/reference/js-reference#type-Credential
|
||||
*/
|
||||
/// The button theme.
|
||||
ButtonTheme theme,
|
||||
|
||||
/// 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
|
||||
/// that was returned by the Browser.
|
||||
@ -307,6 +409,9 @@ typedef CallbackFn = void Function(CredentialResponse credentialResponse);
|
||||
/// in the browser through the [storeCredential] method.
|
||||
///
|
||||
/// 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()
|
||||
@anonymous
|
||||
@staticInterop
|
||||
@ -333,95 +438,22 @@ extension CredentialExtension on Credential {
|
||||
/// 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.
|
||||
/// The parameter passed to the `callback` of the [revoke] function.
|
||||
///
|
||||
/// Data type: RevocationResponse
|
||||
/// https://developers.google.com/identity/gsi/web/reference/js-reference#RevocationResponse
|
||||
@JS()
|
||||
@staticInterop
|
||||
abstract class RevocationResponse {}
|
||||
|
@ -9,34 +9,84 @@
|
||||
// * non_constant_identifier_names required to be able to use the same parameter
|
||||
// names as the underlying library.
|
||||
|
||||
@JS('google.accounts.oauth2')
|
||||
library oauth2;
|
||||
@JS()
|
||||
library google_accounts_oauth2;
|
||||
|
||||
import 'package:js/js.dart';
|
||||
|
||||
import 'dom.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;
|
||||
|
||||
/*
|
||||
// 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].
|
||||
/// The Dart definition of the `google.accounts.oauth2` global.
|
||||
@JS()
|
||||
external InitCodeClientFn get initCodeClient;
|
||||
@staticInterop
|
||||
abstract class GoogleAccountsOauth2 {}
|
||||
|
||||
/// The type of the [initCodeClient] function.
|
||||
typedef InitCodeClientFn = CodeClient Function(CodeClientConfig config);
|
||||
/// The `google.accounts.oauth2` methods
|
||||
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);
|
||||
|
||||
/*
|
||||
// Data type: CodeClientConfig
|
||||
// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig
|
||||
*/
|
||||
/// Initializes and returns a token client, with the passed-in [config].
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Data type: CodeClientConfig
|
||||
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig
|
||||
@JS()
|
||||
@anonymous
|
||||
@staticInterop
|
||||
@ -51,6 +101,7 @@ abstract class CodeClientConfig {
|
||||
String? redirect_uri,
|
||||
bool? auto_select,
|
||||
CodeClientCallbackFn? callback,
|
||||
ErrorCallbackFn? error_callback,
|
||||
String? state,
|
||||
bool? enable_serial_consent,
|
||||
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.
|
||||
///
|
||||
/// 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()
|
||||
@staticInterop
|
||||
abstract class CodeClient {}
|
||||
@ -78,12 +127,10 @@ extension CodeClientExtension on CodeClient {
|
||||
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].
|
||||
///
|
||||
/// Data type: CodeResponse
|
||||
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse
|
||||
@JS()
|
||||
@staticInterop
|
||||
abstract class CodeResponse {}
|
||||
@ -116,27 +163,10 @@ extension CodeResponseExtension on CodeResponse {
|
||||
/// 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.
|
||||
///
|
||||
/// Data type: TokenClientConfig
|
||||
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClientConfig
|
||||
@JS()
|
||||
@anonymous
|
||||
@staticInterop
|
||||
@ -149,6 +179,7 @@ abstract class TokenClientConfig {
|
||||
required String client_id,
|
||||
required TokenClientCallbackFn? callback,
|
||||
required String scope,
|
||||
ErrorCallbackFn? error_callback,
|
||||
String? prompt,
|
||||
bool? enable_serial_consent,
|
||||
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.
|
||||
///
|
||||
/// 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()
|
||||
@staticInterop
|
||||
abstract class TokenClient {}
|
||||
@ -177,13 +206,10 @@ extension TokenClientExtension on TokenClient {
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
// Data type: OverridableTokenClientConfig
|
||||
// https://developers.google.com/identity/oauth2/web/reference/js-reference#OverridableTokenClientConfig
|
||||
*/
|
||||
|
||||
/// The overridable configuration object for the
|
||||
/// [TokenClientExtension.requestAccessToken] method.
|
||||
/// The overridable configuration object for the [TokenClientExtension.requestAccessToken] method.
|
||||
///
|
||||
/// Data type: OverridableTokenClientConfig
|
||||
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#OverridableTokenClientConfig
|
||||
@JS()
|
||||
@anonymous
|
||||
@staticInterop
|
||||
@ -229,15 +255,18 @@ abstract class OverridableTokenClientConfig {
|
||||
/// uses to maintain state between your authorization request and the
|
||||
/// authorization server's response.
|
||||
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].
|
||||
///
|
||||
/// Data type: TokenResponse
|
||||
/// https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse
|
||||
@JS()
|
||||
@staticInterop
|
||||
abstract class TokenResponse {}
|
||||
@ -283,63 +312,55 @@ extension TokenResponseExtension on TokenResponse {
|
||||
/// 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
|
||||
*/
|
||||
/// The type of the `error_callback` in both oauth2 initXClient calls.
|
||||
///
|
||||
/// (Currently undocumented)
|
||||
///
|
||||
/// `error` should be of type [GoogleIdentityServicesError]?, but it cannot be
|
||||
/// because of this DDC bug: https://github.com/dart-lang/sdk/issues/50899
|
||||
typedef ErrorCallbackFn = void Function(Object? error);
|
||||
|
||||
/// 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()
|
||||
external HasGrantedScopesFn get hasGrantedAllScopes;
|
||||
@staticInterop
|
||||
abstract class GoogleIdentityServicesError extends DomError {}
|
||||
|
||||
/*
|
||||
// Method: google.accounts.oauth2.hasGrantedAnyScopes
|
||||
// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.hasGrantedAnyScopes
|
||||
*/
|
||||
/// Methods of the GoogleIdentityServicesError object.
|
||||
///
|
||||
/// Cannot be used: https://github.com/dart-lang/sdk/issues/50899
|
||||
extension GoogleIdentityServicesErrorExtension on GoogleIdentityServicesError {
|
||||
@JS('type')
|
||||
external String get _type;
|
||||
// String get _type => js_util.getProperty<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()
|
||||
external HasGrantedScopesFn get hasGrantedAnyScopes;
|
||||
@staticInterop
|
||||
abstract class TokenRevocationResponse {}
|
||||
|
||||
/// 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,
|
||||
]);
|
||||
/// The fields that are contained in the [TokenRevocationResponse] object.
|
||||
extension TokenRevocationResponseExtension on TokenRevocationResponse {
|
||||
/// This field is a boolean value set to true if the revoke method call
|
||||
/// succeeded or false on failure.
|
||||
external bool get successful;
|
||||
|
||||
/*
|
||||
// Method: google.accounts.oauth2.revoke
|
||||
// https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke
|
||||
*/
|
||||
/// 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;
|
||||
|
||||
/// 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);
|
||||
/// The description of the error.
|
||||
external String? get error_description;
|
||||
}
|
||||
|
@ -213,3 +213,159 @@ enum CredentialSelectBy {
|
||||
@override
|
||||
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;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ name: google_identity_services_web
|
||||
description: A Dart JS-interop layer for Google Identity Services. Google's new sign-in SDK for Web that supports multiple types of credentials.
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/google_identity_services_web
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_identiy_services_web%22
|
||||
version: 0.1.1
|
||||
version: 0.2.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
@ -1,3 +1,8 @@
|
||||
# Tests
|
||||
|
||||
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).
|
||||
|
Reference in New Issue
Block a user