Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 98 additions & 22 deletions packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@ import 'dart:html' as html;

import 'package:flutter/services.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:js/js.dart';

import 'src/gapi.dart';

const String _kClientIdMetaSelector = 'meta[name=google-signin-client_id]';
const String _kClientIdAttributeName = 'content';
const List<String> _kJsLibraries = <String>[
'https://apis.google.com/js/platform.js'
];

/// Implementation of the google_sign_in plugin for Web
class GoogleSignInPlugin {
GoogleSignInPlugin() {
_jsLibrariesLoading = _injectJSLibraries(<String>[
'https://apis.google.com/js/platform.js',
])..then(_initGapi);
_autoDetectedClientId = html
.querySelector(_kClientIdMetaSelector)
?.getAttribute(_kClientIdAttributeName);

_isGapiInitialized = _injectJSLibraries(_kJsLibraries).then((_) => _initGapi());
}

Future<List<void>> _jsLibrariesLoading;
Future<void> _isGapiInitialized;
String _autoDetectedClientId;

Future<void> get isJsLoaded => _jsLibrariesLoading;

static void registerWith(Registrar registrar) {
final MethodChannel channel = MethodChannel(
'plugins.flutter.io/google_sign_in',
Expand All @@ -28,14 +38,50 @@ class GoogleSignInPlugin {

Future<dynamic> handleMethodCall(MethodCall call) async {
// Await for initialization promises to complete, then do your thing...
await isJsLoaded;
await _isGapiInitialized;

html.window.console.log('Doing things! $call');
final GoogleAuth authInstance = gapi.auth2.getAuthInstance();
final GoogleUser currentUser = authInstance?.currentUser?.get();

switch (call.method) {
case 'init':
// Initialize the gapi
return _init(call.arguments);
_init(call.arguments);
return true;
break;
case 'signInSilently':
// TODO: convert call.arguments to an Auth2SignInOptions object (when needed)
await _signIn(Auth2SignInOptions(
prompt: 'none',
));
return _currentUserToPluginMap(currentUser);
break;
case 'signIn':
// TODO: convert call.arguments to an Auth2SignInOptions object (when needed)
await _signIn(null);
return _currentUserToPluginMap(currentUser);
break;
case 'getTokens':
final Auth2AuthResponse response = currentUser.getAuthResponse();
return <String, String>{
'idToken': response.id_token,
'accessToken': response.access_token,
};
break;
case 'signOut':
await _signOut();
return null;
break;
case 'disconnect':
currentUser.disconnect();
return null;
break;
case 'isSignedIn':
return currentUser.isSignedIn();
break;
case 'clearAuthCache':
// We really don't keep any cache here, but let's try to be drastic:
authInstance.disconnect();
return null;
break;
default:
throw PlatformException(
Expand All @@ -45,31 +91,61 @@ class GoogleSignInPlugin {
}
}

Map<String, dynamic> _currentUserToPluginMap(GoogleUser currentUser) {
assert(currentUser != null);
final Auth2BasicProfile profile = currentUser.getBasicProfile();
return <String, dynamic>{
'displayName': profile?.getName(),
'email': profile?.getEmail(),
'id': profile?.getId(),
'photoUrl': profile?.getImageUrl(),
'idToken': currentUser?.getAuthResponse()?.id_token,
};
}

// Load the auth2 library
Future<void> _init(Map<String, dynamic> arguments) {
//
GoogleAuth _init(dynamic arguments) => gapi.auth2.init(Auth2ClientConfig(
hosted_domain: arguments['hostedDomain'],
// The js lib wants a space-separated list of values
scope: arguments['scopes'].join(' '),
client_id: arguments['clientId'] ?? _autoDetectedClientId,
));

Future<dynamic> _signIn(Auth2SignInOptions signInOptions) async {
return html.promiseToFuture<dynamic>(
gapi.auth2.getAuthInstance().signIn(signInOptions));
}

Future<void> _signOut() async {
return html.promiseToFuture<void>(gapi.auth2.getAuthInstance().signOut());
}

Future<void> _initGapi(List<void> _) {
// JS-interop with the global gapi method and call gapi.load('auth2'), and wait for the
// promise to resolve...

Future<void> _initGapi() {
// JS-interop with the global gapi method and call gapi.load('auth2'),
// then wait for the promise to resolve...
final Completer<void> gapiLoadCompleter = Completer<void>();
gapi.load('auth2', allowInterop(() {
gapiLoadCompleter.complete();
}));

// After this is resolved, we can use gapi.auth2!
return gapiLoadCompleter.future;
}

/// Injects a bunch of libraries in the <head> and returns a
/// Injects a bunch of libraries in the <head> and returns a
/// Future that resolves when all load.
Future<List<void>> _injectJSLibraries(List<String> libraries, { Duration timeout }) {
final List<Future<void>> loading = <Future<void>>[
Future<bool>.delayed(const Duration(seconds: 10), () => true) // TODO: Remove this delay before submitting :)
];
Future<void> _injectJSLibraries(List<String> libraries,
{Duration timeout}) {
final List<Future<void>> loading = <Future<void>>[];
final List<html.HtmlElement> tags = <html.HtmlElement>[];

libraries.forEach((String library) {
final html.ScriptElement script = html.ScriptElement()
..async = true
..defer = true
..src = library;
loading.add(script.onLoad.first); // TODO add a timeout race to fail this future
// TODO add a timeout race to fail this future
loading.add(script.onLoad.first);
tags.add(script);
});
html.querySelector('head').children.addAll(tags);
Expand Down
168 changes: 168 additions & 0 deletions packages/google_sign_in/google_sign_in_web/lib/src/gapi.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/// This library describes the global gapi object, and the 'auth2' module
/// as described here: https://developers.google.com/identity/sign-in/web/reference
@JS()
library gapi;

import 'dart:async';
import 'dart:html' as html;

import 'package:js/js.dart';

// Global window.gapi, we need to call `load('auth2', cb)` on it...
@JS('gapi')
external Gapi get gapi;

@JS()
abstract class Gapi {
external void load(String module, Function callback);
external Auth2 get auth2;
}

@JS()
abstract class Auth2 {
// https://developers.google.com/identity/sign-in/web/reference#gapiauth2initparams
external GoogleAuth init(Auth2ClientConfig params);
external GoogleAuth getAuthInstance();
external void authorize(
Auth2AuthorizeConfig params, Auth2AuthorizeCallback callback);
}

typedef Auth2AuthorizeCallback = void Function(Auth2AuthorizeResponse response);

@JS()
abstract class Auth2CurrentUser {
external GoogleUser get();
external void listen(Auth2CurrentUserListener listener);
}

typedef Auth2CurrentUserListener = void Function(bool);

@JS()
abstract class GoogleAuth {
external Future<void> then(Function onInit, Function onError);
// Authentication: https://developers.google.com/identity/sign-in/web/reference#authentication
external Future<GoogleUser> signIn([Auth2SignInOptions options]);
external Future<void> signOut();
external void disconnect();
// Offline access not implemented
external void attachClickHandler(html.HtmlElement container,
Auth2SignInOptions options, Function onSuccess, Function onFailure);
external Auth2CurrentUser get currentUser;
}

// https://developers.google.com/identity/sign-in/web/reference#googleusergetbasicprofile
@JS()
abstract class Auth2BasicProfile {
external String getId();
external String getName();
external String getGivenName();
external String getFamilyName();
external String getImageUrl();
external String getEmail();
}

// https://developers.google.com/identity/sign-in/web/reference#gapiauth2authresponse
@JS()
abstract class Auth2AuthResponse {
external String get access_token;
external String get id_token;
external String get scope;
external num get expires_in;
external num get first_issued_at;
external num get expires_at;
}

// https://developers.google.com/identity/sign-in/web/reference#gapiauth2authorizeresponse
@JS()
abstract class Auth2AuthorizeResponse {
external String get access_token;
external String get id_token;
external String get code;
external String get scope;
external int get expires_in;
external int get first_issued_at;
external int get expires_at;
external String get error;
external String get error_subtype;
}

// https://developers.google.com/identity/sign-in/web/reference#users
@JS()
abstract class GoogleUser {
external String getId();
external bool isSignedIn();
external String getHostedDomain();
external String getGrantedScopes();
external Auth2BasicProfile getBasicProfile();
external Auth2AuthResponse getAuthResponse([bool includeAuthorizationData]);
external Future<Auth2AuthResponse> reloadAuthResponse();
external bool hasGrantedScopes(String scopes);
external void grant(Auth2SignInOptions options);
// Offline access not implemented
external void disconnect();
}

// https://developers.google.com/identity/sign-in/web/reference#gapiauth2signinoptions
@JS()
@anonymous
abstract class Auth2SignInOptions {
external factory Auth2SignInOptions(
{String prompt, String scope, String ux_mode, String redirect_uri});

external String get prompt;
external String get scope;
external String get ux_mode;
external String get redirect_uri;
}

// https://developers.google.com/identity/sign-in/web/reference#gapiauth2clientconfig
@JS()
@anonymous
abstract class Auth2ClientConfig {
external factory Auth2ClientConfig({
String client_id,
String cookie_policy,
String scope,
bool fetch_basic_profile,
String hosted_domain,
String open_id_realm,
String ux_mode,
String redirect_uri,
});

external String get client_id;
external String get cookie_policy;
external String get scope;
external bool get fetch_basic_profile;
external String get hosted_domain;
external String get open_id_realm;
external String get ux_mode;
external String get redirect_uri;
}

// https://developers.google.com/identity/sign-in/web/reference#gapiauth2authorizeconfig
@JS()
@anonymous
abstract class Auth2AuthorizeConfig {
external factory Auth2AuthorizeConfig({
String client_id,
String scope,
String response_type,
String prompt,
String cookie_policy,
String hosted_domain,
String login_hint,
String open_id_realm,
bool include_granted_scopes,
});

external String get client_id;
external String get scope;
external String get response_type;
external String get prompt;
external String get cookie_policy;
external String get hosted_domain;
external String get login_hint;
external String get open_id_realm;
external bool get include_granted_scopes;
}
1 change: 1 addition & 0 deletions packages/google_sign_in/google_sign_in_web/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies:
flutter_web_plugins:
sdk: flutter
meta: ^1.1.7
js: ^0.6.1

dev_dependencies:
flutter_test:
Expand Down