-
Notifications
You must be signed in to change notification settings - Fork 6
[WIP] Google sign in web #85
Changes from 1 commit
10e5030
b721c7a
1026a3a
4beb332
a53eece
209dcd7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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:google_sign_in_web/src/gapi.dart'; | ||
| import 'package:js/js.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 = | ||
| Future.wait(_injectJSLibraries(_kJsLibraries)).then(_initGapi); | ||
ditman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| 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', | ||
|
|
@@ -28,14 +38,35 @@ 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.debug(call); | ||
ditman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| html.window.console.log('Doing things! $call'); | ||
| final GoogleUser currentUser = | ||
| gapi.auth2.getAuthInstance()?.currentUser?.get(); | ||
|
|
||
| switch (call.method) { | ||
| case 'init': | ||
| // Initialize the gapi | ||
| return _init(call.arguments); | ||
| case 'init': // void | ||
| return _init(call.arguments) | ||
|
||
| .toString(); // Anything serializable, really! | ||
| break; | ||
| case 'signInSilently': | ||
| await _silentSignIn(currentUser, call.arguments); | ||
| return _currentUserToPluginMap(currentUser); | ||
| break; | ||
| case 'signIn': | ||
| await _signIn(call.arguments); | ||
| return _currentUserToPluginMap(currentUser); | ||
| break; | ||
| case 'disconnect': | ||
| currentUser.disconnect(); | ||
| return null; | ||
| case 'getTokens': | ||
| final Auth2AuthResponse response = currentUser.getAuthResponse(); | ||
| return <String, String>{ | ||
| 'idToken': response.id_token, | ||
| 'accessToken': response.access_token, | ||
| }; | ||
| break; | ||
| default: | ||
| throw PlatformException( | ||
|
|
@@ -45,34 +76,67 @@ 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( | ||
| hostedDomain: arguments['hostedDomain'], | ||
| scope: arguments['scopes'].join( | ||
| ' '), // The backend wants a space-separated list of values, not an array | ||
| clientId: arguments['clientId'] ?? _autoDetectedClientId, | ||
| )); | ||
|
|
||
| Future<dynamic> _signIn(dynamic arguments) async { | ||
| return html.promiseToFuture<dynamic>(gapi.auth2.getAuthInstance().signIn()); | ||
| } | ||
|
|
||
| Future<void> _initGapi(List<void> _) { | ||
| // JS-interop with the global gapi method and call gapi.load('auth2'), and wait for the | ||
| Future<dynamic> _silentSignIn( | ||
| GoogleUser currentUser, dynamic arguments) async { | ||
| return html.promiseToFuture<dynamic>( | ||
| gapi.auth2.getAuthInstance().signIn(Auth2SignInOptions( | ||
| prompt: 'none', | ||
| ))); | ||
| } | ||
|
|
||
| Future<void> _initGapi(dynamic _) { | ||
ditman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // JS-interop with the global gapi method and call gapi.load('auth2'), and wait for the | ||
| // promise to resolve... | ||
|
|
||
| final Completer<void> gapiLoadCompleter = Completer<void>(); | ||
| gapi.load('auth2', allowInterop(() { | ||
| gapiLoadCompleter.complete(); | ||
| })); | ||
|
|
||
| return gapiLoadCompleter | ||
| .future; // After this is resolved, we can use gapi.auth2! | ||
| } | ||
|
|
||
| /// 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 :) | ||
| ]; | ||
| List<Future<void>> _injectJSLibraries(List<String> libraries, | ||
ditman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {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 | ||
| loading.add( | ||
| script.onLoad.first); // TODO add a timeout race to fail this future | ||
ditman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| tags.add(script); | ||
| }); | ||
| html.querySelector('head').children.addAll(tags); | ||
| return Future.wait(loading); | ||
| return loading; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: prefer to use relative imports for same-package imports (i.e.
import 'src/gapi.dart';)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change this, but I don't know enough dart to have been bitten by any issue with package vs relative imports. Any reason for this?
I did this because I think I remember reading somewhere in the dart docs something along the lines "always prefer import from package:blah, because it always works" or similar :/