@capgo/capacitor-social-login 1.2.6 → 6.0.1

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.
@@ -11,11 +11,12 @@ Pod::Spec.new do |s|
11
11
  s.author = package['author']
12
12
  s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
13
  s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.exclude_files = '**/node_modules/**/*', '**/examples/**/*'
14
15
  s.ios.deployment_target = '14.0'
15
16
  s.dependency 'Capacitor'
16
- s.dependency 'FBSDKCoreKit', '17.1.0'
17
- s.dependency 'FBSDKLoginKit', '17.1.0'
17
+ s.dependency 'FBSDKCoreKit', '17.4.0'
18
+ s.dependency 'FBSDKLoginKit', '17.4.0'
18
19
  s.dependency 'GoogleSignIn', '~> 8.0.0'
19
- s.dependency 'Alamofire'
20
+ s.dependency 'Alamofire', '~> 5.10.2'
20
21
  s.swift_version = '5.1'
21
22
  end
package/Package.swift CHANGED
@@ -3,16 +3,16 @@ import PackageDescription
3
3
 
4
4
  let package = Package(
5
5
  name: "CapgoCapacitorSocialLogin",
6
- platforms: [.iOS(.v14)],
6
+ platforms: [.iOS(.v13)],
7
7
  products: [
8
8
  .library(
9
9
  name: "CapgoCapacitorSocialLogin",
10
10
  targets: ["SocialLoginPlugin"])
11
11
  ],
12
12
  dependencies: [
13
- .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"),
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "6.2.1"),
14
14
  // FBSDKCoreKit and FBSDKLoginKit
15
- .package(url: "https://github.com/facebook/facebook-ios-sdk.git", .upToNextMajor(from: "18.0.0")),
15
+ .package(url: "https://github.com/facebook/facebook-ios-sdk.git", .upToNextMajor(from: "17.4.0")),
16
16
  // Add Google Sign-In dependency
17
17
  .package(url: "https://github.com/google/GoogleSignIn-iOS.git", .upToNextMajor(from: "8.0.0")),
18
18
  // Alamofire
package/README.md CHANGED
@@ -197,6 +197,8 @@ const res = await SocialLogin.login({
197
197
 
198
198
  ### Android configuration
199
199
 
200
+ The implemention use the new library of Google who use Google account at Os level, make sure your device does have at least one google account connected
201
+
200
202
  Directly call the `initialize` method with the `google` provider
201
203
 
202
204
  ```typescript
@@ -221,7 +223,7 @@ Call the `initialize` method with the `google` provider
221
223
  await SocialLogin.initialize({
222
224
  google: {
223
225
  iOSClientId: 'your-client-id', // the iOS client id
224
- iOSServerClientId: 'your-server-client-id', // the iOS server client id (optional)
226
+ iOSServerClientId: 'your-server-client-id', // the iOS server client id (required in mode offline)
225
227
  },
226
228
  });
227
229
  const res = await SocialLogin.login({
@@ -232,6 +234,10 @@ const res = await SocialLogin.login({
232
234
  });
233
235
  ```
234
236
 
237
+ ### Web
238
+
239
+ Initialize method create a script tag with google lib, we canot knwo when it's ready so be sure to do it early in web otherwise it will fail
240
+
235
241
  ## API
236
242
 
237
243
  <docgen-index>
@@ -351,11 +357,11 @@ Refresh the access token
351
357
 
352
358
  #### InitializeOptions
353
359
 
354
- | Prop | Type |
355
- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |
356
- | **`facebook`** | <code>{ appId: string; clientToken: string; }</code> |
357
- | **`google`** | <code>{ iOSClientId?: string; iOSServerClientId?: string; webClientId?: string; mode?: 'online' \| 'offline'; }</code> |
358
- | **`apple`** | <code>{ clientId?: string; redirectUrl?: string; }</code> |
360
+ | Prop | Type |
361
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
362
+ | **`facebook`** | <code>{ appId: string; clientToken: string; }</code> |
363
+ | **`google`** | <code>{ iOSClientId?: string; iOSServerClientId?: string; webClientId?: string; mode?: 'online' \| 'offline'; hostedDomain?: string; }</code> |
364
+ | **`apple`** | <code>{ clientId?: string; redirectUrl?: string; }</code> |
359
365
 
360
366
 
361
367
  #### FacebookLoginResponse
@@ -420,12 +426,13 @@ Refresh the access token
420
426
 
421
427
  #### GoogleLoginOptions
422
428
 
423
- | Prop | Type | Description | Default |
424
- | ----------------------- | --------------------- | ---------------------------------------------------------------------------------------------------- | ------------------ |
425
- | **`scopes`** | <code>string[]</code> | Specifies the scopes required for accessing Google APIs The default is defined in the configuration. | |
426
- | **`nonce`** | <code>string</code> | Nonce | |
427
- | **`forceRefreshToken`** | <code>boolean</code> | Force refresh token (only for Android) | <code>false</code> |
428
- | **`disableOneTap`** | <code>boolean</code> | Disable one-tap login (web only) | <code>false</code> |
429
+ | Prop | Type | Description | Default |
430
+ | ----------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------- |
431
+ | **`scopes`** | <code>string[]</code> | Specifies the scopes required for accessing Google APIs The default is defined in the configuration. | |
432
+ | **`nonce`** | <code>string</code> | Nonce | |
433
+ | **`forceRefreshToken`** | <code>boolean</code> | Force refresh token (only for Android) | <code>false</code> |
434
+ | **`forcePrompt`** | <code>boolean</code> | Force account selection prompt (iOS) | <code>false</code> |
435
+ | **`style`** | <code>'bottom' \| 'standard'</code> | Style | <code>'standard'</code> |
429
436
 
430
437
 
431
438
  #### AppleProviderOptions
@@ -1,8 +1,8 @@
1
1
  ext {
2
2
  junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
- androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
4
- androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
5
- androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1'
6
6
  }
7
7
 
8
8
  buildscript {
@@ -11,7 +11,7 @@ buildscript {
11
11
  mavenCentral()
12
12
  }
13
13
  dependencies {
14
- classpath 'com.android.tools.build:gradle:8.7.2'
14
+ classpath 'com.android.tools.build:gradle:8.2.1'
15
15
  }
16
16
  }
17
17
 
@@ -19,10 +19,10 @@ apply plugin: 'com.android.library'
19
19
 
20
20
  android {
21
21
  namespace "ee.forgr.capacitor.social.login"
22
- compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
22
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
23
23
  defaultConfig {
24
- minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
25
- targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
26
26
  versionCode 1
27
27
  versionName "1.0"
28
28
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -37,8 +37,8 @@ android {
37
37
  abortOnError false
38
38
  }
39
39
  compileOptions {
40
- sourceCompatibility JavaVersion.VERSION_21
41
- targetCompatibility JavaVersion.VERSION_21
40
+ sourceCompatibility JavaVersion.VERSION_17
41
+ targetCompatibility JavaVersion.VERSION_17
42
42
  }
43
43
  }
44
44
 
@@ -52,7 +52,7 @@ dependencies {
52
52
  implementation fileTree(dir: 'libs', include: ['*.jar'])
53
53
  implementation project(':capacitor-android')
54
54
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
- implementation 'com.facebook.android:facebook-login:18.0.1'
55
+ implementation 'com.facebook.android:facebook-login:18.0.2'
56
56
  implementation 'com.squareup.okhttp3:okhttp:4.12.0'
57
57
  implementation 'com.auth0.android:jwtdecode:2.0.2'
58
58
  implementation "androidx.credentials:credentials:1.3.0"
@@ -1,12 +1,10 @@
1
1
  package ee.forgr.capacitor.social.login;
2
2
 
3
- import android.accounts.Account;
4
3
  import android.app.Activity;
5
4
  import android.app.PendingIntent;
6
5
  import android.content.Context;
7
6
  import android.content.Intent;
8
7
  import android.content.IntentSender;
9
- import android.text.TextUtils;
10
8
  import android.util.Log;
11
9
  import androidx.annotation.NonNull;
12
10
  import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -22,13 +20,12 @@ import androidx.credentials.exceptions.GetCredentialException;
22
20
  import androidx.credentials.exceptions.NoCredentialException;
23
21
  import com.getcapacitor.JSObject;
24
22
  import com.getcapacitor.PluginCall;
25
- import com.google.android.gms.auth.GoogleAuthException;
26
- import com.google.android.gms.auth.GoogleAuthUtil;
27
23
  import com.google.android.gms.auth.api.identity.AuthorizationRequest;
28
24
  import com.google.android.gms.auth.api.identity.AuthorizationResult;
29
25
  import com.google.android.gms.auth.api.identity.Identity;
30
26
  import com.google.android.gms.common.api.ApiException;
31
27
  import com.google.android.gms.common.api.Scope;
28
+ import com.google.android.libraries.identity.googleid.GetGoogleIdOption;
32
29
  import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption;
33
30
  import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential;
34
31
  import com.google.common.util.concurrent.ListenableFuture;
@@ -37,12 +34,11 @@ import java.io.IOException;
37
34
  import java.util.ArrayList;
38
35
  import java.util.HashSet;
39
36
  import java.util.List;
37
+ import java.util.Objects;
40
38
  import java.util.Set;
41
- import java.util.concurrent.Callable;
42
39
  import java.util.concurrent.Executor;
43
40
  import java.util.concurrent.ExecutorService;
44
41
  import java.util.concurrent.Executors;
45
- import java.util.concurrent.Future;
46
42
  import java.util.concurrent.TimeUnit;
47
43
  import okhttp3.Call;
48
44
  import okhttp3.Callback;
@@ -71,11 +67,12 @@ public class GoogleProvider implements SocialProvider {
71
67
  private CredentialManager credentialManager;
72
68
  private String clientId;
73
69
  private String[] scopes;
74
- private List<CallbackToFutureAdapter.Completer<AuthorizationResult>> futuresList = new ArrayList<>(FUTURE_LIST_LENGTH);
70
+ private final List<CallbackToFutureAdapter.Completer<AuthorizationResult>> futuresList = new ArrayList<>(FUTURE_LIST_LENGTH);
75
71
 
76
72
  private String idToken = null;
77
73
  private String accessToken = null;
78
74
  private GoogleProviderLoginType mode = GoogleProviderLoginType.ONLINE;
75
+ private String hostedDomain = null;
79
76
 
80
77
  public enum GoogleProviderLoginType {
81
78
  ONLINE,
@@ -91,10 +88,11 @@ public class GoogleProvider implements SocialProvider {
91
88
  }
92
89
  }
93
90
 
94
- public void initialize(String clientId, GoogleProviderLoginType mode) {
91
+ public void initialize(String clientId, GoogleProviderLoginType mode, String hostedDomain) {
95
92
  this.credentialManager = CredentialManager.create(activity);
96
93
  this.clientId = clientId;
97
94
  this.mode = mode;
95
+ this.hostedDomain = hostedDomain;
98
96
 
99
97
  String data = context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).getString(GOOGLE_DATA_PREFERENCE, null);
100
98
 
@@ -200,7 +198,7 @@ public class GoogleProvider implements SocialProvider {
200
198
  return;
201
199
  }
202
200
 
203
- Integer expressInInt;
201
+ int expressInInt;
204
202
  try {
205
203
  expressInInt = Integer.parseInt(expiresIn);
206
204
  } catch (Exception e) {
@@ -261,9 +259,9 @@ public class GoogleProvider implements SocialProvider {
261
259
  }
262
260
 
263
261
  public String arrayFind(String[] array, String search) {
264
- for (int i = 0; i < array.length; i++) {
265
- if (array[i].equals(search)) {
266
- return array[i];
262
+ for (String s : array) {
263
+ if (s.equals(search)) {
264
+ return s;
267
265
  }
268
266
  }
269
267
  return null;
@@ -281,66 +279,90 @@ public class GoogleProvider implements SocialProvider {
281
279
  return;
282
280
  }
283
281
 
284
- String nonce = call.getString("nonce");
282
+ String nonce = config.optString("nonce");
283
+ JSONObject options = call.getObject("options", new JSObject());
284
+ boolean bottomUi = false;
285
+ boolean forcePrompt = false;
286
+ boolean filterByAuthorizedAccounts = false;
287
+ boolean autoSelectEnabled = false;
285
288
 
286
- // Extract scopes from the config
287
- JSONArray scopesArray = config.optJSONArray("scopes");
288
-
289
- // Remove duplicates from scopes array
290
- if (scopesArray != null) {
291
- Set<String> uniqueScopes = new HashSet<>();
292
- for (int i = 0; i < scopesArray.length(); i++) {
293
- uniqueScopes.add(scopesArray.optString(i));
289
+ try {
290
+ if (options != null) {
291
+ bottomUi = options.has("style") && Objects.equals(options.getString("style"), "bottom");
292
+ filterByAuthorizedAccounts = options.has("filterByAuthorizedAccounts") && options.getBoolean("filterByAuthorizedAccounts");
293
+ autoSelectEnabled = options.has("autoSelectEnabled") && options.getBoolean("autoSelectEnabled");
294
+ forcePrompt = options.has("forcePrompt") && options.getBoolean("forcePrompt");
294
295
  }
295
- scopesArray = new JSONArray(uniqueScopes);
296
+ } catch (JSONException e) {
297
+ Log.e(LOG_TAG, "Error parsing options", e);
298
+ call.reject("Error parsing options: " + e.getMessage());
299
+ return;
296
300
  }
301
+
302
+ // Handle scopes
303
+ JSONArray scopesArray = config.optJSONArray("scopes");
304
+ Set<String> uniqueScopes = new HashSet<>();
305
+
306
+ // Add default scopes
307
+ uniqueScopes.add("https://www.googleapis.com/auth/userinfo.email");
308
+ uniqueScopes.add("https://www.googleapis.com/auth/userinfo.profile");
309
+ uniqueScopes.add("openid");
310
+
311
+ // Add custom scopes if provided
297
312
  if (scopesArray != null) {
298
313
  if (!(this.activity instanceof ModifiedMainActivityForSocialLoginPlugin)) {
299
314
  call.reject("You CANNOT use scopes without modifying the main activity. Please follow the docs!");
300
315
  return;
301
316
  }
302
-
303
- this.scopes = new String[scopesArray.length()];
304
317
  for (int i = 0; i < scopesArray.length(); i++) {
305
- this.scopes[i] = scopesArray.optString(i);
318
+ uniqueScopes.add(scopesArray.optString(i));
306
319
  }
320
+ }
321
+
322
+ this.scopes = uniqueScopes.toArray(new String[0]);
307
323
 
308
- if (arrayFind(this.scopes, "https://www.googleapis.com/auth/userinfo.email") == null) {
309
- String[] newScopes = new String[this.scopes.length + 1];
310
- System.arraycopy(this.scopes, 0, newScopes, 0, this.scopes.length);
311
- newScopes[this.scopes.length] = "https://www.googleapis.com/auth/userinfo.email";
312
- this.scopes = newScopes;
324
+ // Build credential request
325
+ GetCredentialRequest.Builder requestBuilder = new GetCredentialRequest.Builder();
326
+
327
+ if (bottomUi) {
328
+ Log.e(LOG_TAG, "use bottomUi");
329
+ GetGoogleIdOption.Builder googleIdOptionBuilder = new GetGoogleIdOption.Builder().setServerClientId(this.clientId);
330
+ // Handle bottom UI specific options
331
+ if (forcePrompt) {
332
+ filterByAuthorizedAccounts = false;
333
+ autoSelectEnabled = false;
334
+ }
335
+
336
+ if (!nonce.isEmpty()) {
337
+ googleIdOptionBuilder.setNonce(nonce);
313
338
  }
314
- if (arrayFind(this.scopes, "https://www.googleapis.com/auth/userinfo.profile") == null) {
315
- String[] newScopes = new String[this.scopes.length + 1];
316
- System.arraycopy(this.scopes, 0, newScopes, 0, this.scopes.length);
317
- newScopes[this.scopes.length] = "https://www.googleapis.com/auth/userinfo.profile";
318
- this.scopes = newScopes;
339
+ if (filterByAuthorizedAccounts) {
340
+ googleIdOptionBuilder.setFilterByAuthorizedAccounts(true);
319
341
  }
320
- if (arrayFind(this.scopes, "openid") == null) {
321
- String[] newScopes = new String[this.scopes.length + 1];
322
- System.arraycopy(this.scopes, 0, newScopes, 0, this.scopes.length);
323
- newScopes[this.scopes.length] = "openid";
324
- this.scopes = newScopes;
342
+
343
+ if (autoSelectEnabled) {
344
+ googleIdOptionBuilder.setAutoSelectEnabled(true);
325
345
  }
346
+
347
+ GetGoogleIdOption googleIdOptionFiltered = googleIdOptionBuilder.build();
348
+ requestBuilder.addCredentialOption(googleIdOptionFiltered);
326
349
  } else {
327
- // Default scopes if not provided
328
- this.scopes = new String[] {
329
- "https://www.googleapis.com/auth/userinfo.profile",
330
- "https://www.googleapis.com/auth/userinfo.email",
331
- "openid"
332
- };
333
- }
350
+ // For standard UI, we don't use these options
351
+ GetSignInWithGoogleOption.Builder googleIdOptionBuilder = new GetSignInWithGoogleOption.Builder(this.clientId);
334
352
 
335
- GetSignInWithGoogleOption.Builder googleIdOptionBuilder = new GetSignInWithGoogleOption.Builder(this.clientId);
353
+ if (!nonce.isEmpty()) {
354
+ googleIdOptionBuilder.setNonce(nonce);
355
+ }
356
+ if (this.hostedDomain != null && !this.hostedDomain.isEmpty()) {
357
+ googleIdOptionBuilder.setHostedDomainFilter(this.hostedDomain);
358
+ }
336
359
 
337
- if (nonce != null && !nonce.isEmpty()) {
338
- googleIdOptionBuilder.setNonce(nonce);
360
+ requestBuilder.addCredentialOption(googleIdOptionBuilder.build());
339
361
  }
340
362
 
341
- GetSignInWithGoogleOption googleIdOptionFiltered = googleIdOptionBuilder.build();
342
- GetCredentialRequest filteredRequest = new GetCredentialRequest.Builder().addCredentialOption(googleIdOptionFiltered).build();
363
+ GetCredentialRequest filteredRequest = requestBuilder.build();
343
364
 
365
+ // Execute credential request
344
366
  Executor executor = Executors.newSingleThreadExecutor();
345
367
  credentialManager.getCredentialAsync(
346
368
  context,
@@ -354,8 +376,8 @@ public class GoogleProvider implements SocialProvider {
354
376
  }
355
377
 
356
378
  @Override
357
- public void onError(GetCredentialException e) {
358
- handleSignInError(e, call);
379
+ public void onError(@NonNull GetCredentialException e) {
380
+ handleSignInError(e, call, config);
359
381
  }
360
382
  }
361
383
  );
@@ -394,8 +416,8 @@ public class GoogleProvider implements SocialProvider {
394
416
 
395
417
  ListenableFuture<AuthorizationResult> future = CallbackToFutureAdapter.getFuture(completer -> {
396
418
  List<Scope> scopes = new ArrayList<>(this.scopes.length);
397
- for (int i = 0; i < this.scopes.length; i++) {
398
- scopes.add(new Scope(this.scopes[i]));
419
+ for (String scope : this.scopes) {
420
+ scopes.add(new Scope(scope));
399
421
  }
400
422
  AuthorizationRequest.Builder authorizationRequestBuilder = AuthorizationRequest.builder().setRequestedScopes(scopes);
401
423
  // .requestOfflineAccess(this.clientId)
@@ -489,8 +511,12 @@ public class GoogleProvider implements SocialProvider {
489
511
  JSObject resultObj = new JSObject();
490
512
 
491
513
  JSONObject options = call.getObject("options", new JSObject());
492
- Boolean forceRefreshToken =
493
- options != null && options.has("forceRefreshToken") && options.getBoolean("forceRefreshToken");
514
+ Boolean forceRefreshToken = false;
515
+ try {
516
+ forceRefreshToken = options != null && options.has("forceRefreshToken") && options.getBoolean("forceRefreshToken");
517
+ } catch (JSONException e) {
518
+ Log.e(LOG_TAG, "Error parsing forceRefreshToken option", e);
519
+ }
494
520
 
495
521
  GoogleIdTokenCredential googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.getData());
496
522
  ListenableFuture<AuthorizationResult> future = getAuthorizationResult(forceRefreshToken);
@@ -577,10 +603,29 @@ public class GoogleProvider implements SocialProvider {
577
603
  }
578
604
  }
579
605
 
580
- private void handleSignInError(GetCredentialException e, PluginCall call) {
606
+ private void handleSignInError(GetCredentialException e, PluginCall call, JSONObject config) {
581
607
  Log.e(LOG_TAG, "Google Sign-In failed", e);
582
- if (e instanceof NoCredentialException) {
583
- call.reject("No Google accounts available. Please add a Google account to your device and try again.");
608
+ boolean isBottomUi = false;
609
+ JSONObject options = call.getObject("options", new JSObject());
610
+ if (options.has("style")) {
611
+ try {
612
+ isBottomUi = options.getString("style").equals("bottom");
613
+ } catch (JSONException ex) {
614
+ // do nothing
615
+ }
616
+ }
617
+ if (e instanceof NoCredentialException && isBottomUi) {
618
+ Log.e(LOG_TAG, "No Google accounts available or miss configuration using bottomUi, auto switch to standard UI");
619
+ // During the get credential flow, this is returned when no viable credential is available for the the user. This can be caused by various scenarios such as that the user doesn't have any credential or the user doesn't grant consent to using any available credential. Upon this exception, your app should navigate to use the regular app sign-up or sign-in screen.
620
+ // https://developer.android.com/reference/androidx/credentials/exceptions/NoCredentialException
621
+ try {
622
+ options.put("style", "standard");
623
+ call.getData().put("options", options);
624
+ } catch (JSONException ex) {
625
+ call.reject("Google Sign-In failed: " + ex.getMessage());
626
+ return;
627
+ }
628
+ login(call, config);
584
629
  } else {
585
630
  call.reject("Google Sign-In failed: " + e.getMessage());
586
631
  }
@@ -650,7 +695,7 @@ public class GoogleProvider implements SocialProvider {
650
695
  }
651
696
 
652
697
  @Override
653
- public void onError(ClearCredentialException e) {
698
+ public void onError(@NonNull ClearCredentialException e) {
654
699
  Log.e(LOG_TAG, "Failed to clear credential state", e);
655
700
  handler.onError(e);
656
701
  }
@@ -59,6 +59,7 @@ public class SocialLoginPlugin extends Plugin {
59
59
  call.reject("google.clientId is null or empty");
60
60
  return;
61
61
  }
62
+ String hostedDomain = google.getString("hostedDomain");
62
63
  String modestr = google.getString("mode", "online");
63
64
  GoogleProvider.GoogleProviderLoginType mode = null;
64
65
  switch (modestr) {
@@ -72,7 +73,7 @@ public class SocialLoginPlugin extends Plugin {
72
73
  call.reject("google.mode != (online || offline)");
73
74
  return;
74
75
  }
75
- googleProvider.initialize(googleClientId, mode);
76
+ googleProvider.initialize(googleClientId, mode, hostedDomain);
76
77
  this.socialProviderHashMap.put("google", googleProvider);
77
78
  }
78
79
 
package/dist/docs.json CHANGED
@@ -168,7 +168,7 @@
168
168
  "tags": [],
169
169
  "docs": "",
170
170
  "complexTypes": [],
171
- "type": "{ iOSClientId?: string | undefined; iOSServerClientId?: string | undefined; webClientId?: string | undefined; mode?: 'online' | 'offline' | undefined; } | undefined"
171
+ "type": "{ iOSClientId?: string | undefined; iOSServerClientId?: string | undefined; webClientId?: string | undefined; mode?: 'online' | 'offline' | undefined; hostedDomain?: string | undefined; } | undefined"
172
172
  },
173
173
  {
174
174
  "name": "apple",
@@ -478,10 +478,10 @@
478
478
  "type": "boolean | undefined"
479
479
  },
480
480
  {
481
- "name": "disableOneTap",
481
+ "name": "forcePrompt",
482
482
  "tags": [
483
483
  {
484
- "text": "disable one-tap login",
484
+ "text": "forces the account selection prompt to appear on iOS",
485
485
  "name": "description"
486
486
  },
487
487
  {
@@ -489,9 +489,25 @@
489
489
  "name": "default"
490
490
  }
491
491
  ],
492
- "docs": "Disable one-tap login (web only)",
492
+ "docs": "Force account selection prompt (iOS)",
493
493
  "complexTypes": [],
494
494
  "type": "boolean | undefined"
495
+ },
496
+ {
497
+ "name": "style",
498
+ "tags": [
499
+ {
500
+ "text": "style",
501
+ "name": "description"
502
+ },
503
+ {
504
+ "text": "'standard'",
505
+ "name": "default"
506
+ }
507
+ ],
508
+ "docs": "Style",
509
+ "complexTypes": [],
510
+ "type": "'bottom' | 'standard' | undefined"
495
511
  }
496
512
  ]
497
513
  },
@@ -0,0 +1,17 @@
1
+ import { BaseSocialLogin } from './base';
2
+ import type { AppleProviderOptions, AuthorizationCode, LoginResult } from './definitions';
3
+ export declare class AppleSocialLogin extends BaseSocialLogin {
4
+ private clientId;
5
+ private redirectUrl;
6
+ private scriptLoaded;
7
+ private scriptUrl;
8
+ initialize(clientId: string | null, redirectUrl: string | null | undefined): Promise<void>;
9
+ login(options: AppleProviderOptions): Promise<LoginResult>;
10
+ logout(): Promise<void>;
11
+ isLoggedIn(): Promise<{
12
+ isLoggedIn: boolean;
13
+ }>;
14
+ getAuthorizationCode(): Promise<AuthorizationCode>;
15
+ refresh(): Promise<void>;
16
+ private loadAppleScript;
17
+ }
@@ -0,0 +1,83 @@
1
+ import { BaseSocialLogin } from './base';
2
+ export class AppleSocialLogin extends BaseSocialLogin {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.clientId = null;
6
+ this.redirectUrl = null;
7
+ this.scriptLoaded = false;
8
+ this.scriptUrl = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js';
9
+ }
10
+ async initialize(clientId, redirectUrl) {
11
+ this.clientId = clientId;
12
+ this.redirectUrl = redirectUrl || null;
13
+ if (clientId) {
14
+ await this.loadAppleScript();
15
+ }
16
+ }
17
+ async login(options) {
18
+ if (!this.clientId) {
19
+ throw new Error('Apple Client ID not set. Call initialize() first.');
20
+ }
21
+ if (!this.scriptLoaded) {
22
+ throw new Error('Apple Sign-In script not loaded.');
23
+ }
24
+ return new Promise((resolve, reject) => {
25
+ var _a;
26
+ AppleID.auth.init({
27
+ clientId: this.clientId,
28
+ scope: ((_a = options.scopes) === null || _a === void 0 ? void 0 : _a.join(' ')) || 'name email',
29
+ redirectURI: this.redirectUrl || window.location.href,
30
+ state: options.state,
31
+ nonce: options.nonce,
32
+ usePopup: true,
33
+ });
34
+ AppleID.auth
35
+ .signIn()
36
+ .then((res) => {
37
+ var _a, _b, _c, _d, _e;
38
+ const result = {
39
+ profile: {
40
+ user: res.user || '',
41
+ email: ((_a = res.user) === null || _a === void 0 ? void 0 : _a.email) || null,
42
+ givenName: ((_c = (_b = res.user) === null || _b === void 0 ? void 0 : _b.name) === null || _c === void 0 ? void 0 : _c.firstName) || null,
43
+ familyName: ((_e = (_d = res.user) === null || _d === void 0 ? void 0 : _d.name) === null || _e === void 0 ? void 0 : _e.lastName) || null,
44
+ },
45
+ accessToken: {
46
+ token: res.authorization.id_token || '',
47
+ },
48
+ idToken: res.authorization.code || null,
49
+ };
50
+ resolve({ provider: 'apple', result });
51
+ })
52
+ .catch((error) => {
53
+ reject(error);
54
+ });
55
+ });
56
+ }
57
+ async logout() {
58
+ // Apple doesn't provide a logout method for web
59
+ console.log('Apple logout: Session should be managed on the client side');
60
+ }
61
+ async isLoggedIn() {
62
+ // Apple doesn't provide a method to check login status on web
63
+ console.log('Apple login status should be managed on the client side');
64
+ return { isLoggedIn: false };
65
+ }
66
+ async getAuthorizationCode() {
67
+ // Apple authorization code should be obtained during login
68
+ console.log('Apple authorization code should be stored during login');
69
+ throw new Error('Apple authorization code not available');
70
+ }
71
+ async refresh() {
72
+ // Apple doesn't provide a refresh method for web
73
+ console.log('Apple refresh not available on web');
74
+ }
75
+ async loadAppleScript() {
76
+ if (this.scriptLoaded)
77
+ return;
78
+ return this.loadScript(this.scriptUrl).then(() => {
79
+ this.scriptLoaded = true;
80
+ });
81
+ }
82
+ }
83
+ //# sourceMappingURL=apple-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apple-provider.js","sourceRoot":"","sources":["../../src/apple-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseSocialLogin } from './base';\nimport type { AppleProviderOptions, AppleProviderResponse, AuthorizationCode, LoginResult } from './definitions';\n\ndeclare const AppleID: any;\n\nexport class AppleSocialLogin extends BaseSocialLogin {\n private clientId: string | null = null;\n private redirectUrl: string | null = null;\n private scriptLoaded = false;\n private scriptUrl = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js';\n\n async initialize(clientId: string | null, redirectUrl: string | null | undefined): Promise<void> {\n this.clientId = clientId;\n this.redirectUrl = redirectUrl || null;\n\n if (clientId) {\n await this.loadAppleScript();\n }\n }\n\n async login(options: AppleProviderOptions): Promise<LoginResult> {\n if (!this.clientId) {\n throw new Error('Apple Client ID not set. Call initialize() first.');\n }\n\n if (!this.scriptLoaded) {\n throw new Error('Apple Sign-In script not loaded.');\n }\n\n return new Promise((resolve, reject) => {\n AppleID.auth.init({\n clientId: this.clientId!,\n scope: options.scopes?.join(' ') || 'name email',\n redirectURI: this.redirectUrl || window.location.href,\n state: options.state,\n nonce: options.nonce,\n usePopup: true,\n });\n\n AppleID.auth\n .signIn()\n .then((res: any) => {\n const result: AppleProviderResponse = {\n profile: {\n user: res.user || '',\n email: res.user?.email || null,\n givenName: res.user?.name?.firstName || null,\n familyName: res.user?.name?.lastName || null,\n },\n accessToken: {\n token: res.authorization.id_token || '',\n },\n idToken: res.authorization.code || null,\n };\n resolve({ provider: 'apple', result });\n })\n .catch((error: any) => {\n reject(error);\n });\n });\n }\n\n async logout(): Promise<void> {\n // Apple doesn't provide a logout method for web\n console.log('Apple logout: Session should be managed on the client side');\n }\n\n async isLoggedIn(): Promise<{ isLoggedIn: boolean }> {\n // Apple doesn't provide a method to check login status on web\n console.log('Apple login status should be managed on the client side');\n return { isLoggedIn: false };\n }\n\n async getAuthorizationCode(): Promise<AuthorizationCode> {\n // Apple authorization code should be obtained during login\n console.log('Apple authorization code should be stored during login');\n throw new Error('Apple authorization code not available');\n }\n\n async refresh(): Promise<void> {\n // Apple doesn't provide a refresh method for web\n console.log('Apple refresh not available on web');\n }\n\n private async loadAppleScript(): Promise<void> {\n if (this.scriptLoaded) return;\n\n return this.loadScript(this.scriptUrl).then(() => {\n this.scriptLoaded = true;\n });\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ export declare class BaseSocialLogin extends WebPlugin {
3
+ protected static readonly OAUTH_STATE_KEY = "social_login_oauth_pending";
4
+ constructor();
5
+ protected parseJwt(token: string): any;
6
+ protected loadScript(src: string): Promise<void>;
7
+ }