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.
- package/CapacitorNativeGoogleOneTapSignin.podspec +17 -15
- package/README.md +104 -21
- package/android/build.gradle +3 -2
- package/android/src/main/java/com/proventask/capacitor/GoogleOneTapAuth/GoogleOneTapAuth.java +147 -69
- package/dist/docs.json +345 -0
- package/dist/esm/GoogleOneTapAuth.d.ts +13 -0
- package/dist/esm/GoogleOneTapAuth.js +87 -0
- package/dist/esm/GoogleOneTapAuth.js.map +1 -0
- package/dist/esm/definitions.d.ts +76 -53
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/helpers.d.ts +2 -0
- package/dist/esm/helpers.js +15 -0
- package/dist/esm/helpers.js.map +1 -0
- package/dist/esm/index.d.ts +1 -2
- package/dist/esm/index.js +1 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +10 -14
- package/dist/esm/web.js +59 -66
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +256 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +257 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Plugin.m +3 -1
- package/ios/Plugin/Plugin.swift +74 -133
- package/package.json +60 -34
- package/android/.gradle/7.3.3/checksums/checksums.lock +0 -0
- package/android/.gradle/7.3.3/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/7.3.3/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/7.3.3/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/7.3.3/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/7.3.3/fileChanges/last-build.bin +0 -0
- package/android/.gradle/7.3.3/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/7.3.3/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/7.3.3/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/7.3.3/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/.idea/workspace.xml +0 -89
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/android/gradle.properties +0 -16
- package/android/gradlew +0 -160
- package/android/gradlew.bat +0 -90
- package/android/proguard-rules.pro +0 -21
- package/android/settings.gradle +0 -2
- package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +0 -26
- package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +0 -17
- package/ios/Plugin.xcodeproj/project.pbxproj +0 -565
- package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
- package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/PluginTests/Info.plist +0 -22
- package/ios/PluginTests/PluginTests.swift +0 -35
- package/ios/Podfile +0 -23
- package/ios/Podfile.lock +0 -47
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
require 'json'
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
See the `demo` folder.
|
|
20
|
+
#### 1. Install package
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
```sh
|
|
23
|
+
npm i --save capacitor-native-google-one-tap-signin
|
|
24
|
+
```
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
#### 2. Update capacitor dependencies
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
```sh
|
|
29
|
+
npx cap update
|
|
30
|
+
```
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
+
Parameters that are independent of a sign-in method as the clientId must be passed to `initialize`.
|
|
48
130
|
|
|
49
|
-
|
|
131
|
+
## Contributions
|
|
132
|
+
Welcome
|
|
50
133
|
|
|
51
134
|
## License
|
|
52
135
|
|
package/android/build.gradle
CHANGED
|
@@ -4,7 +4,7 @@ buildscript {
|
|
|
4
4
|
google()
|
|
5
5
|
}
|
|
6
6
|
dependencies {
|
|
7
|
-
classpath 'com.android.tools.build:gradle:7.
|
|
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
|
|
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
|
|
package/android/src/main/java/com/proventask/capacitor/GoogleOneTapAuth/GoogleOneTapAuth.java
CHANGED
|
@@ -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>
|
|
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
|
-
|
|
54
|
+
registerSignInResultHandler();
|
|
55
|
+
}
|
|
41
56
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
@PluginMethod()
|
|
58
|
+
public void initialize(PluginCall call) {
|
|
59
|
+
initializeOptionsFromCall(call);
|
|
45
60
|
oneTapClient = Identity.getSignInClient(this.getActivity());
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
85
|
+
signInMethod = SignInMethod.OneTap;
|
|
86
|
+
oneTapSignInFuture.complete(createSuccessResponse(credential.getGoogleIdToken()));
|
|
61
87
|
} catch (ApiException e) {
|
|
62
|
-
|
|
88
|
+
oneTapSignInFuture.complete(createErrorResponse(null, e.toString()));
|
|
63
89
|
}
|
|
64
90
|
} else {
|
|
65
91
|
var resultCodeString = ActivityResult.resultCodeToString(resultCode);
|
|
66
|
-
|
|
92
|
+
oneTapSignInFuture.complete(createErrorResponse(null, resultCodeString));
|
|
67
93
|
}
|
|
68
94
|
});
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
@PluginMethod()
|
|
72
|
-
public void
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
115
|
+
public void triggerGoogleSignIn(PluginCall call) {
|
|
91
116
|
try {
|
|
92
|
-
var signInResult =
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
oneTapSignInFuture.complete(createErrorResponse(null, errorMessage));
|
|
150
162
|
}
|
|
151
163
|
});
|
|
152
|
-
return
|
|
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("
|
|
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
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
}
|