capacitor-native-google-one-tap-signin 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CapacitorNativeGoogleOneTapSignin.podspec +17 -15
  2. package/README.md +104 -21
  3. package/android/build.gradle +3 -2
  4. package/android/src/main/java/com/proventask/capacitor/GoogleOneTapAuth/GoogleOneTapAuth.java +147 -69
  5. package/dist/docs.json +345 -0
  6. package/dist/esm/GoogleOneTapAuth.d.ts +13 -0
  7. package/dist/esm/GoogleOneTapAuth.js +87 -0
  8. package/dist/esm/GoogleOneTapAuth.js.map +1 -0
  9. package/dist/esm/definitions.d.ts +76 -53
  10. package/dist/esm/definitions.js.map +1 -1
  11. package/dist/esm/helpers.d.ts +2 -0
  12. package/dist/esm/helpers.js +15 -0
  13. package/dist/esm/helpers.js.map +1 -0
  14. package/dist/esm/index.d.ts +1 -2
  15. package/dist/esm/index.js +1 -4
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/web.d.ts +10 -14
  18. package/dist/esm/web.js +59 -66
  19. package/dist/esm/web.js.map +1 -1
  20. package/dist/plugin.cjs.js +256 -0
  21. package/dist/plugin.cjs.js.map +1 -0
  22. package/dist/plugin.js +257 -0
  23. package/dist/plugin.js.map +1 -0
  24. package/ios/Plugin/Plugin.m +3 -1
  25. package/ios/Plugin/Plugin.swift +74 -133
  26. package/package.json +60 -34
  27. package/android/.gradle/7.3.3/checksums/checksums.lock +0 -0
  28. package/android/.gradle/7.3.3/dependencies-accessors/dependencies-accessors.lock +0 -0
  29. package/android/.gradle/7.3.3/dependencies-accessors/gc.properties +0 -0
  30. package/android/.gradle/7.3.3/executionHistory/executionHistory.bin +0 -0
  31. package/android/.gradle/7.3.3/executionHistory/executionHistory.lock +0 -0
  32. package/android/.gradle/7.3.3/fileChanges/last-build.bin +0 -0
  33. package/android/.gradle/7.3.3/fileHashes/fileHashes.bin +0 -0
  34. package/android/.gradle/7.3.3/fileHashes/fileHashes.lock +0 -0
  35. package/android/.gradle/7.3.3/fileHashes/resourceHashesCache.bin +0 -0
  36. package/android/.gradle/7.3.3/gc.properties +0 -0
  37. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  38. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  39. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  40. package/android/.gradle/vcs-1/gc.properties +0 -0
  41. package/android/.idea/workspace.xml +0 -89
  42. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  43. package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  44. package/android/gradle.properties +0 -16
  45. package/android/gradlew +0 -160
  46. package/android/gradlew.bat +0 -90
  47. package/android/proguard-rules.pro +0 -21
  48. package/android/settings.gradle +0 -2
  49. package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +0 -26
  50. package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +0 -17
  51. package/ios/Plugin.xcodeproj/project.pbxproj +0 -565
  52. package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  53. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
  54. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  55. package/ios/PluginTests/Info.plist +0 -22
  56. package/ios/PluginTests/PluginTests.swift +0 -35
  57. package/ios/Podfile +0 -23
  58. package/ios/Podfile.lock +0 -47
@@ -1,16 +1,18 @@
1
+ require 'json'
1
2
 
2
- Pod::Spec.new do |s|
3
- s.name = 'CapacitorNativeGoogleOneTapSignin'
4
- s.version = '0.0.1'
5
- s.summary = 'Google One Tap sign-in plugin for capacitor.'
6
- s.license = 'MIT'
7
- s.homepage = 'https://github.com/inouiw/capacitor-native-google-one-tap-signin'
8
- s.author = 'David Neuy'
9
- s.source = { :git => 'https://github.com/inouiw/capacitor-native-google-one-tap-signin.git', :tag => s.version.to_s }
10
- s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
11
- s.ios.deployment_target = '12.0'
12
- s.dependency 'Capacitor'
13
- s.dependency 'GoogleSignIn', '~> 6.2.4'
14
- s.static_framework = true,
15
- s.swift_version = '5.1'
16
- end
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapacitorNativeGoogleOneTapSignin'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '13.0'
15
+ s.dependency 'Capacitor'
16
+ s.dependency 'GoogleSignIn'
17
+ s.swift_version = '5.1'
18
+ end
package/README.md CHANGED
@@ -2,51 +2,134 @@
2
2
 
3
3
  Wraps the native android One Tap api for ionic capacitor apps. One Tap Sign In is part of Sign in with Google and offers features as automatic sign-in on return visits.
4
4
 
5
+ This library intends to provide the best google authentication experience for each platform.
6
+
5
7
  <img src="screenshots/one-tap-sign-in-demo-initial-sign-in.jpg" alt="One tap signin screenshot initial sign-in" width=250/>
6
8
  <img src="screenshots/one-tap-sign-in-demo-sign-back-in.jpg" alt="One tap signin screenshot sign back in" width=250/>
7
9
  <img src="screenshots/one-tap-sign-in-demo-without-filter-by-authorized-acounts.jpg" alt="One tap signin screenshot without filter by authorized accounts" width=250/>
8
10
 
9
11
 
10
- Currently the idToken, id (e-mail), displayName, givenName, familyName, profilePictureUri are returned. You may check the idToken contents on https://jwt.io/.
12
+ After a successful authentication, the idToken is returned as base64 and as object.
11
13
 
12
14
  The user is automatically signed-in, without a prompt after the first sign-in.
13
15
 
14
- API version 29 or higher seems to be required for one-tap sign-in to work.
16
+ The andorid minSdkVersion is 24, however the one-tap library seems to require API version 29. For lower API versions GoogleSignIn is used.
15
17
 
18
+ # Install and Configure
16
19
 
17
- # Demos
18
- See the `demo` folder.
20
+ #### 1. Install package
19
21
 
20
- # Setup
21
- You need to provide the plug-in a client ID of type "Web application". However you will also need to create a client ID of type "Android" as stated in the [One Tap Get started docu](https://developers.google.cn/identity/one-tap/android/get-started) in the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard).
22
+ ```sh
23
+ npm i --save capacitor-native-google-one-tap-signin
24
+ ```
22
25
 
23
- To test it using an emulator, you need to create an emulator with android play services and API version 29 or higher. See [my stackoverflow answer](https://stackoverflow.com/questions/71325279/missing-featurename-auth-api-credentials-begin-sign-in-version-6/75285717#75285717).
26
+ #### 2. Update capacitor dependencies
24
27
 
25
- # Design decisions
26
- Promises will not be rejected for anticipated unsuccessful control flow. For example, if the one tap auto-login does not succeed with the reason `opt_out_or_no_session` then this is not exceptional but expected in many cases. Rejecting the promise would mean the caller would have to catch an exception when calling `await xy` and then run follow-up code in the catch block. Instead of rejecting promises the resolved promise contains a `isSuccess` property.
28
+ ```sh
29
+ npx cap update
30
+ ```
27
31
 
28
- ## Contributions
29
- Welcome
32
+ #### 3. Configure
33
+ You need to provide the plug-in a client ID of type "Web application". This client ID is used for the web and android platforms. The client ID is passed in the `initialize` call.
30
34
 
31
- ## Install
35
+ For the android platform, you need to create a client ID of type "Android" in the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard), as stated in [One Tap Get started docu](https://developers.google.cn/identity/one-tap/android/get-started). You just need to create that client ID, you do not need to include it in any config.
32
36
 
33
- #### 1. Install package
37
+ For the ios platform, you need to create a client ID of type iOS in the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard). Add the client ID to the `{your-app}/ios/App/App/Info.plist` file with the key `GIDClientID` (see the demo app for reference). When creating the client ID, you will see the "iOS URL scheme" value in the Google Cloud Console. Add this also to the `Info.plist` file.
34
38
 
35
- ```sh
36
- npm i --save capacitor-native-google-one-tap-signin
39
+ # Usage
40
+
41
+ ```TypeScript
42
+ import { GoogleOneTapAuth, SignInResult } from 'capacitor-native-google-one-tap-signin';
43
+
44
+ await GoogleOneTapAuth.initialize({ clientId: clientId });
45
+
46
+ // Example 1: Showing the button only if auto-sign-in or one-tap sign in fail.
47
+ const signInResult = await GoogleOneTapAuth.tryAutoOrOneTapSignIn().then(res => res.signInResultOptionPromise);
48
+ if (signInResult.isSuccess) {
49
+ console.log(signInResult);
50
+ } else { 
51
+ const successResult = await GoogleOneTapAuth.renderSignInButton('appleid-signin', {}, { locale: 'en-US', theme: 'outline', text: 'continue_with', shape: 'rectangular' });
52
+ console.log(successResult);
53
+ }
54
+
55
+ // See the demo folder for an example application.
37
56
  ```
38
57
 
39
- #### 2. Update capacitor deps
58
+ ```TypeScript
59
+ import { GoogleOneTapAuth, SignInResult } from 'capacitor-native-google-one-tap-signin';
60
+
61
+ await GoogleOneTapAuth.initialize({ clientId: clientId });
62
+
63
+ // Example 2: Trigger auto-sign-in or one-tap sign and show the button in parallel.
64
+ const autoOrOneTapSuccessPromise = GoogleOneTapAuth.tryAutoOrOneTapSignIn().then(res => res.successPromise);
65
+ const renderButtonPromise = GoogleOneTapAuth.renderSignInButton('appleid-signin', {}, { locale: 'en-US', theme: 'outline', text: 'continue_with', shape: 'rectangular' });
66
+ const signInResultSuccess = await Promise.race([autoOrOneTapSuccessPromise, renderButtonPromise]);
67
+ console.log(signInResultSuccess);
68
+
69
+ // See the demo folder for an example application.
70
+ ```
71
+
72
+ # Notes about testing with an emulator
73
+
74
+ To test it using an emulator, you need to create an emulator with android play services and API version 24 or higher. See [my stackoverflow answer](https://stackoverflow.com/questions/71325279/missing-featurename-auth-api-credentials-begin-sign-in-version-6/75285717#75285717). For older API levels like 24, you may need to update Chrome after creating the emulator because the plugin targets JavaScript es2017.
75
+
76
+ # Supported platforms
77
+ Android, iOS, Web.
78
+
79
+ # Demos
80
+ See the `demo` folder.
81
+
82
+ # Detailed description of different use cases
83
+
84
+ See https://developers.google.com/identity/gsi/web/guides/features
85
+
86
+ # Exposed api
87
+ See `src/definitions.ts` for a complete definition.
88
+
89
+ ```TypeScript
90
+ /**
91
+ * For the web platform, starts pre-loading the google one tap JavaScript library.
92
+ * @param options
93
+ */
94
+ initialize(options: InitializeOptions): Promise<void>;
95
+ /**
96
+ * Tries to either auto-sign-in the user or sign-in the user with just one tap/click.
97
+ * If there is a single google account and that account has previously signed into the app, then that user is auto signed in. A short popover is displayed during sign-in.
98
+ * If there are multiple google accounts and more than one have previously signed into the app then a user selection screen is shown.
99
+ * If there is no active google session or if no user session has logged in previously in the app or if the user has opt out of One Tap, then the response will indicate that the auto sign-in did not succeed.
100
+ * See https://developers.google.com/identity/gsi/web/guides/features
101
+ * @returns A Promise object that contains 3 properties with promises. One resolves only when authentication succeeds, the second on error and the third on success or error.
102
+ */
103
+ tryAutoOrOneTapSignIn(): Promise<SignInResultPromises>;
104
+ /**
105
+ * Renders the sign-in button.
106
+ * The returned promise will only resolve if successful.
107
+ * @param parentElementId
108
+ * @param options
109
+ * @param gsiButtonConfiguration Not all button configuration options are supported on android.
110
+ */
111
+ renderSignInButton(parentElementId: string, options: RenderSignInButtonOptions, gsiButtonConfiguration?: google.GsiButtonConfiguration): Promise<SuccessSignInResult>;
112
+ /**
113
+ * Ends the session.
114
+ */
115
+ signOut(): Promise<SignOutResult>;
116
+ /**
117
+ * Gets the last user defined or auto-created nonce.
118
+ * Unfortunately not all google libraries support setting a nonce, so this is currently not universally useful.
119
+ */
120
+ getNonce(): string;
40
121
 
41
- ```sh
42
- npx cap update
43
122
  ```
44
123
 
45
- ## Usage
124
+ # Design decisions
125
+ Promises will not be rejected for anticipated unsuccessful control flow. For example, if the one tap auto-login does not succeed with the reason `opt_out_or_no_session` then this is not exceptional but expected in many cases. Rejecting the promise would mean the caller would have to catch an exception when calling `await xy` and then run follow-up code in the catch block. However when creating the demo app it was also evident that it is useful to have a promise that only resolves when authentication succeeds. Therefore the `tryAutoOrOneTapSignIn` method returns a Promise object that contains 3 properties with promises. One resolves only when authentication succeeds, the second on error and the third on success or error.
126
+
127
+ Instead of creating one signIn method with many parameters that are only used in some cases (=unclear dependencies), there are two methods with different parameters and different return types.
46
128
 
47
- ### Android
129
+ Parameters that are independent of a sign-in method as the clientId must be passed to `initialize`.
48
130
 
49
- todo
131
+ ## Contributions
132
+ Welcome
50
133
 
51
134
  ## License
52
135
 
@@ -4,7 +4,7 @@ buildscript {
4
4
  google()
5
5
  }
6
6
  dependencies {
7
- classpath 'com.android.tools.build:gradle:7.2.1'
7
+ classpath 'com.android.tools.build:gradle:7.4.0'
8
8
  classpath 'com.google.gms:google-services:4.3.13'
9
9
  }
10
10
  }
@@ -14,7 +14,7 @@ apply plugin: 'com.android.library'
14
14
  android {
15
15
  compileSdkVersion 31
16
16
  defaultConfig {
17
- minSdkVersion 29
17
+ minSdkVersion 24
18
18
  targetSdkVersion 31
19
19
  versionCode 1
20
20
  versionName "1.0"
@@ -51,5 +51,6 @@ dependencies {
51
51
  implementation 'com.google.android.gms:play-services-auth:20.4.1'
52
52
  implementation 'com.google.android.gms:play-services-tasks:18.0.2'
53
53
  implementation 'com.google.android.gms:play-services-base:18.0.1'
54
+ implementation 'org.json:json:20220924'
54
55
  }
55
56
 
@@ -14,39 +14,65 @@ import com.getcapacitor.JSObject;
14
14
  import com.getcapacitor.Plugin;
15
15
  import com.getcapacitor.PluginCall;
16
16
  import com.getcapacitor.PluginMethod;
17
+ import com.getcapacitor.annotation.ActivityCallback;
17
18
  import com.getcapacitor.annotation.CapacitorPlugin;
18
19
  import com.google.android.gms.auth.api.identity.BeginSignInRequest;
19
20
  import com.google.android.gms.auth.api.identity.Identity;
20
21
  import com.google.android.gms.auth.api.identity.SignInClient;
22
+ import com.google.android.gms.auth.api.signin.GoogleSignIn;
23
+ import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
24
+ import com.google.android.gms.auth.api.signin.GoogleSignInClient;
25
+ import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
26
+ import com.google.android.gms.auth.api.signin.GoogleSignInStatusCodes;
21
27
  import com.google.android.gms.common.ConnectionResult;
22
28
  import com.google.android.gms.common.GoogleApiAvailability;
23
29
  import com.google.android.gms.common.api.ApiException;
30
+ import com.google.android.gms.tasks.Task;
31
+
32
+ import org.json.JSONException;
33
+ import org.json.JSONObject;
24
34
 
25
- import java.util.Base64;
26
35
  import java.util.concurrent.CompletableFuture;
27
36
  import java.util.concurrent.ExecutionException;
28
37
  import java.util.concurrent.Future;
29
38
 
30
39
  @CapacitorPlugin()
31
40
  public class GoogleOneTapAuth extends Plugin {
41
+ private enum SignInMethod {NotSignedIn, OneTap, GoogleSignIn}
32
42
  private static final String TAG = "GoogleOneTapAuth Plugin";
33
43
  private String clientId;
44
+ private String nonce;
34
45
  private SignInClient oneTapClient;
46
+ private GoogleSignInClient googleSignInClient;
35
47
  private ActivityResultLauncher<IntentSenderRequest> googleOneTapSignInActivityResultHandlerIntentSenderRequest;
36
- private CompletableFuture<JSObject> signInFuture;
48
+ private CompletableFuture<JSObject> oneTapSignInFuture;
49
+ private CompletableFuture<JSObject> googleSignInFuture;
50
+ private SignInMethod signInMethod = SignInMethod.NotSignedIn;
37
51
 
38
52
  @Override
39
53
  public void load() {
40
- clientId = getConfig().getString("clientId");
54
+ registerSignInResultHandler();
55
+ }
41
56
 
42
- if (clientId == null || clientId.endsWith("apps.googleusercontent.com") == false) {
43
- throw new RuntimeException("clientId must end with 'apps.googleusercontent.com' but is: " + clientId + ". Check capacitor.config.ts.");
44
- }
57
+ @PluginMethod()
58
+ public void initialize(PluginCall call) {
59
+ initializeOptionsFromCall(call);
45
60
  oneTapClient = Identity.getSignInClient(this.getActivity());
46
- RegisterSignInResultHandler();
61
+
62
+ var gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
63
+ .requestEmail()
64
+ .requestIdToken(clientId)
65
+ .build();
66
+ googleSignInClient = GoogleSignIn.getClient(this.getActivity(), gso);
67
+ call.resolve();
47
68
  }
48
69
 
49
- private void RegisterSignInResultHandler() {
70
+ private void initializeOptionsFromCall(PluginCall call) {
71
+ clientId = call.getString("clientId");
72
+ nonce = call.getString("nonce");
73
+ }
74
+
75
+ private void registerSignInResultHandler() {
50
76
  googleOneTapSignInActivityResultHandlerIntentSenderRequest = bridge.registerForActivityResult(
51
77
  new ActivityResultContracts.StartIntentSenderForResult(),
52
78
  result -> {
@@ -56,67 +82,57 @@ public class GoogleOneTapAuth extends Plugin {
56
82
  try {
57
83
  Intent data = result.getData();
58
84
  var credential = oneTapClient.getSignInCredentialFromIntent(data);
59
- var email = credential.getId();
60
- signInFuture.complete(createSuccessResponse(credential.getGoogleIdToken(), email));
85
+ signInMethod = SignInMethod.OneTap;
86
+ oneTapSignInFuture.complete(createSuccessResponse(credential.getGoogleIdToken()));
61
87
  } catch (ApiException e) {
62
- signInFuture.complete(createErrorResponse(e.toString()));
88
+ oneTapSignInFuture.complete(createErrorResponse(null, e.toString()));
63
89
  }
64
90
  } else {
65
91
  var resultCodeString = ActivityResult.resultCodeToString(resultCode);
66
- signInFuture.complete(createErrorResponse(resultCodeString));
92
+ oneTapSignInFuture.complete(createErrorResponse(null, resultCodeString));
67
93
  }
68
94
  });
69
95
  }
70
96
 
71
97
  @PluginMethod()
72
- public void initialize(PluginCall call) {
73
- // Currently no code to run here.
74
- call.resolve();
75
- }
76
-
77
- @PluginMethod()
78
- public void tryAutoSignIn(PluginCall call) {
98
+ public void tryAutoOrOneTapSignIn(PluginCall call) {
79
99
  try {
80
100
  var signInResult = beginSignIn(true).get();
81
- call.resolve(signInResult);
82
- } catch (ExecutionException e) {
83
- call.reject(e.toString());
84
- } catch (InterruptedException e) {
101
+ if (!signInResult.has("idToken")) {
102
+ signInResult = beginSignIn(false).get();
103
+ }
104
+ if (!signInResult.has("idToken")) {
105
+ signInResult = googleSilentSignIn(call).get();
106
+ }
107
+ call.resolve(wrapInSignInResultInOption(signInResult));
108
+ } catch (ExecutionException | InterruptedException e) {
85
109
  call.reject(e.toString());
86
110
  }
87
111
  }
88
112
 
113
+ // This method is not part of the api but only called from GoogleOneTapAuth.ts in method renderSignInButton.
89
114
  @PluginMethod()
90
- public void trySignInWithPrompt(PluginCall call) {
115
+ public void triggerGoogleSignIn(PluginCall call) {
91
116
  try {
92
- var signInResult = beginSignIn(false).get();
117
+ var signInResult = googleSignIn(call).get();
118
+ assert(signInResult.has("idToken") && signInResult.getString("idToken").length() > 0);
93
119
  call.resolve(signInResult);
94
- } catch (ExecutionException e) {
95
- call.reject(e.toString());
96
- } catch (InterruptedException e) {
120
+ } catch (ExecutionException | InterruptedException e) {
97
121
  call.reject(e.toString());
98
122
  }
99
123
  }
100
124
 
101
- @PluginMethod()
102
- public void tryAutoSignInThenTrySignInWithPrompt(PluginCall call) {
103
- try {
104
- var signInResult = beginSignIn(true).get();
105
- if (!signInResult.getBool("isSuccess")) {
106
- signInResult = beginSignIn(false).get();
107
- }
108
- call.resolve(signInResult);
109
- } catch (ExecutionException e) {
110
- call.reject(e.toString());
111
- } catch (InterruptedException e) {
112
- call.reject(e.toString());
113
- }
125
+ private JSObject wrapInSignInResultInOption(JSObject successSignInResult) {
126
+ var result = new JSObject();
127
+ result.put("isSuccess", true);
128
+ result.put("success", successSignInResult);
129
+ return result;
114
130
  }
115
131
 
116
132
  private Future<JSObject> beginSignIn(boolean filterByAuthorizedAccounts) {
117
133
  var context = this.getContext();
118
134
  var beginSignInRequest = createBeginSignInRequest(filterByAuthorizedAccounts);
119
- signInFuture = new CompletableFuture<>();
135
+ oneTapSignInFuture = new CompletableFuture<>();
120
136
 
121
137
  oneTapClient.beginSignIn(beginSignInRequest)
122
138
  .addOnCompleteListener(task -> {
@@ -126,11 +142,7 @@ public class GoogleOneTapAuth extends Plugin {
126
142
  var intentSenderRequest = new IntentSenderRequest.Builder(intentSender).build();
127
143
  googleOneTapSignInActivityResultHandlerIntentSenderRequest.launch(intentSenderRequest);
128
144
  } else {
129
- if (filterByAuthorizedAccounts) {
130
- Log.i(TAG, "beginSignIn with FilterByAuthorizedAccounts=true failed", task.getException());
131
- } else {
132
- Log.e(TAG, "beginSignIn with FilterByAuthorizedAccounts=false failed", task.getException());
133
- }
145
+ Log.i(TAG, "beginSignIn with FilterByAuthorizedAccounts=" + filterByAuthorizedAccounts + " did not succeed.", task.getException());
134
146
  var errorMessage = task.getException().toString();
135
147
 
136
148
  if (!isGooglePlayServicesAvailable(context)) {
@@ -140,22 +152,23 @@ public class GoogleOneTapAuth extends Plugin {
140
152
  errorMessage += "\nGooglePlay is not installed or must be logged in to setup.";
141
153
  }
142
154
  if (errorMessage.contains("ApiException: 8")) {
143
- errorMessage += "\nOne reason for the exception is when the device has no internet.";
155
+ errorMessage += "\nOne reason for the exception is when the device has no internet, or one-tap is not possible.";
144
156
  }
145
157
  // Other errors:
146
158
  // com.google.android.gms.common.api.ApiException: 10: Caller not whitelisted to call this API.
147
159
  // --> Try if restarting the phone fixes the problem.
148
160
  // --> Test to sign in several times to ensure it works repeatedly.
149
- signInFuture.complete(createErrorResponse(errorMessage));
161
+ oneTapSignInFuture.complete(createErrorResponse(null, errorMessage));
150
162
  }
151
163
  });
152
- return signInFuture;
164
+ return oneTapSignInFuture;
153
165
  }
154
166
 
155
167
  private BeginSignInRequest createBeginSignInRequest(boolean filterByAuthorizedAccounts) {
156
168
  return BeginSignInRequest.builder()
157
169
  .setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
158
170
  .setSupported(true)
171
+ .setNonce(nonce)
159
172
  // A OAuth client ID with application type "Web application".
160
173
  .setServerClientId(clientId)
161
174
  // If true, only the Google accounts that the user has authorized before will show up in the credential list. This can
@@ -167,27 +180,36 @@ public class GoogleOneTapAuth extends Plugin {
167
180
  .build();
168
181
  }
169
182
 
170
- private JSObject createErrorResponse(String noSuccessAdditionalInfo) {
183
+ private JSObject createErrorResponse(String reasonCode, String noSuccessAdditionalInfo) {
171
184
  var result = new JSObject();
172
- result.put("isSuccess", false);
185
+ result.put("noSuccessReasonCode", reasonCode);
173
186
  result.put("noSuccessAdditionalInfo", noSuccessAdditionalInfo);
174
187
  return result;
175
188
  }
176
189
 
177
- private JSObject createSuccessResponse(String idToken, String email) {
190
+ private JSObject createSuccessResponse(String idToken) {
191
+ var decodedIdToken = decodeJwtBody(idToken);
192
+ JSONObject decodedIdTokenJson = null;
193
+ String userId = null;
194
+ String email = null;
195
+ try {
196
+ decodedIdTokenJson = new JSONObject(decodedIdToken);
197
+ userId = decodedIdTokenJson.getString("sub");
198
+ email = decodedIdTokenJson.getString("email");
199
+ } catch (JSONException e) {
200
+ // Do nothing.
201
+ }
178
202
  var result = new JSObject();
179
- result.put("isSuccess", true);
180
203
  result.put("idToken", idToken);
204
+ result.put("userId", userId);
181
205
  result.put("email", email);
182
- result.put("decodedIdToken", decodeJwtBody(idToken));
206
+ result.put("decodedIdToken", decodedIdTokenJson);
183
207
  return result;
184
208
  }
185
209
 
186
210
  private String decodeJwtBody(String jwtToken) {
187
- var decoder = Base64.getUrlDecoder();
188
211
  var parts = jwtToken.split("\\.");
189
- var payloadJson = new String(decoder.decode(parts[1]));
190
- return payloadJson;
212
+ return new String(android.util.Base64.decode(parts[1], android.util.Base64.DEFAULT));
191
213
  }
192
214
 
193
215
  private boolean isGooglePlayServicesAvailable(Context context) {
@@ -199,16 +221,72 @@ public class GoogleOneTapAuth extends Plugin {
199
221
  @PluginMethod()
200
222
  public void signOut(final PluginCall call) {
201
223
  oneTapClient.signOut()
202
- .addOnCompleteListener(task -> {
203
- var signOutResult = new JSObject();
204
- if (task.isSuccessful()) {
205
- signOutResult.put("isSuccess", true);
206
- } else {
207
- Log.e(TAG, "signOut failed", task.getException());
208
- signOutResult.put("isSuccess", false);
209
- signOutResult.put("error", task.getException().toString());
210
- }
211
- call.resolve(signOutResult);
212
- });
224
+ .addOnCompleteListener(task -> call.resolve(signOutResultTaskToJSObject(task)));
225
+ googleSignInClient.signOut()
226
+ .addOnCompleteListener(task -> call.resolve(signOutResultTaskToJSObject(task)));
227
+ }
228
+
229
+ private JSObject signOutResultTaskToJSObject(Task<Void> completedTask) {
230
+ var signOutResult = new JSObject();
231
+ if (completedTask.isSuccessful()) {
232
+ signOutResult.put("isSuccess", true);
233
+ } else {
234
+ Log.e(TAG, "signOut failed", completedTask.getException());
235
+ signOutResult.put("isSuccess", false);
236
+ signOutResult.put("error", completedTask.getException().toString());
237
+ }
238
+ return signOutResult;
239
+ }
240
+
241
+ private JSObject createSuccessResult() {
242
+ var jsObject = new JSObject();
243
+ jsObject.put("isSuccess", true);
244
+ return jsObject;
245
+ }
246
+
247
+ private Future<JSObject> googleSilentSignIn(final PluginCall call) {
248
+ googleSignInFuture = new CompletableFuture<>();
249
+ googleSignInClient.silentSignIn()
250
+ .addOnCompleteListener(task -> handleGoogleSignInResult(call, task, false,"silentSignIn"));
251
+ return googleSignInFuture;
252
+ }
253
+
254
+ private Future<JSObject> googleSignIn(final PluginCall call) {
255
+ googleSignInFuture = new CompletableFuture<>();
256
+ var intent = googleSignInClient.getSignInIntent();
257
+ startActivityForResult(call, intent, "googleSignInResultIntentCallback");
258
+ return googleSignInFuture;
259
+ }
260
+
261
+ @ActivityCallback
262
+ private void googleSignInResultIntentCallback(PluginCall call, ActivityResult result) {
263
+ if (call == null) {
264
+ return;
265
+ }
266
+ Intent data = result.getData();
267
+ var task = GoogleSignIn.getSignedInAccountFromIntent(data);
268
+ handleGoogleSignInResult(call, task, true, "GoogleSignInClient.getSignInIntent");
269
+ }
270
+
271
+ private void handleGoogleSignInResult(PluginCall call, Task<GoogleSignInAccount> completedTask,
272
+ boolean completeFutureOnlyOnSuccess, String triggerMethodName) {
273
+ try {
274
+ var account = completedTask.getResult(ApiException.class);
275
+ signInMethod = SignInMethod.GoogleSignIn;
276
+ Log.i(TAG, triggerMethodName + " result " + account.toString());
277
+ googleSignInFuture.complete(createSuccessResponse(account.getIdToken()));
278
+ } catch (ApiException e) {
279
+ int statusCode = e.getStatusCode();
280
+ var statusCodeString = GoogleSignInStatusCodes.getStatusCodeString(statusCode);
281
+ Log.i(TAG, triggerMethodName + " not successful, statusCodeString " + statusCodeString, e);
282
+ if (!completeFutureOnlyOnSuccess) {
283
+ if (statusCode == GoogleSignInStatusCodes.SIGN_IN_REQUIRED
284
+ || statusCode == GoogleSignInStatusCodes.SIGN_IN_CANCELLED) {
285
+ googleSignInFuture.complete(createErrorResponse(statusCodeString, null));
286
+ } else {
287
+ googleSignInFuture.completeExceptionally(e);
288
+ }
289
+ }
290
+ }
213
291
  }
214
292
  }