@capgo/capacitor-social-login 7.9.5 → 7.11.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/README.md CHANGED
@@ -509,7 +509,7 @@ Execute provider-specific calls
509
509
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
510
510
  | **`facebook`** | <code>{ appId: string; clientToken?: string; locale?: string; }</code> |
511
511
  | **`google`** | <code>{ iOSClientId?: string; iOSServerClientId?: string; webClientId?: string; mode?: 'online' \| 'offline'; hostedDomain?: string; redirectUrl?: string; }</code> |
512
- | **`apple`** | <code>{ clientId?: string; redirectUrl?: string; }</code> |
512
+ | **`apple`** | <code>{ clientId?: string; redirectUrl?: string; useProperTokenExchange?: boolean; useBroadcastChannel?: boolean; }</code> |
513
513
 
514
514
 
515
515
  #### FacebookLoginResponse
@@ -556,11 +556,12 @@ Execute provider-specific calls
556
556
 
557
557
  #### AppleProviderResponse
558
558
 
559
- | Prop | Type |
560
- | ----------------- | ------------------------------------------------------------------------------------------------------------ |
561
- | **`accessToken`** | <code><a href="#accesstoken">AccessToken</a> \| null</code> |
562
- | **`idToken`** | <code>string \| null</code> |
563
- | **`profile`** | <code>{ user: string; email: string \| null; givenName: string \| null; familyName: string \| null; }</code> |
559
+ | Prop | Type | Description |
560
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
561
+ | **`accessToken`** | <code><a href="#accesstoken">AccessToken</a> \| null</code> | Access token from Apple |
562
+ | **`idToken`** | <code>string \| null</code> | Identity token (JWT) from Apple |
563
+ | **`profile`** | <code>{ user: string; email: string \| null; givenName: string \| null; familyName: string \| null; }</code> | User profile information |
564
+ | **`authorizationCode`** | <code>string</code> | Authorization code for proper token exchange (when useProperTokenExchange is enabled) |
564
565
 
565
566
 
566
567
  #### FacebookLoginOptions
@@ -587,11 +588,12 @@ Execute provider-specific calls
587
588
 
588
589
  #### AppleProviderOptions
589
590
 
590
- | Prop | Type | Description |
591
- | ------------ | --------------------- | ----------- |
592
- | **`scopes`** | <code>string[]</code> | Scopes |
593
- | **`nonce`** | <code>string</code> | Nonce |
594
- | **`state`** | <code>string</code> | State |
591
+ | Prop | Type | Description | Default |
592
+ | ------------------------- | --------------------- | --------------------------------------------- | ------------------ |
593
+ | **`scopes`** | <code>string[]</code> | Scopes | |
594
+ | **`nonce`** | <code>string</code> | Nonce | |
595
+ | **`state`** | <code>string</code> | State | |
596
+ | **`useBroadcastChannel`** | <code>boolean</code> | Use Broadcast Channel for authentication flow | <code>false</code> |
595
597
 
596
598
 
597
599
  #### isLoggedInOptions
@@ -17,7 +17,10 @@ import android.view.View;
17
17
  import android.view.ViewGroup;
18
18
  import android.view.Window;
19
19
  import android.view.WindowManager;
20
+ import android.webkit.ConsoleMessage;
21
+ import android.webkit.WebChromeClient;
20
22
  import android.webkit.WebResourceRequest;
23
+ import android.webkit.WebSettings;
21
24
  import android.webkit.WebView;
22
25
  import android.webkit.WebViewClient;
23
26
  import android.widget.ImageButton;
@@ -68,6 +71,8 @@ public class AppleProvider implements SocialProvider {
68
71
  private final String redirectUrl;
69
72
  private final Activity activity;
70
73
  private final Context context;
74
+ private final boolean useProperTokenExchange;
75
+ private final boolean useBroadcastChannel;
71
76
 
72
77
  private CustomTabsClient customTabsClient;
73
78
  private CustomTabsSession currentSession;
@@ -82,11 +87,20 @@ public class AppleProvider implements SocialProvider {
82
87
  public void onServiceDisconnected(ComponentName name) {}
83
88
  };
84
89
 
85
- public AppleProvider(String redirectUrl, String clientId, Activity activity, Context context) {
90
+ public AppleProvider(
91
+ String redirectUrl,
92
+ String clientId,
93
+ Activity activity,
94
+ Context context,
95
+ boolean useProperTokenExchange,
96
+ boolean useBroadcastChannel
97
+ ) {
86
98
  this.redirectUrl = redirectUrl;
87
99
  this.clientId = clientId;
88
100
  this.activity = activity;
89
101
  this.context = context;
102
+ this.useProperTokenExchange = useProperTokenExchange;
103
+ this.useBroadcastChannel = useBroadcastChannel;
90
104
  }
91
105
 
92
106
  public void initialize() {
@@ -136,6 +150,72 @@ public class AppleProvider implements SocialProvider {
136
150
  call.reject("Last call is not null");
137
151
  }
138
152
 
153
+ // Check if Broadcast Channel is enabled
154
+ boolean useBroadcastChannel = config.optBoolean("useBroadcastChannel", this.useBroadcastChannel);
155
+
156
+ if (useBroadcastChannel) {
157
+ // Use Broadcast Channel approach - simplified flow
158
+ loginWithBroadcastChannel(call, config);
159
+ } else {
160
+ // Use traditional URL redirect approach
161
+ loginWithRedirect(call, config);
162
+ }
163
+ }
164
+
165
+ private void loginWithBroadcastChannel(PluginCall call, JSONObject config) {
166
+ String state = UUID.randomUUID().toString();
167
+
168
+ // Extract scopes from config
169
+ String scopes = DEFAULT_SCOPE;
170
+ if (config.has("scopes")) {
171
+ try {
172
+ JSONArray scopesArray = config.getJSONArray("scopes");
173
+ if (scopesArray.length() > 0) {
174
+ scopes = String.join("%20", toStringArray(scopesArray));
175
+ }
176
+ } catch (JSONException e) {
177
+ Log.e(SocialLoginPlugin.LOG_TAG, "Error parsing scopes", e);
178
+ }
179
+ }
180
+
181
+ String nonce = null;
182
+ if (config.has("nonce")) {
183
+ try {
184
+ nonce = config.getString("nonce");
185
+ } catch (JSONException e) {
186
+ Log.e(SocialLoginPlugin.LOG_TAG, "Error parsing nonce", e);
187
+ }
188
+ }
189
+
190
+ // For Broadcast Channel, we use a special redirect URI that handles the channel communication
191
+ String broadcastRedirectUri = "https://capacitor-social-login.firebaseapp.com/__/auth/handler";
192
+
193
+ this.appleAuthURLFull =
194
+ AUTHURL +
195
+ "?client_id=" +
196
+ this.clientId +
197
+ "&redirect_uri=" +
198
+ broadcastRedirectUri +
199
+ "&response_type=code&scope=" +
200
+ scopes +
201
+ "&response_mode=form_post&state=" +
202
+ state;
203
+
204
+ if (nonce != null) {
205
+ this.appleAuthURLFull += "&nonce=" + nonce;
206
+ }
207
+
208
+ if (context == null || activity == null) {
209
+ call.reject("Context or Activity is null");
210
+ return;
211
+ }
212
+
213
+ this.lastcall = call;
214
+ call.setKeepAlive(true);
215
+ activity.runOnUiThread(() -> setupBroadcastChannelWebview(context, activity, call, appleAuthURLFull));
216
+ }
217
+
218
+ private void loginWithRedirect(PluginCall call, JSONObject config) {
139
219
  String state = UUID.randomUUID().toString();
140
220
 
141
221
  // Extract scopes from config
@@ -235,6 +315,7 @@ public class AppleProvider implements SocialProvider {
235
315
  if ("true".equals(success)) {
236
316
  String accessToken = uri.getQueryParameter("access_token");
237
317
  if (accessToken != null) {
318
+ // We have proper tokens from the backend
238
319
  String refreshToken = uri.getQueryParameter("refresh_token");
239
320
  String idToken = uri.getQueryParameter("id_token");
240
321
  try {
@@ -244,6 +325,11 @@ public class AppleProvider implements SocialProvider {
244
325
  result.put("profile", createProfileObject(idToken));
245
326
  result.put("idToken", idToken);
246
327
 
328
+ // For proper token exchange mode, don't include authorization code
329
+ if (!useProperTokenExchange) {
330
+ result.put("authorizationCode", (String) null);
331
+ }
332
+
247
333
  JSObject response = new JSObject();
248
334
  response.put("provider", "apple");
249
335
  response.put("result", result);
@@ -254,9 +340,18 @@ public class AppleProvider implements SocialProvider {
254
340
  this.lastcall.reject("Cannot persist state", e);
255
341
  }
256
342
  } else {
343
+ // We only have authorization code, need to exchange it
257
344
  String appleAuthCode = uri.getQueryParameter("code");
258
345
  String appleClientSecret = uri.getQueryParameter("client_secret");
259
- requestForAccessToken(appleAuthCode, appleClientSecret);
346
+
347
+ if (useProperTokenExchange) {
348
+ // In proper token exchange mode, we should have received proper tokens
349
+ // from the backend. If we only got an auth code, reject the call.
350
+ this.lastcall.reject("Expected proper tokens from backend but received authorization code only");
351
+ } else {
352
+ // Legacy mode: exchange the authorization code for tokens
353
+ requestForAccessToken(appleAuthCode, appleClientSecret);
354
+ }
260
355
  }
261
356
  } else {
262
357
  this.lastcall.reject("We couldn't get the Auth Code");
@@ -298,9 +393,21 @@ public class AppleProvider implements SocialProvider {
298
393
  String idToken = jsonObject.getString("id_token");
299
394
 
300
395
  persistState(idToken, refreshToken, accessToken);
301
- AppleProvider.this.lastcall.resolve(
302
- new JSObject().put("provider", "apple").put("result", new JSObject().put("identityToken", idToken))
303
- );
396
+
397
+ // Create proper response with all tokens
398
+ JSObject result = new JSObject();
399
+ result.put("accessToken", createAccessTokenObject(accessToken));
400
+ result.put("profile", createProfileObject(idToken));
401
+ result.put("idToken", idToken);
402
+
403
+ // For legacy mode, we don't include authorization code in the response
404
+ // since we've already exchanged it for proper tokens
405
+
406
+ JSObject appleResponse = new JSObject();
407
+ appleResponse.put("provider", "apple");
408
+ appleResponse.put("result", result);
409
+
410
+ AppleProvider.this.lastcall.resolve(appleResponse);
304
411
  AppleProvider.this.lastcall = null;
305
412
  } catch (Exception e) {
306
413
  AppleProvider.this.lastcall.reject("Cannot get access_token", e);
@@ -348,6 +455,220 @@ public class AppleProvider implements SocialProvider {
348
455
  builder.build().launchUrl(context, Uri.parse(url));
349
456
  }
350
457
 
458
+ @SuppressLint("SetJavaScriptEnabled")
459
+ private void setupBroadcastChannelWebview(Context context, Activity activity, PluginCall call, String url) {
460
+ // Create a custom WebView with Broadcast Channel support
461
+ Dialog dialog = new Dialog(activity);
462
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
463
+ dialog.setCancelable(true);
464
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
465
+
466
+ WebView webView = new WebView(context);
467
+ webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
468
+
469
+ // Enable JavaScript
470
+ WebSettings webSettings = webView.getSettings();
471
+ webSettings.setJavaScriptEnabled(true);
472
+ webSettings.setDomStorageEnabled(true);
473
+ webSettings.setSupportMultipleWindows(false);
474
+
475
+ // Set up Broadcast Channel communication
476
+ webView.addJavascriptInterface(new BroadcastChannelInterface(call), "AndroidBridge");
477
+
478
+ // Set up WebViewClient to handle redirects
479
+ webView.setWebViewClient(
480
+ new WebViewClient() {
481
+ @Override
482
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
483
+ String url = request.getUrl().toString();
484
+
485
+ // Check if this is the Broadcast Channel redirect
486
+ if (url.contains("capacitor-social-login.firebaseapp.com")) {
487
+ // Extract authorization code from URL parameters
488
+ Uri uri = Uri.parse(url);
489
+ String success = uri.getQueryParameter("success");
490
+
491
+ if ("true".equals(success)) {
492
+ String accessToken = uri.getQueryParameter("access_token");
493
+ if (accessToken != null) {
494
+ // We have proper tokens from the backend
495
+ String refreshToken = uri.getQueryParameter("refresh_token");
496
+ String idToken = uri.getQueryParameter("id_token");
497
+ try {
498
+ persistState(idToken, refreshToken, accessToken);
499
+ JSObject result = new JSObject();
500
+ result.put("accessToken", createAccessTokenObject(accessToken));
501
+ result.put("profile", createProfileObject(idToken));
502
+ result.put("idToken", idToken);
503
+
504
+ JSObject response = new JSObject();
505
+ response.put("provider", "apple");
506
+ response.put("result", result);
507
+
508
+ lastcall.resolve(response);
509
+ } catch (JSONException e) {
510
+ Log.e(SocialLoginPlugin.LOG_TAG, "Cannot persist state", e);
511
+ lastcall.reject("Cannot persist state", e);
512
+ }
513
+ } else {
514
+ // We only have authorization code, need to handle it
515
+ String appleAuthCode = uri.getQueryParameter("code");
516
+ String appleClientSecret = uri.getQueryParameter("client_secret");
517
+
518
+ if (useProperTokenExchange) {
519
+ // For Broadcast Channel, we can handle the token exchange directly
520
+ // or pass the authorization code back to the client
521
+ JSObject result = new JSObject();
522
+ result.put("authorizationCode", appleAuthCode);
523
+ result.put("idToken", ""); // Will be filled by client-side token exchange
524
+
525
+ JSObject response = new JSObject();
526
+ response.put("provider", "apple");
527
+ response.put("result", result);
528
+
529
+ lastcall.resolve(response);
530
+ } else {
531
+ // Legacy mode: use authorization code as access token
532
+ JSObject result = new JSObject();
533
+ result.put("accessToken", createAccessTokenObject(appleAuthCode));
534
+ result.put("profile", createProfileObject(""));
535
+ result.put("idToken", "");
536
+
537
+ JSObject response = new JSObject();
538
+ response.put("provider", "apple");
539
+ response.put("result", result);
540
+
541
+ lastcall.resolve(response);
542
+ }
543
+ }
544
+ } else {
545
+ lastcall.reject("Authentication failed");
546
+ }
547
+
548
+ dialog.dismiss();
549
+ lastcall = null;
550
+ return true;
551
+ }
552
+
553
+ return super.shouldOverrideUrlLoading(view, request);
554
+ }
555
+ }
556
+ );
557
+
558
+ // Inject Broadcast Channel polyfill and setup
559
+ webView.setWebChromeClient(
560
+ new WebChromeClient() {
561
+ @Override
562
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
563
+ Log.d("WebView", consoleMessage.message());
564
+ return super.onConsoleMessage(consoleMessage);
565
+ }
566
+ }
567
+ );
568
+
569
+ dialog.setContentView(webView);
570
+
571
+ // Set dialog to fullscreen
572
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
573
+ lp.copyFrom(dialog.getWindow().getAttributes());
574
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
575
+ lp.height = WindowManager.LayoutParams.MATCH_PARENT;
576
+ dialog.getWindow().setAttributes(lp);
577
+
578
+ // Load the Apple authentication URL
579
+ webView.loadUrl(url);
580
+
581
+ // Inject Broadcast Channel setup after page loads
582
+ webView.setWebViewClient(
583
+ new WebViewClient() {
584
+ @Override
585
+ public void onPageFinished(WebView view, String url) {
586
+ super.onPageFinished(view, url);
587
+
588
+ // Inject Broadcast Channel setup
589
+ String broadcastChannelScript =
590
+ "javascript:" +
591
+ "if (!window.BroadcastChannel) {" +
592
+ " window.BroadcastChannel = function(name) {" +
593
+ " this.name = name;" +
594
+ " this.onmessage = null;" +
595
+ " this.postMessage = function(data) {" +
596
+ " if (window.AndroidBridge) {" +
597
+ " window.AndroidBridge.postMessage(JSON.stringify({channel: this.name, data: data}));" +
598
+ " }" +
599
+ " };" +
600
+ " window.addEventListener('message', (event) => {" +
601
+ " if (this.onmessage) {" +
602
+ " this.onmessage({data: event.data});" +
603
+ " }" +
604
+ " });" +
605
+ " };" +
606
+ "}" +
607
+ "console.log('Broadcast Channel polyfill loaded');";
608
+
609
+ view.evaluateJavascript(broadcastChannelScript, null);
610
+ }
611
+ }
612
+ );
613
+
614
+ dialog.show();
615
+ }
616
+
617
+ // JavaScript interface for Broadcast Channel communication
618
+ private class BroadcastChannelInterface {
619
+
620
+ private final PluginCall call;
621
+
622
+ BroadcastChannelInterface(PluginCall call) {
623
+ this.call = call;
624
+ }
625
+
626
+ @android.webkit.JavascriptInterface
627
+ public void postMessage(String message) {
628
+ try {
629
+ JSONObject data = new JSONObject(message);
630
+ String channel = data.getString("channel");
631
+ JSONObject messageData = data.getJSONObject("data");
632
+
633
+ Log.d("BroadcastChannel", "Received message from channel: " + channel);
634
+ Log.d("BroadcastChannel", "Message data: " + messageData.toString());
635
+
636
+ // Handle authentication messages
637
+ if ("auth".equals(channel)) {
638
+ String type = messageData.getString("type");
639
+ if ("success".equals(type)) {
640
+ // Handle successful authentication
641
+ String idToken = messageData.optString("idToken", "");
642
+ String accessToken = messageData.optString("accessToken", "");
643
+
644
+ try {
645
+ persistState(idToken, "refresh_token_placeholder", accessToken);
646
+ JSObject result = new JSObject();
647
+ result.put("accessToken", createAccessTokenObject(accessToken));
648
+ result.put("profile", createProfileObject(idToken));
649
+ result.put("idToken", idToken);
650
+
651
+ JSObject response = new JSObject();
652
+ response.put("provider", "apple");
653
+ response.put("result", result);
654
+
655
+ lastcall.resolve(response);
656
+ } catch (JSONException e) {
657
+ Log.e(SocialLoginPlugin.LOG_TAG, "Cannot create response", e);
658
+ lastcall.reject("Cannot create response", e);
659
+ }
660
+ } else if ("error".equals(type)) {
661
+ String error = messageData.optString("error", "Authentication failed");
662
+ lastcall.reject(error);
663
+ }
664
+ }
665
+ } catch (JSONException e) {
666
+ Log.e("BroadcastChannel", "Error parsing message", e);
667
+ lastcall.reject("Error parsing authentication message", e);
668
+ }
669
+ }
670
+ }
671
+
351
672
  private JSObject createAccessTokenObject(String accessToken) {
352
673
  JSObject tokenObject = new JSObject();
353
674
  tokenObject.put("token", accessToken);
@@ -43,11 +43,16 @@ public class SocialLoginPlugin extends Plugin {
43
43
  return;
44
44
  }
45
45
 
46
+ boolean useProperTokenExchange = apple.has("useProperTokenExchange") ? apple.getBool("useProperTokenExchange") : false;
47
+ boolean useBroadcastChannel = apple.has("useBroadcastChannel") ? apple.getBool("useBroadcastChannel") : false;
48
+
46
49
  AppleProvider appleProvider = new AppleProvider(
47
50
  androidAppleRedirect,
48
51
  androidAppleClientId,
49
52
  this.getActivity(),
50
- this.getContext()
53
+ this.getContext(),
54
+ useProperTokenExchange,
55
+ useBroadcastChannel
51
56
  );
52
57
 
53
58
  appleProvider.initialize();
package/dist/docs.json CHANGED
@@ -201,7 +201,7 @@
201
201
  "tags": [],
202
202
  "docs": "",
203
203
  "complexTypes": [],
204
- "type": "{ clientId?: string | undefined; redirectUrl?: string | undefined; } | undefined"
204
+ "type": "{ clientId?: string | undefined; redirectUrl?: string | undefined; useProperTokenExchange?: boolean | undefined; useBroadcastChannel?: boolean | undefined; } | undefined"
205
205
  }
206
206
  ]
207
207
  },
@@ -380,8 +380,13 @@
380
380
  "properties": [
381
381
  {
382
382
  "name": "accessToken",
383
- "tags": [],
384
- "docs": "",
383
+ "tags": [
384
+ {
385
+ "text": "Content depends on `useProperTokenExchange` setting:\n- When `useProperTokenExchange: true`: Real access token from Apple (~1 hour validity)\n- When `useProperTokenExchange: false`: Contains authorization code as token (legacy mode)\nUse `idToken` for user authentication, `accessToken` for API calls when properly exchanged.",
386
+ "name": "description"
387
+ }
388
+ ],
389
+ "docs": "Access token from Apple",
385
390
  "complexTypes": [
386
391
  "AccessToken"
387
392
  ],
@@ -389,17 +394,43 @@
389
394
  },
390
395
  {
391
396
  "name": "idToken",
392
- "tags": [],
393
- "docs": "",
397
+ "tags": [
398
+ {
399
+ "text": "Always contains the JWT with user identity information including:\n- User ID (sub claim)\n- Email (if user granted permission)\n- Name components (if user granted permission)\n- Email verification status\nThis is the primary token for user authentication and should be verified on your backend.",
400
+ "name": "description"
401
+ }
402
+ ],
403
+ "docs": "Identity token (JWT) from Apple",
394
404
  "complexTypes": [],
395
405
  "type": "string | null"
396
406
  },
397
407
  {
398
408
  "name": "profile",
399
- "tags": [],
400
- "docs": "",
409
+ "tags": [
410
+ {
411
+ "text": "Basic user profile data extracted from the identity token and Apple response:\n- `user`: Apple's user identifier (sub claim from idToken)\n- `email`: User's email address (if permission granted)\n- `givenName`: User's first name (if permission granted)\n- `familyName`: User's last name (if permission granted)",
412
+ "name": "description"
413
+ }
414
+ ],
415
+ "docs": "User profile information",
401
416
  "complexTypes": [],
402
417
  "type": "{ user: string; email: string | null; givenName: string | null; familyName: string | null; }"
418
+ },
419
+ {
420
+ "name": "authorizationCode",
421
+ "tags": [
422
+ {
423
+ "text": "Only present when `useProperTokenExchange` is `true`. This code should be exchanged\nfor proper access tokens on your backend using Apple's token endpoint. Use this for secure\nserver-side token validation and to obtain refresh tokens.",
424
+ "name": "description"
425
+ },
426
+ {
427
+ "text": "https ://developer.apple.com/documentation/sign_in_with_apple/tokenresponse",
428
+ "name": "see"
429
+ }
430
+ ],
431
+ "docs": "Authorization code for proper token exchange (when useProperTokenExchange is enabled)",
432
+ "complexTypes": [],
433
+ "type": "string | undefined"
403
434
  }
404
435
  ]
405
436
  },
@@ -615,6 +646,22 @@
615
646
  "docs": "State",
616
647
  "complexTypes": [],
617
648
  "type": "string | undefined"
649
+ },
650
+ {
651
+ "name": "useBroadcastChannel",
652
+ "tags": [
653
+ {
654
+ "text": "When enabled, uses Broadcast Channel API for communication instead of URL redirects.\nOnly applicable on platforms that support Broadcast Channel (Android).",
655
+ "name": "description"
656
+ },
657
+ {
658
+ "text": "false",
659
+ "name": "default"
660
+ }
661
+ ],
662
+ "docs": "Use Broadcast Channel for authentication flow",
663
+ "complexTypes": [],
664
+ "type": "boolean | undefined"
618
665
  }
619
666
  ]
620
667
  },
@@ -5,7 +5,8 @@ export declare class AppleSocialLogin extends BaseSocialLogin {
5
5
  private redirectUrl;
6
6
  private scriptLoaded;
7
7
  private scriptUrl;
8
- initialize(clientId: string | null, redirectUrl: string | null | undefined): Promise<void>;
8
+ private useProperTokenExchange;
9
+ initialize(clientId: string | null, redirectUrl: string | null | undefined, useProperTokenExchange?: boolean): Promise<void>;
9
10
  login(options: AppleProviderOptions): Promise<LoginResult>;
10
11
  logout(): Promise<void>;
11
12
  isLoggedIn(): Promise<{
@@ -6,10 +6,12 @@ export class AppleSocialLogin extends BaseSocialLogin {
6
6
  this.redirectUrl = null;
7
7
  this.scriptLoaded = false;
8
8
  this.scriptUrl = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js';
9
+ this.useProperTokenExchange = false;
9
10
  }
10
- async initialize(clientId, redirectUrl) {
11
+ async initialize(clientId, redirectUrl, useProperTokenExchange = false) {
11
12
  this.clientId = clientId;
12
13
  this.redirectUrl = redirectUrl || null;
14
+ this.useProperTokenExchange = useProperTokenExchange;
13
15
  if (clientId) {
14
16
  await this.loadAppleScript();
15
17
  }
@@ -35,18 +37,25 @@ export class AppleSocialLogin extends BaseSocialLogin {
35
37
  .signIn()
36
38
  .then((res) => {
37
39
  var _a, _b, _c, _d, _e;
38
- const result = {
39
- profile: {
40
+ let accessToken = null;
41
+ if (this.useProperTokenExchange) {
42
+ // When using proper token exchange, the authorization code should be exchanged
43
+ // for a proper access token on the backend. For now, we set accessToken to null
44
+ // and provide the authorization code in a separate field for backend processing.
45
+ accessToken = null;
46
+ }
47
+ else {
48
+ // Legacy behavior: use authorization code as access token for backward compatibility
49
+ accessToken = {
50
+ token: res.authorization.code || '',
51
+ };
52
+ }
53
+ const result = Object.assign({ profile: {
40
54
  user: res.user || '',
41
55
  email: ((_a = res.user) === null || _a === void 0 ? void 0 : _a.email) || null,
42
56
  givenName: ((_c = (_b = res.user) === null || _b === void 0 ? void 0 : _b.name) === null || _c === void 0 ? void 0 : _c.firstName) || null,
43
57
  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
- };
58
+ }, accessToken: accessToken, idToken: res.authorization.id_token || null }, (this.useProperTokenExchange && { authorizationCode: res.authorization.code }));
50
59
  resolve({ provider: 'apple', result });
51
60
  })
52
61
  .catch((error) => {
@@ -1 +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"]}
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 private useProperTokenExchange = false;\n\n async initialize(\n clientId: string | null,\n redirectUrl: string | null | undefined,\n useProperTokenExchange = false,\n ): Promise<void> {\n this.clientId = clientId;\n this.redirectUrl = redirectUrl || null;\n this.useProperTokenExchange = useProperTokenExchange;\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 let accessToken: { token: string } | null = null;\n\n if (this.useProperTokenExchange) {\n // When using proper token exchange, the authorization code should be exchanged\n // for a proper access token on the backend. For now, we set accessToken to null\n // and provide the authorization code in a separate field for backend processing.\n accessToken = null;\n } else {\n // Legacy behavior: use authorization code as access token for backward compatibility\n accessToken = {\n token: res.authorization.code || '',\n };\n }\n\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: accessToken,\n idToken: res.authorization.id_token || null,\n // Add authorization code for proper token exchange when flag is enabled\n ...(this.useProperTokenExchange && { authorizationCode: res.authorization.code }),\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"]}