@capgo/capacitor-social-login 8.3.17 → 8.3.18
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 +1 -0
- package/android/src/main/java/ee/forgr/capacitor/social/login/OAuth2Provider.java +18 -0
- package/android/src/main/java/ee/forgr/capacitor/social/login/SocialLoginPlugin.java +1 -1
- package/dist/docs.json +7 -0
- package/dist/esm/definitions.d.ts +5 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/google-provider.js +14 -12
- package/dist/esm/google-provider.js.map +1 -1
- package/dist/esm/oauth2-provider.js +7 -0
- package/dist/esm/oauth2-provider.js.map +1 -1
- package/dist/plugin.cjs.js +21 -12
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +21 -12
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/SocialLoginPlugin/OAuth2Provider.swift +9 -0
- package/ios/Sources/SocialLoginPlugin/SocialLoginPlugin.swift +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1074,6 +1074,7 @@ Configuration for a single OAuth2 provider instance
|
|
|
1074
1074
|
| **`issuerUrl`** | <code>string</code> | OpenID Connect issuer URL (enables discovery via `/.well-known/openid-configuration`). When set, you may omit explicit endpoints like `authorizationBaseUrl` and `accessTokenEndpoint`. Notes: - Explicit endpoints (authorization/token/logout) take precedence over discovered values. - Discovery is supported for `oauth2` on Web, iOS, and Android. | |
|
|
1075
1075
|
| **`authorizationBaseUrl`** | <code>string</code> | The base URL of the authorization endpoint | |
|
|
1076
1076
|
| **`authorizationEndpoint`** | <code>string</code> | Alias for `authorizationBaseUrl` (to match common OAuth/OIDC naming). | |
|
|
1077
|
+
| **`clientSecret`** | <code>string</code> | OAuth 2.0 client secret for token requests (e.g., when exchanging the code). This value is sent as `client_secret` in token/refresh requests when provided. | |
|
|
1077
1078
|
| **`accessTokenEndpoint`** | <code>string</code> | The URL to exchange the authorization code for tokens Required for authorization code flow | |
|
|
1078
1079
|
| **`tokenEndpoint`** | <code>string</code> | Alias for `accessTokenEndpoint` (to match common OAuth/OIDC naming). | |
|
|
1079
1080
|
| **`redirectUrl`** | <code>string</code> | Redirect URL that receives the OAuth callback | |
|
|
@@ -72,6 +72,7 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
72
72
|
private static class OAuth2ProviderConfig {
|
|
73
73
|
|
|
74
74
|
final String appId;
|
|
75
|
+
final String clientSecret;
|
|
75
76
|
final String issuerUrl;
|
|
76
77
|
final String authorizationBaseUrl;
|
|
77
78
|
final String accessTokenEndpoint;
|
|
@@ -92,6 +93,7 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
92
93
|
|
|
93
94
|
OAuth2ProviderConfig(
|
|
94
95
|
String appId,
|
|
96
|
+
String clientSecret,
|
|
95
97
|
String issuerUrl,
|
|
96
98
|
String authorizationBaseUrl,
|
|
97
99
|
String accessTokenEndpoint,
|
|
@@ -111,6 +113,7 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
111
113
|
boolean logsEnabled
|
|
112
114
|
) {
|
|
113
115
|
this.appId = appId;
|
|
116
|
+
this.clientSecret = clientSecret;
|
|
114
117
|
this.issuerUrl = issuerUrl;
|
|
115
118
|
this.authorizationBaseUrl = authorizationBaseUrl;
|
|
116
119
|
this.accessTokenEndpoint = accessTokenEndpoint;
|
|
@@ -216,6 +219,7 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
216
219
|
|
|
217
220
|
OAuth2ProviderConfig resolved = new OAuth2ProviderConfig(
|
|
218
221
|
config.appId,
|
|
222
|
+
config.clientSecret,
|
|
219
223
|
config.issuerUrl,
|
|
220
224
|
(config.authorizationBaseUrl != null && !config.authorizationBaseUrl.isEmpty())
|
|
221
225
|
? config.authorizationBaseUrl
|
|
@@ -285,6 +289,11 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
285
289
|
continue;
|
|
286
290
|
}
|
|
287
291
|
|
|
292
|
+
String clientSecret = config.optString("clientSecret", null);
|
|
293
|
+
if (clientSecret != null && clientSecret.isEmpty()) {
|
|
294
|
+
clientSecret = null;
|
|
295
|
+
}
|
|
296
|
+
|
|
288
297
|
Map<String, String> additionalParameters = null;
|
|
289
298
|
if (config.has("additionalParameters")) {
|
|
290
299
|
additionalParameters = jsonObjectToMap(config.getJSONObject("additionalParameters"));
|
|
@@ -307,6 +316,7 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
307
316
|
|
|
308
317
|
OAuth2ProviderConfig providerConfig = new OAuth2ProviderConfig(
|
|
309
318
|
appId,
|
|
319
|
+
clientSecret,
|
|
310
320
|
issuerUrl,
|
|
311
321
|
(authorizationBaseUrl != null && !authorizationBaseUrl.isEmpty()) ? authorizationBaseUrl : null,
|
|
312
322
|
config.has("accessTokenEndpoint") ? config.optString("accessTokenEndpoint", null) : config.optString("tokenEndpoint", null),
|
|
@@ -813,6 +823,10 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
813
823
|
bodyBuilder.add("code_verifier", pendingState.codeVerifier);
|
|
814
824
|
}
|
|
815
825
|
|
|
826
|
+
if (config.clientSecret != null) {
|
|
827
|
+
bodyBuilder.add("client_secret", config.clientSecret);
|
|
828
|
+
}
|
|
829
|
+
|
|
816
830
|
if (config.additionalTokenParameters != null) {
|
|
817
831
|
for (Map.Entry<String, String> entry : config.additionalTokenParameters.entrySet()) {
|
|
818
832
|
bodyBuilder.add(entry.getKey(), entry.getValue());
|
|
@@ -899,6 +913,10 @@ public class OAuth2Provider implements SocialProvider {
|
|
|
899
913
|
.add("refresh_token", refreshToken)
|
|
900
914
|
.add("client_id", config.appId);
|
|
901
915
|
|
|
916
|
+
if (config.clientSecret != null) {
|
|
917
|
+
bodyBuilder.add("client_secret", config.clientSecret);
|
|
918
|
+
}
|
|
919
|
+
|
|
902
920
|
if (config.additionalTokenParameters != null) {
|
|
903
921
|
for (Map.Entry<String, String> entry : config.additionalTokenParameters.entrySet()) {
|
|
904
922
|
bodyBuilder.add(entry.getKey(), entry.getValue());
|
|
@@ -24,7 +24,7 @@ import org.json.JSONObject;
|
|
|
24
24
|
@CapacitorPlugin(name = "SocialLogin")
|
|
25
25
|
public class SocialLoginPlugin extends Plugin {
|
|
26
26
|
|
|
27
|
-
private final String pluginVersion = "8.3.
|
|
27
|
+
private final String pluginVersion = "8.3.18";
|
|
28
28
|
|
|
29
29
|
public static String LOG_TAG = "CapgoSocialLogin";
|
|
30
30
|
public HashMap<String, SocialProvider> socialProviderHashMap = new HashMap<>();
|
package/dist/docs.json
CHANGED
|
@@ -466,6 +466,13 @@
|
|
|
466
466
|
"complexTypes": [],
|
|
467
467
|
"type": "string | undefined"
|
|
468
468
|
},
|
|
469
|
+
{
|
|
470
|
+
"name": "clientSecret",
|
|
471
|
+
"tags": [],
|
|
472
|
+
"docs": "OAuth 2.0 client secret for token requests (e.g., when exchanging the code).\nThis value is sent as `client_secret` in token/refresh requests when provided.",
|
|
473
|
+
"complexTypes": [],
|
|
474
|
+
"type": "string | undefined"
|
|
475
|
+
},
|
|
469
476
|
{
|
|
470
477
|
"name": "accessTokenEndpoint",
|
|
471
478
|
"tags": [
|
|
@@ -37,6 +37,11 @@ export interface OAuth2ProviderConfig {
|
|
|
37
37
|
* @example 'https://accounts.example.com/oauth2/authorize'
|
|
38
38
|
*/
|
|
39
39
|
authorizationEndpoint?: string;
|
|
40
|
+
/**
|
|
41
|
+
* OAuth 2.0 client secret for token requests (e.g., when exchanging the code).
|
|
42
|
+
* This value is sent as `client_secret` in token/refresh requests when provided.
|
|
43
|
+
*/
|
|
44
|
+
clientSecret?: string;
|
|
40
45
|
/**
|
|
41
46
|
* The URL to exchange the authorization code for tokens
|
|
42
47
|
* Required for authorization code flow
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration for a single OAuth2 provider instance\n */\nexport interface OAuth2ProviderConfig {\n /**\n * The OAuth 2.0 client identifier (App ID / Client ID).\n *\n * Note: this configuration object is only used by the plugin's built-in `oauth2` provider\n * (i.e. `SocialLogin.initialize({ oauth2: { ... } })`). It does not affect Google/Apple/Facebook/Twitter.\n * @example 'your-client-id'\n */\n appId?: string;\n /**\n * Alias for `appId` to match common OAuth/OIDC naming (`clientId`).\n * If both are provided, `appId` takes precedence.\n * @example 'your-client-id'\n */\n clientId?: string;\n /**\n * OpenID Connect issuer URL (enables discovery via `/.well-known/openid-configuration`).\n * When set, you may omit explicit endpoints like `authorizationBaseUrl` and `accessTokenEndpoint`.\n *\n * Notes:\n * - Explicit endpoints (authorization/token/logout) take precedence over discovered values.\n * - Discovery is supported for `oauth2` on Web, iOS, and Android.\n *\n * @example 'https://accounts.example.com'\n */\n issuerUrl?: string;\n /**\n * The base URL of the authorization endpoint\n * @example 'https://accounts.example.com/oauth2/authorize'\n */\n authorizationBaseUrl?: string;\n /**\n * Alias for `authorizationBaseUrl` (to match common OAuth/OIDC naming).\n * @example 'https://accounts.example.com/oauth2/authorize'\n */\n authorizationEndpoint?: string;\n /**\n * The URL to exchange the authorization code for tokens\n * Required for authorization code flow\n * @example 'https://accounts.example.com/oauth2/token'\n */\n accessTokenEndpoint?: string;\n /**\n * Alias for `accessTokenEndpoint` (to match common OAuth/OIDC naming).\n * @example 'https://accounts.example.com/oauth2/token'\n */\n tokenEndpoint?: string;\n /**\n * Redirect URL that receives the OAuth callback\n * @example 'myapp://oauth/callback'\n */\n redirectUrl: string;\n /**\n * Optional URL to fetch user profile/resource data after authentication\n * The access token will be sent as Bearer token in the Authorization header\n * @example 'https://api.example.com/userinfo'\n */\n resourceUrl?: string;\n /**\n * The OAuth response type\n * - 'code': Authorization Code flow (recommended, requires accessTokenEndpoint)\n * - 'token': Implicit flow (less secure, tokens returned directly)\n * @default 'code'\n */\n responseType?: 'code' | 'token';\n /**\n * Enable PKCE (Proof Key for Code Exchange)\n * Strongly recommended for public clients (mobile/web apps)\n * @default true\n */\n pkceEnabled?: boolean;\n /**\n * Default scopes to request during authorization\n * @example 'openid profile email'\n * @example ['openid','profile','email']\n */\n scope?: string | string[];\n /**\n * Alias for `scope` using common naming (`scopes`).\n * If both are provided, `scope` takes precedence.\n */\n scopes?: string[];\n /**\n * Additional parameters to include in the authorization request\n * @example { prompt: 'consent', login_hint: 'user@example.com' }\n */\n additionalParameters?: Record<string, string>;\n /**\n * Convenience option for OIDC `login_hint`.\n * Equivalent to passing `additionalParameters.login_hint`.\n */\n loginHint?: string;\n /**\n * Convenience option for OAuth/OIDC `prompt`.\n * Equivalent to passing `additionalParameters.prompt`.\n */\n prompt?: string;\n /**\n * Additional parameters to include in token requests (code exchange / refresh).\n * Useful for providers that require non-standard parameters.\n */\n additionalTokenParameters?: Record<string, string>;\n /**\n * Additional headers to include when fetching the resource URL\n * @example { 'X-Custom-Header': 'value' }\n */\n additionalResourceHeaders?: Record<string, string>;\n /**\n * Custom logout URL for ending the session\n * @example 'https://accounts.example.com/logout'\n */\n logoutUrl?: string;\n /**\n * Alias for `logoutUrl` to match OIDC naming (`endSessionEndpoint`).\n * @example 'https://accounts.example.com/logout'\n */\n endSessionEndpoint?: string;\n /**\n * OIDC post logout redirect URL (sent as `post_logout_redirect_uri` when building the end-session URL).\n * @example 'myapp://logout/callback'\n */\n postLogoutRedirectUrl?: string;\n /**\n * Additional parameters to include in logout / end-session URL.\n */\n additionalLogoutParameters?: Record<string, string>;\n /**\n * iOS-only: Whether to prefer an ephemeral browser session for ASWebAuthenticationSession.\n * Defaults to true to match existing behavior in this plugin.\n */\n iosPrefersEphemeralWebBrowserSession?: boolean;\n /**\n * Alias for `iosPrefersEphemeralWebBrowserSession` (to match Capawesome OAuth naming).\n */\n iosPrefersEphemeralSession?: boolean;\n /**\n * Enable debug logging\n * @default false\n */\n logsEnabled?: boolean;\n}\n\nexport interface InitializeOptions {\n /**\n * OAuth2 provider configurations.\n * Supports multiple providers by using a Record with provider IDs as keys.\n * @example\n * {\n * github: { appId: '...', authorizationBaseUrl: 'https://github.com/login/oauth/authorize', ... },\n * azure: { appId: '...', authorizationBaseUrl: 'https://login.microsoftonline.com/.../oauth2/v2.0/authorize', ... }\n * }\n */\n oauth2?: Record<string, OAuth2ProviderConfig>;\n twitter?: {\n /**\n * The OAuth 2.0 client identifier issued by X (Twitter) Developer Portal\n * @example 'Y2xpZW50SWQ'\n */\n clientId: string;\n /**\n * Redirect URL that is registered inside the X Developer Portal.\n * The plugin uses this URL on every platform to receive the authorization code.\n * @example 'myapp://auth/x'\n */\n redirectUrl: string;\n /**\n * Default scopes appended to every login request when no custom scopes are provided.\n * @description Defaults to the minimum required scopes for Log in with X.\n * @default ['tweet.read','users.read']\n */\n defaultScopes?: string[];\n /**\n * Force the consent screen to show on every login attempt.\n * Mirrors X's `force_login=true` flag.\n * @default false\n */\n forceLogin?: boolean;\n /**\n * Optional audience value when your application has been approved for multi-tenant access.\n */\n audience?: string;\n };\n facebook?: {\n /**\n * Facebook App ID, provided by Facebook for web, in mobile it's set in the native files\n * @description For business integrations, use your Business App ID from Facebook Developer Console.\n * Business apps can access additional permissions like Instagram API, Pages API, and business management features.\n * @see docs/facebook_business_login.md for business app setup guide\n */\n appId: string;\n /**\n * Facebook Client Token, provided by Facebook for web, in mobile it's set in the native files\n */\n clientToken?: string;\n /**\n * Locale\n * @description The locale to use for the Facebook SDK (e.g., 'en_US', 'fr_FR', 'es_ES')\n * @default 'en_US'\n * @example 'fr_FR'\n */\n locale?: string;\n };\n\n google?: {\n /**\n * The app's client ID, found and created in the Google Developers Console.\n * Required for iOS platform.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSClientId?: string;\n /**\n * The app's server client ID, required for offline mode on iOS.\n * Should be the same value as webClientId.\n * Found and created in the Google Developers Console.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSServerClientId?: string;\n /**\n * The app's web client ID, found and created in the Google Developers Console.\n * Required for Android and Web platforms.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n webClientId?: string;\n /**\n * The login mode, can be online or offline.\n *\n * **Online mode (default):**\n * - Returns user profile data and access tokens\n * - Supports all methods: login, logout, isLoggedIn, getAuthorizationCode\n *\n * **Offline mode:**\n * - Returns only serverAuthCode for backend authentication\n * - No user profile data available\n * - **Limitations:** The following methods are NOT supported in offline mode:\n * - `logout()` - Will reject with \"not implemented when using offline mode\"\n * - `isLoggedIn()` - Will reject with \"not implemented when using offline mode\"\n * - `getAuthorizationCode()` - Will reject with \"not implemented when using offline mode\"\n * - `refresh()` - Will reject because offline mode only returns `serverAuthCode`; token refresh must happen on your backend\n * - Only `login()` method works in offline mode, returning serverAuthCode only\n * - `serverAuthCode` must be exchanged on your backend for access/refresh tokens\n * - Requires `iOSServerClientId` to be set on iOS\n *\n * @example 'offline'\n * @default 'online'\n * @since 3.1.0\n */\n mode?: 'online' | 'offline';\n /**\n * Filter visible accounts by hosted domain\n * @description filter visible accounts by hosted domain\n */\n hostedDomain?: string;\n /**\n * Google Redirect URL, should be your backend url that is configured in your google app\n */\n redirectUrl?: string;\n };\n apple?: {\n /**\n * Apple Client ID, provided by Apple for web and Android\n */\n clientId?: string;\n /**\n * Apple Redirect URL, should be your backend url that is configured in your apple app\n *\n * **Note**: Use empty string `''` for iOS to prevent redirect.\n * **Note**: Not required when using Broadcast Channel mode on Android.\n */\n redirectUrl?: string;\n /**\n * Use proper token exchange for Apple Sign-In\n * @description Controls how Apple Sign-In tokens are handled and what gets returned:\n *\n * **When `true` (Recommended for new implementations):**\n * - Exchanges authorization code for proper access tokens via Apple's token endpoint\n * - `idToken`: JWT containing user identity information (email, name, user ID)\n * - `accessToken.token`: Proper access token from Apple (short-lived, ~1 hour)\n * - `authorizationCode`: Raw authorization code for backend token exchange\n *\n * **When `false` (Default - Legacy mode):**\n * - Uses authorization code directly as access token for backward compatibility\n * - `idToken`: JWT containing user identity information (email, name, user ID)\n * - `accessToken.token`: The authorization code itself (not a real access token)\n * - `authorizationCode`: undefined\n *\n * @default false\n * @example\n * // Enable proper token exchange (recommended)\n * useProperTokenExchange: true\n * // Result: idToken=JWT, accessToken=real_token, authorizationCode=present\n *\n * // Legacy mode (backward compatibility)\n * useProperTokenExchange: false\n * // Result: idToken=JWT, accessToken=auth_code, authorizationCode=undefined\n */\n useProperTokenExchange?: boolean;\n /**\n * Use Broadcast Channel for Android Apple Sign-In (Recommended)\n * @description When enabled, Android uses Broadcast Channel API instead of URL redirects.\n * This eliminates the need for redirect URL configuration and server-side setup.\n *\n * **Benefits:**\n * - No redirect URL configuration required\n * - No backend server needed for Android\n * - Simpler setup and more reliable communication\n * - Direct client-server communication via Broadcast Channel\n *\n * **When `true`:**\n * - Uses Broadcast Channel for authentication flow\n * - `redirectUrl` is ignored\n * - Requires Broadcast Channel compatible backend or direct token handling\n *\n * **When `false` (Default - Legacy mode):**\n * - Uses traditional URL redirect flow\n * - Requires `redirectUrl` configuration\n * - Requires backend server for token exchange\n *\n * @default false\n * @since 7.10.0\n * @example\n * // Enable Broadcast Channel mode (recommended for new Android implementations)\n * useBroadcastChannel: true\n * // Result: Simplified setup, no redirect URL needed\n *\n * // Legacy mode (backward compatibility)\n * useBroadcastChannel: false\n * // Result: Traditional URL redirect flow with server-side setup\n */\n useBroadcastChannel?: boolean;\n };\n}\n\nexport interface FacebookLoginOptions {\n /**\n * Permissions\n * @description Select permissions to login with. Supports both consumer and business permissions.\n *\n * **Consumer Permissions:**\n * - `email` - User's email address\n * - `public_profile` - User's public profile info\n * - `user_friends` - List of friends who also use your app\n *\n * **Business Permissions** (require business app configuration and may need App Review):\n * - `instagram_basic` - Instagram Basic Display API access\n * - `instagram_manage_insights` - Instagram Insights data\n * - `instagram_manage_comments` - Manage Instagram comments\n * - `instagram_content_publish` - Publish to Instagram\n * - `pages_show_list` - List of Pages managed by user\n * - `pages_read_engagement` - Read Page engagement metrics\n * - `pages_manage_posts` - Manage Page posts\n * - `pages_messaging` - Page messaging features\n * - `business_management` - Manage business assets\n * - `catalog_management` - Manage product catalogs\n * - `ads_management` - Manage advertising accounts\n *\n * @example ['email', 'public_profile'] // Consumer permissions\n * @example ['email', 'instagram_basic', 'pages_show_list'] // Business permissions\n * @see https://developers.facebook.com/docs/permissions/reference\n * @see docs/facebook_business_login.md for complete business integration guide\n */\n permissions: string[];\n /**\n * Is Limited Login\n * @description use limited login for Facebook iOS only. Important: This is iOS-only and doesn't affect Android.\n * Even if set to false, Facebook will automatically force it to true if App Tracking Transparency (ATT) permission is not granted.\n * Developers should always be prepared to handle both limited and full login scenarios.\n * @default false\n */\n limitedLogin?: boolean;\n /**\n * Nonce\n * @description A custom nonce to use for the login request\n */\n nonce?: string;\n}\n\nexport interface TwitterLoginOptions {\n /**\n * Additional scopes to request during login.\n * If omitted the plugin falls back to the default scopes configured during initialization.\n * @example ['tweet.read','users.read','offline.access']\n */\n scopes?: string[];\n /**\n * Provide a custom OAuth state value.\n * When not provided the plugin generates a cryptographically random value.\n */\n state?: string;\n /**\n * Provide a pre-computed PKCE code verifier (mostly used for testing).\n * When omitted the plugin generates a secure verifier automatically.\n */\n codeVerifier?: string;\n /**\n * Override the redirect URI for a single login call.\n * Useful when the same app supports multiple callback URLs per platform.\n */\n redirectUrl?: string;\n /**\n * Force the consent screen on every attempt, maps to `force_login=true`.\n */\n forceLogin?: boolean;\n}\n\nexport interface OAuth2LoginOptions {\n /**\n * The provider ID as configured in initialize()\n * This is required to identify which OAuth2 provider to use\n * @example 'github', 'azure', 'keycloak'\n */\n providerId: string;\n /**\n * Override the scopes for this login request\n * If not provided, uses the scopes from initialization\n */\n scope?: string | string[];\n /**\n * Alias for `scope` using common naming (`scopes`).\n * If both are provided, `scope` takes precedence.\n */\n scopes?: string[];\n /**\n * Custom state parameter for CSRF protection\n * If not provided, a random value is generated\n */\n state?: string;\n /**\n * Override PKCE code verifier (for testing purposes)\n * If not provided, a secure random verifier is generated\n */\n codeVerifier?: string;\n /**\n * Override redirect URL for this login request\n */\n redirectUrl?: string;\n /**\n * Additional parameters to add to the authorization URL\n */\n additionalParameters?: Record<string, string>;\n /**\n * Convenience option for OIDC `login_hint`.\n * Equivalent to passing `additionalParameters.login_hint`.\n */\n loginHint?: string;\n /**\n * Convenience option for OAuth/OIDC `prompt`.\n * Equivalent to passing `additionalParameters.prompt`.\n */\n prompt?: string;\n /**\n * Web-only (`oauth2` provider only): Use a full-page redirect instead of a popup window.\n *\n * When using `redirect`, the promise returned by `login()` will not resolve because the page navigates away.\n * After the redirect lands back in your app, call `SocialLogin.handleRedirectCallback()` on that page to\n * parse the result.\n *\n * @default 'popup'\n */\n flow?: 'popup' | 'redirect';\n}\n\nexport interface OAuth2LoginResponse {\n /**\n * The provider ID that was used for this login\n */\n providerId: string;\n /**\n * The access token received from the OAuth provider\n */\n accessToken: AccessToken | null;\n /**\n * The ID token (JWT) if provided by the OAuth server (e.g., OpenID Connect)\n */\n idToken: string | null;\n /**\n * The refresh token if provided (requires appropriate scope like offline_access)\n */\n refreshToken: string | null;\n /**\n * Resource data fetched from resourceUrl if configured\n * Contains the raw JSON response from the resource endpoint\n */\n resourceData: Record<string, unknown> | null;\n /**\n * The scopes that were granted\n */\n scope: string[];\n /**\n * Token type (usually 'bearer')\n */\n tokenType: string;\n /**\n * Token expiration time in seconds\n */\n expiresIn: number | null;\n}\n\nexport interface GoogleLoginOptions {\n /**\n * Specifies the scopes required for accessing Google APIs\n * The default is defined in the configuration.\n * @example [\"profile\", \"email\"]\n * @see [Google OAuth2 Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * Force refresh token (only for Android)\n * @description force refresh token\n * @default false\n * @note On Android, the OS caches access tokens, and if a token is invalid (e.g., user revoked app access), the plugin might return an invalid accessToken. Using getAuthorizationCode() is recommended to ensure the token is valid.\n */\n forceRefreshToken?: boolean;\n /**\n * Force account selection prompt (iOS)\n * @description forces the account selection prompt to appear on iOS\n * @default false\n */\n forcePrompt?: boolean;\n /**\n * Style\n * @description style\n * @default 'standard'\n */\n style?: 'bottom' | 'standard';\n /**\n * Filter by authorized accounts (Android only)\n * @description Only show accounts that have previously been used to sign in to the app.\n * This option is only available for the 'bottom' style.\n * Note: For Family Link supervised accounts, this should be set to false.\n * @default true\n */\n filterByAuthorizedAccounts?: boolean;\n /**\n * Auto select enabled (Android only)\n * @description Automatically select the account if only one Google account is available.\n * This option is only available for the 'bottom' style.\n * @default false\n */\n autoSelectEnabled?: boolean;\n /**\n * Prompt parameter for Google OAuth (Web only)\n * @description A space-delimited, case-sensitive list of prompts to present the user.\n * If you don't specify this parameter, the user will be prompted only the first time your project requests access.\n *\n * **Possible values:**\n * - `none`: Don't display any authentication or consent screens. Must not be specified with other values.\n * - `consent`: Prompt the user for consent.\n * - `select_account`: Prompt the user to select an account.\n *\n * **Examples:**\n * - `prompt: 'consent'` - Always show consent screen\n * - `prompt: 'select_account'` - Always show account selection\n * - `prompt: 'consent select_account'` - Show both consent and account selection\n *\n * **Note:** This parameter only affects web platform behavior. Mobile platforms use their own native prompts.\n *\n * @example 'consent'\n * @example 'select_account'\n * @example 'consent select_account'\n * @see [Google OAuth2 Prompt Parameter](https://developers.google.com/identity/protocols/oauth2/openid-connect#prompt)\n * @since 7.12.0\n */\n prompt?: 'none' | 'consent' | 'select_account' | 'consent select_account' | 'select_account consent';\n}\n\nexport interface GoogleLoginResponseOnline {\n accessToken: AccessToken | null;\n idToken: string | null;\n profile: {\n email: string | null;\n familyName: string | null;\n givenName: string | null;\n id: string | null;\n name: string | null;\n imageUrl: string | null;\n };\n responseType: 'online';\n}\n\nexport interface GoogleLoginResponseOffline {\n serverAuthCode: string;\n responseType: 'offline';\n}\n\nexport type GoogleLoginResponse = GoogleLoginResponseOnline | GoogleLoginResponseOffline;\n\nexport interface AppleProviderOptions {\n /**\n * Scopes\n * @description An array of scopes to request during login\n * @example [\"name\", \"email\"]\n * default: [\"name\", \"email\"]\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * State\n * @description state\n */\n state?: string;\n /**\n * Use Broadcast Channel for authentication flow\n * @description When enabled, uses Broadcast Channel API for communication instead of URL redirects.\n * Only applicable on platforms that support Broadcast Channel (Android).\n * @default false\n */\n useBroadcastChannel?: boolean;\n}\n\nexport interface AppleProviderResponse {\n /**\n * Access token from Apple\n * @description 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)\n * Use `idToken` for user authentication, `accessToken` for API calls when properly exchanged.\n */\n accessToken: AccessToken | null;\n\n /**\n * Identity token (JWT) from Apple\n * @description 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\n * This is the primary token for user authentication and should be verified on your backend.\n */\n idToken: string | null;\n\n /**\n * User profile information\n * @description 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)\n */\n profile: {\n user: string;\n email: string | null;\n givenName: string | null;\n familyName: string | null;\n };\n\n /**\n * Authorization code for proper token exchange (when useProperTokenExchange is enabled)\n * @description Only present when `useProperTokenExchange` is `true`. This code should be exchanged\n * for proper access tokens on your backend using Apple's token endpoint. Use this for secure\n * server-side token validation and to obtain refresh tokens.\n * @see https://developer.apple.com/documentation/sign_in_with_apple/tokenresponse\n */\n authorizationCode?: string;\n}\n\nexport type LoginOptions =\n | {\n provider: 'facebook';\n options: FacebookLoginOptions;\n }\n | {\n provider: 'google';\n options: GoogleLoginOptions;\n }\n | {\n provider: 'apple';\n options: AppleProviderOptions;\n }\n | {\n provider: 'twitter';\n options: TwitterLoginOptions;\n }\n | {\n provider: 'oauth2';\n options: OAuth2LoginOptions;\n };\n\nexport type LoginResult =\n | {\n provider: 'facebook';\n result: FacebookLoginResponse;\n }\n | {\n provider: 'google';\n result: GoogleLoginResponse;\n }\n | {\n provider: 'apple';\n result: AppleProviderResponse;\n }\n | {\n provider: 'twitter';\n result: TwitterLoginResponse;\n }\n | {\n provider: 'oauth2';\n result: OAuth2LoginResponse;\n };\n\nexport interface AccessToken {\n applicationId?: string;\n declinedPermissions?: string[];\n expires?: string;\n isExpired?: boolean;\n lastRefresh?: string;\n permissions?: string[];\n token: string;\n tokenType?: string;\n refreshToken?: string;\n userId?: string;\n}\n\nexport interface FacebookLoginResponse {\n accessToken: AccessToken | null;\n idToken: string | null;\n profile: {\n userID: string;\n email: string | null;\n friendIDs: string[];\n birthday: string | null;\n ageRange: { min?: number; max?: number } | null;\n gender: string | null;\n location: { id: string; name: string } | null;\n hometown: { id: string; name: string } | null;\n profileURL: string | null;\n name: string | null;\n imageURL: string | null;\n };\n}\n\nexport interface TwitterProfile {\n id: string;\n username: string;\n name: string | null;\n profileImageUrl: string | null;\n verified: boolean;\n email?: string | null;\n}\n\nexport interface TwitterLoginResponse {\n accessToken: AccessToken | null;\n refreshToken?: string | null;\n scope: string[];\n tokenType: 'bearer';\n expiresIn?: number | null;\n profile: TwitterProfile;\n}\n\nexport interface AuthorizationCode {\n /**\n * Jwt\n * @description A JSON web token\n */\n jwt?: string;\n /**\n * Access Token\n * @description An access token\n */\n accessToken?: string;\n}\n\nexport interface AuthorizationCodeOptions {\n /**\n * Provider\n * @description Provider for the authorization code\n */\n provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2';\n /**\n * Provider ID for OAuth2 providers (required when provider is 'oauth2')\n * @description The ID used when configuring the OAuth2 provider in initialize()\n */\n providerId?: string;\n}\n\nexport interface isLoggedInOptions {\n /**\n * Provider\n * @description Provider for the isLoggedIn\n */\n provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2';\n /**\n * Provider ID for OAuth2 providers (required when provider is 'oauth2')\n * @description The ID used when configuring the OAuth2 provider in initialize()\n */\n providerId?: string;\n}\n\n// Define the provider-specific call types\nexport type ProviderSpecificCall = 'facebook#getProfile' | 'facebook#requestTracking';\n\n// Define the options and response types for each specific call\nexport interface FacebookGetProfileOptions {\n /**\n * Fields to retrieve from Facebook profile\n * @example [\"id\", \"name\", \"email\", \"picture\"]\n */\n fields?: string[];\n}\n\nexport interface FacebookGetProfileResponse {\n /**\n * Facebook profile data\n */\n profile: {\n id: string | null;\n name: string | null;\n email: string | null;\n first_name: string | null;\n last_name: string | null;\n picture?: {\n data: {\n height: number | null;\n is_silhouette: boolean | null;\n url: string | null;\n width: number | null;\n };\n } | null;\n [key: string]: any; // For additional fields that might be requested\n };\n}\n\nexport interface OpenSecureWindowOptions {\n /**\n * The endpoint to open\n */\n authEndpoint: string;\n /**\n * The redirect URI to use for the openSecureWindow call.\n * This will be checked to make sure it matches the redirect URI after the window finishes the redirection.\n */\n redirectUri: string;\n /**\n * The name of the broadcast channel to listen to, relevant only for web\n */\n broadcastChannelName?: string;\n}\n\nexport interface OpenSecureWindowResponse {\n /**\n * The result of the openSecureWindow call\n */\n redirectedUri: string;\n}\n\nexport type FacebookRequestTrackingOptions = Record<string, never>;\n\nexport interface FacebookRequestTrackingResponse {\n /**\n * App tracking authorization status\n */\n status: 'authorized' | 'denied' | 'notDetermined' | 'restricted';\n}\n\n// Map call strings to their options and response types\nexport type ProviderSpecificCallOptionsMap = {\n 'facebook#getProfile': FacebookGetProfileOptions;\n 'facebook#requestTracking': FacebookRequestTrackingOptions;\n};\n\nexport type ProviderSpecificCallResponseMap = {\n 'facebook#getProfile': FacebookGetProfileResponse;\n 'facebook#requestTracking': FacebookRequestTrackingResponse;\n};\n\n// Add a helper type to map providers to their response types\nexport type ProviderResponseMap = {\n facebook: FacebookLoginResponse;\n google: GoogleLoginResponse;\n apple: AppleProviderResponse;\n twitter: TwitterLoginResponse;\n oauth2: OAuth2LoginResponse;\n};\n\nexport interface SocialLoginPlugin {\n /**\n * Initialize the plugin\n * @description initialize the plugin with the required options\n */\n initialize(options: InitializeOptions): Promise<void>;\n /**\n * Login with the selected provider\n * @description login with the selected provider\n */\n login<T extends LoginOptions['provider']>(\n options: Extract<LoginOptions, { provider: T }>,\n ): Promise<{ provider: T; result: ProviderResponseMap[T] }>;\n /**\n * Logout\n * @description Logout the user from the specified provider\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * It will reject with error: \"logout is not implemented when using offline mode\"\n *\n * @throws Error if Google provider is in offline mode\n */\n logout(options: {\n provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2';\n providerId?: string;\n }): Promise<void>;\n /**\n * IsLoggedIn\n * @description Check if the user is currently logged in with the specified provider\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * It will reject with error: \"isLoggedIn is not implemented when using offline mode\"\n *\n * @throws Error if Google provider is in offline mode\n */\n isLoggedIn(options: isLoggedInOptions): Promise<{ isLoggedIn: boolean }>;\n\n /**\n * Get the current authorization code\n * @description Get the authorization code for server-side authentication\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * It will reject with error: \"getAuthorizationCode is not implemented when using offline mode\"\n *\n * In offline mode, the authorization code (serverAuthCode) is already returned by the `login()` method.\n *\n * @throws Error if Google provider is in offline mode\n */\n getAuthorizationCode(options: AuthorizationCodeOptions): Promise<AuthorizationCode>;\n /**\n * Refresh the access token\n * @description refresh the access token\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * Offline mode only returns `serverAuthCode` for backend token exchange, so token refresh must happen on your backend.\n * The plugin logs and rejects with a message explaining that you should send `serverAuthCode` to your backend,\n * refresh the Google tokens there, or switch to `mode: 'online'` for client-side refresh.\n *\n * **Google Web Limitation:**\n * On Web, Google `refresh()` is not implemented, even when using `mode: 'online'`.\n * Call `login()` again on Web to obtain a fresh token instead.\n *\n * @throws Error if Google provider is in offline mode, or on Web where Google `refresh()` is not implemented\n */\n refresh(options: LoginOptions): Promise<void>;\n\n /**\n * OAuth2 refresh-token helper (feature parity with Capawesome OAuth).\n *\n * Scope:\n * - Only applies to the built-in `oauth2` provider (not Google/Apple/Facebook/Twitter).\n * - Requires a token endpoint (either `accessTokenEndpoint`/`tokenEndpoint` or `issuerUrl` discovery).\n *\n * Security note:\n * - This does not validate JWT signatures. It only exchanges/refreshes tokens.\n *\n * If `refreshToken` is omitted, the plugin will attempt to use the stored refresh token (if available).\n */\n refreshToken(options: {\n provider: 'oauth2';\n providerId: string;\n refreshToken?: string;\n additionalParameters?: Record<string, string>;\n }): Promise<OAuth2LoginResponse>;\n\n /**\n * Web-only: handle the OAuth redirect callback and return the parsed result.\n *\n * Notes:\n * - This is only meaningful on Web. iOS/Android implementations will reject.\n * - Intended for redirect-based flows (e.g. `oauth2` with `flow: 'redirect'`) where the page navigates away.\n */\n handleRedirectCallback(): Promise<LoginResult | null>;\n\n /**\n * Decode a JWT (typically an OIDC ID token) into its claims.\n *\n * Notes:\n * - Accepts both `idToken` and `token` to match common naming (Capawesome uses `token`).\n * - This does not validate the signature or issuer/audience. It only base64url-decodes the payload.\n */\n decodeIdToken(options: { idToken?: string; token?: string }): Promise<{ claims: Record<string, any> }>;\n\n /**\n * Convert an access token expiration timestamp (milliseconds since epoch) to an ISO date string.\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n getAccessTokenExpirationDate(options: {\n /**\n * Access token expiration date in milliseconds since epoch.\n * Typically: `Date.now() + expiresInSeconds * 1000`.\n */\n accessTokenExpirationDate: number;\n }): Promise<{ date: string }>;\n\n /**\n * Check if an access token is available (non-empty).\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n isAccessTokenAvailable(options: { accessToken: string | null }): Promise<{ isAvailable: boolean }>;\n\n /**\n * Check if an access token is expired.\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n isAccessTokenExpired(options: { accessTokenExpirationDate: number }): Promise<{ isExpired: boolean }>;\n\n /**\n * Check if a refresh token is available (non-empty).\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n isRefreshTokenAvailable(options: { refreshToken: string | null }): Promise<{ isAvailable: boolean }>;\n\n /**\n * Execute provider-specific calls\n * @description Execute a provider-specific functionality\n */\n providerSpecificCall<T extends ProviderSpecificCall>(options: {\n call: T;\n options: ProviderSpecificCallOptionsMap[T];\n }): Promise<ProviderSpecificCallResponseMap[T]>;\n\n /**\n * Get the native Capacitor plugin version\n *\n * @returns {Promise<{ id: string }>} an Promise with version for this device\n * @throws An error if the something went wrong\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n /**\n * Opens a secured window for OAuth2 authentication.\n * For web, you should have the code in the redirected page to use a broadcast channel to send the redirected url to the app\n * Something like:\n * ```html\n * <html>\n * <head></head>\n * <body>\n * <script>\n * const searchParams = new URLSearchParams(location.search)\n * if (searchParams.has(\"code\")) {\n * new BroadcastChannel(\"my-channel-name\").postMessage(location.href);\n * window.close();\n * }\n * </script>\n * </body>\n * </html>\n * ```\n * For mobile, you should have a redirect uri that opens the app, something like: `myapp://oauth_callback/`\n * And make sure to register it in the app's info.plist:\n * ```xml\n * <key>CFBundleURLTypes</key>\n * <array>\n * <dict>\n * <key>CFBundleURLSchemes</key>\n * <array>\n * <string>myapp</string>\n * </array>\n * </dict>\n * </array>\n * ```\n * And in the AndroidManifest.xml file:\n * ```xml\n * <activity>\n * <intent-filter>\n * <action android:name=\"android.intent.action.VIEW\" />\n * <category android:name=\"android.intent.category.DEFAULT\" />\n * <category android:name=\"android.intent.category.BROWSABLE\" />\n * <data android:host=\"oauth_callback\" android:scheme=\"myapp\" />\n * </intent-filter>\n * </activity>\n * ```\n * @param options - the options for the openSecureWindow call\n */\n openSecureWindow(options: OpenSecureWindowOptions): Promise<OpenSecureWindowResponse>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration for a single OAuth2 provider instance\n */\nexport interface OAuth2ProviderConfig {\n /**\n * The OAuth 2.0 client identifier (App ID / Client ID).\n *\n * Note: this configuration object is only used by the plugin's built-in `oauth2` provider\n * (i.e. `SocialLogin.initialize({ oauth2: { ... } })`). It does not affect Google/Apple/Facebook/Twitter.\n * @example 'your-client-id'\n */\n appId?: string;\n /**\n * Alias for `appId` to match common OAuth/OIDC naming (`clientId`).\n * If both are provided, `appId` takes precedence.\n * @example 'your-client-id'\n */\n clientId?: string;\n /**\n * OpenID Connect issuer URL (enables discovery via `/.well-known/openid-configuration`).\n * When set, you may omit explicit endpoints like `authorizationBaseUrl` and `accessTokenEndpoint`.\n *\n * Notes:\n * - Explicit endpoints (authorization/token/logout) take precedence over discovered values.\n * - Discovery is supported for `oauth2` on Web, iOS, and Android.\n *\n * @example 'https://accounts.example.com'\n */\n issuerUrl?: string;\n /**\n * The base URL of the authorization endpoint\n * @example 'https://accounts.example.com/oauth2/authorize'\n */\n authorizationBaseUrl?: string;\n /**\n * Alias for `authorizationBaseUrl` (to match common OAuth/OIDC naming).\n * @example 'https://accounts.example.com/oauth2/authorize'\n */\n authorizationEndpoint?: string;\n /**\n * OAuth 2.0 client secret for token requests (e.g., when exchanging the code).\n * This value is sent as `client_secret` in token/refresh requests when provided.\n */\n clientSecret?: string;\n /**\n * The URL to exchange the authorization code for tokens\n * Required for authorization code flow\n * @example 'https://accounts.example.com/oauth2/token'\n */\n accessTokenEndpoint?: string;\n /**\n * Alias for `accessTokenEndpoint` (to match common OAuth/OIDC naming).\n * @example 'https://accounts.example.com/oauth2/token'\n */\n tokenEndpoint?: string;\n /**\n * Redirect URL that receives the OAuth callback\n * @example 'myapp://oauth/callback'\n */\n redirectUrl: string;\n /**\n * Optional URL to fetch user profile/resource data after authentication\n * The access token will be sent as Bearer token in the Authorization header\n * @example 'https://api.example.com/userinfo'\n */\n resourceUrl?: string;\n /**\n * The OAuth response type\n * - 'code': Authorization Code flow (recommended, requires accessTokenEndpoint)\n * - 'token': Implicit flow (less secure, tokens returned directly)\n * @default 'code'\n */\n responseType?: 'code' | 'token';\n /**\n * Enable PKCE (Proof Key for Code Exchange)\n * Strongly recommended for public clients (mobile/web apps)\n * @default true\n */\n pkceEnabled?: boolean;\n /**\n * Default scopes to request during authorization\n * @example 'openid profile email'\n * @example ['openid','profile','email']\n */\n scope?: string | string[];\n /**\n * Alias for `scope` using common naming (`scopes`).\n * If both are provided, `scope` takes precedence.\n */\n scopes?: string[];\n /**\n * Additional parameters to include in the authorization request\n * @example { prompt: 'consent', login_hint: 'user@example.com' }\n */\n additionalParameters?: Record<string, string>;\n /**\n * Convenience option for OIDC `login_hint`.\n * Equivalent to passing `additionalParameters.login_hint`.\n */\n loginHint?: string;\n /**\n * Convenience option for OAuth/OIDC `prompt`.\n * Equivalent to passing `additionalParameters.prompt`.\n */\n prompt?: string;\n /**\n * Additional parameters to include in token requests (code exchange / refresh).\n * Useful for providers that require non-standard parameters.\n */\n additionalTokenParameters?: Record<string, string>;\n /**\n * Additional headers to include when fetching the resource URL\n * @example { 'X-Custom-Header': 'value' }\n */\n additionalResourceHeaders?: Record<string, string>;\n /**\n * Custom logout URL for ending the session\n * @example 'https://accounts.example.com/logout'\n */\n logoutUrl?: string;\n /**\n * Alias for `logoutUrl` to match OIDC naming (`endSessionEndpoint`).\n * @example 'https://accounts.example.com/logout'\n */\n endSessionEndpoint?: string;\n /**\n * OIDC post logout redirect URL (sent as `post_logout_redirect_uri` when building the end-session URL).\n * @example 'myapp://logout/callback'\n */\n postLogoutRedirectUrl?: string;\n /**\n * Additional parameters to include in logout / end-session URL.\n */\n additionalLogoutParameters?: Record<string, string>;\n /**\n * iOS-only: Whether to prefer an ephemeral browser session for ASWebAuthenticationSession.\n * Defaults to true to match existing behavior in this plugin.\n */\n iosPrefersEphemeralWebBrowserSession?: boolean;\n /**\n * Alias for `iosPrefersEphemeralWebBrowserSession` (to match Capawesome OAuth naming).\n */\n iosPrefersEphemeralSession?: boolean;\n /**\n * Enable debug logging\n * @default false\n */\n logsEnabled?: boolean;\n}\n\nexport interface InitializeOptions {\n /**\n * OAuth2 provider configurations.\n * Supports multiple providers by using a Record with provider IDs as keys.\n * @example\n * {\n * github: { appId: '...', authorizationBaseUrl: 'https://github.com/login/oauth/authorize', ... },\n * azure: { appId: '...', authorizationBaseUrl: 'https://login.microsoftonline.com/.../oauth2/v2.0/authorize', ... }\n * }\n */\n oauth2?: Record<string, OAuth2ProviderConfig>;\n twitter?: {\n /**\n * The OAuth 2.0 client identifier issued by X (Twitter) Developer Portal\n * @example 'Y2xpZW50SWQ'\n */\n clientId: string;\n /**\n * Redirect URL that is registered inside the X Developer Portal.\n * The plugin uses this URL on every platform to receive the authorization code.\n * @example 'myapp://auth/x'\n */\n redirectUrl: string;\n /**\n * Default scopes appended to every login request when no custom scopes are provided.\n * @description Defaults to the minimum required scopes for Log in with X.\n * @default ['tweet.read','users.read']\n */\n defaultScopes?: string[];\n /**\n * Force the consent screen to show on every login attempt.\n * Mirrors X's `force_login=true` flag.\n * @default false\n */\n forceLogin?: boolean;\n /**\n * Optional audience value when your application has been approved for multi-tenant access.\n */\n audience?: string;\n };\n facebook?: {\n /**\n * Facebook App ID, provided by Facebook for web, in mobile it's set in the native files\n * @description For business integrations, use your Business App ID from Facebook Developer Console.\n * Business apps can access additional permissions like Instagram API, Pages API, and business management features.\n * @see docs/facebook_business_login.md for business app setup guide\n */\n appId: string;\n /**\n * Facebook Client Token, provided by Facebook for web, in mobile it's set in the native files\n */\n clientToken?: string;\n /**\n * Locale\n * @description The locale to use for the Facebook SDK (e.g., 'en_US', 'fr_FR', 'es_ES')\n * @default 'en_US'\n * @example 'fr_FR'\n */\n locale?: string;\n };\n\n google?: {\n /**\n * The app's client ID, found and created in the Google Developers Console.\n * Required for iOS platform.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSClientId?: string;\n /**\n * The app's server client ID, required for offline mode on iOS.\n * Should be the same value as webClientId.\n * Found and created in the Google Developers Console.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSServerClientId?: string;\n /**\n * The app's web client ID, found and created in the Google Developers Console.\n * Required for Android and Web platforms.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n webClientId?: string;\n /**\n * The login mode, can be online or offline.\n *\n * **Online mode (default):**\n * - Returns user profile data and access tokens\n * - Supports all methods: login, logout, isLoggedIn, getAuthorizationCode\n *\n * **Offline mode:**\n * - Returns only serverAuthCode for backend authentication\n * - No user profile data available\n * - **Limitations:** The following methods are NOT supported in offline mode:\n * - `logout()` - Will reject with \"not implemented when using offline mode\"\n * - `isLoggedIn()` - Will reject with \"not implemented when using offline mode\"\n * - `getAuthorizationCode()` - Will reject with \"not implemented when using offline mode\"\n * - `refresh()` - Will reject because offline mode only returns `serverAuthCode`; token refresh must happen on your backend\n * - Only `login()` method works in offline mode, returning serverAuthCode only\n * - `serverAuthCode` must be exchanged on your backend for access/refresh tokens\n * - Requires `iOSServerClientId` to be set on iOS\n *\n * @example 'offline'\n * @default 'online'\n * @since 3.1.0\n */\n mode?: 'online' | 'offline';\n /**\n * Filter visible accounts by hosted domain\n * @description filter visible accounts by hosted domain\n */\n hostedDomain?: string;\n /**\n * Google Redirect URL, should be your backend url that is configured in your google app\n */\n redirectUrl?: string;\n };\n apple?: {\n /**\n * Apple Client ID, provided by Apple for web and Android\n */\n clientId?: string;\n /**\n * Apple Redirect URL, should be your backend url that is configured in your apple app\n *\n * **Note**: Use empty string `''` for iOS to prevent redirect.\n * **Note**: Not required when using Broadcast Channel mode on Android.\n */\n redirectUrl?: string;\n /**\n * Use proper token exchange for Apple Sign-In\n * @description Controls how Apple Sign-In tokens are handled and what gets returned:\n *\n * **When `true` (Recommended for new implementations):**\n * - Exchanges authorization code for proper access tokens via Apple's token endpoint\n * - `idToken`: JWT containing user identity information (email, name, user ID)\n * - `accessToken.token`: Proper access token from Apple (short-lived, ~1 hour)\n * - `authorizationCode`: Raw authorization code for backend token exchange\n *\n * **When `false` (Default - Legacy mode):**\n * - Uses authorization code directly as access token for backward compatibility\n * - `idToken`: JWT containing user identity information (email, name, user ID)\n * - `accessToken.token`: The authorization code itself (not a real access token)\n * - `authorizationCode`: undefined\n *\n * @default false\n * @example\n * // Enable proper token exchange (recommended)\n * useProperTokenExchange: true\n * // Result: idToken=JWT, accessToken=real_token, authorizationCode=present\n *\n * // Legacy mode (backward compatibility)\n * useProperTokenExchange: false\n * // Result: idToken=JWT, accessToken=auth_code, authorizationCode=undefined\n */\n useProperTokenExchange?: boolean;\n /**\n * Use Broadcast Channel for Android Apple Sign-In (Recommended)\n * @description When enabled, Android uses Broadcast Channel API instead of URL redirects.\n * This eliminates the need for redirect URL configuration and server-side setup.\n *\n * **Benefits:**\n * - No redirect URL configuration required\n * - No backend server needed for Android\n * - Simpler setup and more reliable communication\n * - Direct client-server communication via Broadcast Channel\n *\n * **When `true`:**\n * - Uses Broadcast Channel for authentication flow\n * - `redirectUrl` is ignored\n * - Requires Broadcast Channel compatible backend or direct token handling\n *\n * **When `false` (Default - Legacy mode):**\n * - Uses traditional URL redirect flow\n * - Requires `redirectUrl` configuration\n * - Requires backend server for token exchange\n *\n * @default false\n * @since 7.10.0\n * @example\n * // Enable Broadcast Channel mode (recommended for new Android implementations)\n * useBroadcastChannel: true\n * // Result: Simplified setup, no redirect URL needed\n *\n * // Legacy mode (backward compatibility)\n * useBroadcastChannel: false\n * // Result: Traditional URL redirect flow with server-side setup\n */\n useBroadcastChannel?: boolean;\n };\n}\n\nexport interface FacebookLoginOptions {\n /**\n * Permissions\n * @description Select permissions to login with. Supports both consumer and business permissions.\n *\n * **Consumer Permissions:**\n * - `email` - User's email address\n * - `public_profile` - User's public profile info\n * - `user_friends` - List of friends who also use your app\n *\n * **Business Permissions** (require business app configuration and may need App Review):\n * - `instagram_basic` - Instagram Basic Display API access\n * - `instagram_manage_insights` - Instagram Insights data\n * - `instagram_manage_comments` - Manage Instagram comments\n * - `instagram_content_publish` - Publish to Instagram\n * - `pages_show_list` - List of Pages managed by user\n * - `pages_read_engagement` - Read Page engagement metrics\n * - `pages_manage_posts` - Manage Page posts\n * - `pages_messaging` - Page messaging features\n * - `business_management` - Manage business assets\n * - `catalog_management` - Manage product catalogs\n * - `ads_management` - Manage advertising accounts\n *\n * @example ['email', 'public_profile'] // Consumer permissions\n * @example ['email', 'instagram_basic', 'pages_show_list'] // Business permissions\n * @see https://developers.facebook.com/docs/permissions/reference\n * @see docs/facebook_business_login.md for complete business integration guide\n */\n permissions: string[];\n /**\n * Is Limited Login\n * @description use limited login for Facebook iOS only. Important: This is iOS-only and doesn't affect Android.\n * Even if set to false, Facebook will automatically force it to true if App Tracking Transparency (ATT) permission is not granted.\n * Developers should always be prepared to handle both limited and full login scenarios.\n * @default false\n */\n limitedLogin?: boolean;\n /**\n * Nonce\n * @description A custom nonce to use for the login request\n */\n nonce?: string;\n}\n\nexport interface TwitterLoginOptions {\n /**\n * Additional scopes to request during login.\n * If omitted the plugin falls back to the default scopes configured during initialization.\n * @example ['tweet.read','users.read','offline.access']\n */\n scopes?: string[];\n /**\n * Provide a custom OAuth state value.\n * When not provided the plugin generates a cryptographically random value.\n */\n state?: string;\n /**\n * Provide a pre-computed PKCE code verifier (mostly used for testing).\n * When omitted the plugin generates a secure verifier automatically.\n */\n codeVerifier?: string;\n /**\n * Override the redirect URI for a single login call.\n * Useful when the same app supports multiple callback URLs per platform.\n */\n redirectUrl?: string;\n /**\n * Force the consent screen on every attempt, maps to `force_login=true`.\n */\n forceLogin?: boolean;\n}\n\nexport interface OAuth2LoginOptions {\n /**\n * The provider ID as configured in initialize()\n * This is required to identify which OAuth2 provider to use\n * @example 'github', 'azure', 'keycloak'\n */\n providerId: string;\n /**\n * Override the scopes for this login request\n * If not provided, uses the scopes from initialization\n */\n scope?: string | string[];\n /**\n * Alias for `scope` using common naming (`scopes`).\n * If both are provided, `scope` takes precedence.\n */\n scopes?: string[];\n /**\n * Custom state parameter for CSRF protection\n * If not provided, a random value is generated\n */\n state?: string;\n /**\n * Override PKCE code verifier (for testing purposes)\n * If not provided, a secure random verifier is generated\n */\n codeVerifier?: string;\n /**\n * Override redirect URL for this login request\n */\n redirectUrl?: string;\n /**\n * Additional parameters to add to the authorization URL\n */\n additionalParameters?: Record<string, string>;\n /**\n * Convenience option for OIDC `login_hint`.\n * Equivalent to passing `additionalParameters.login_hint`.\n */\n loginHint?: string;\n /**\n * Convenience option for OAuth/OIDC `prompt`.\n * Equivalent to passing `additionalParameters.prompt`.\n */\n prompt?: string;\n /**\n * Web-only (`oauth2` provider only): Use a full-page redirect instead of a popup window.\n *\n * When using `redirect`, the promise returned by `login()` will not resolve because the page navigates away.\n * After the redirect lands back in your app, call `SocialLogin.handleRedirectCallback()` on that page to\n * parse the result.\n *\n * @default 'popup'\n */\n flow?: 'popup' | 'redirect';\n}\n\nexport interface OAuth2LoginResponse {\n /**\n * The provider ID that was used for this login\n */\n providerId: string;\n /**\n * The access token received from the OAuth provider\n */\n accessToken: AccessToken | null;\n /**\n * The ID token (JWT) if provided by the OAuth server (e.g., OpenID Connect)\n */\n idToken: string | null;\n /**\n * The refresh token if provided (requires appropriate scope like offline_access)\n */\n refreshToken: string | null;\n /**\n * Resource data fetched from resourceUrl if configured\n * Contains the raw JSON response from the resource endpoint\n */\n resourceData: Record<string, unknown> | null;\n /**\n * The scopes that were granted\n */\n scope: string[];\n /**\n * Token type (usually 'bearer')\n */\n tokenType: string;\n /**\n * Token expiration time in seconds\n */\n expiresIn: number | null;\n}\n\nexport interface GoogleLoginOptions {\n /**\n * Specifies the scopes required for accessing Google APIs\n * The default is defined in the configuration.\n * @example [\"profile\", \"email\"]\n * @see [Google OAuth2 Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * Force refresh token (only for Android)\n * @description force refresh token\n * @default false\n * @note On Android, the OS caches access tokens, and if a token is invalid (e.g., user revoked app access), the plugin might return an invalid accessToken. Using getAuthorizationCode() is recommended to ensure the token is valid.\n */\n forceRefreshToken?: boolean;\n /**\n * Force account selection prompt (iOS)\n * @description forces the account selection prompt to appear on iOS\n * @default false\n */\n forcePrompt?: boolean;\n /**\n * Style\n * @description style\n * @default 'standard'\n */\n style?: 'bottom' | 'standard';\n /**\n * Filter by authorized accounts (Android only)\n * @description Only show accounts that have previously been used to sign in to the app.\n * This option is only available for the 'bottom' style.\n * Note: For Family Link supervised accounts, this should be set to false.\n * @default true\n */\n filterByAuthorizedAccounts?: boolean;\n /**\n * Auto select enabled (Android only)\n * @description Automatically select the account if only one Google account is available.\n * This option is only available for the 'bottom' style.\n * @default false\n */\n autoSelectEnabled?: boolean;\n /**\n * Prompt parameter for Google OAuth (Web only)\n * @description A space-delimited, case-sensitive list of prompts to present the user.\n * If you don't specify this parameter, the user will be prompted only the first time your project requests access.\n *\n * **Possible values:**\n * - `none`: Don't display any authentication or consent screens. Must not be specified with other values.\n * - `consent`: Prompt the user for consent.\n * - `select_account`: Prompt the user to select an account.\n *\n * **Examples:**\n * - `prompt: 'consent'` - Always show consent screen\n * - `prompt: 'select_account'` - Always show account selection\n * - `prompt: 'consent select_account'` - Show both consent and account selection\n *\n * **Note:** This parameter only affects web platform behavior. Mobile platforms use their own native prompts.\n *\n * @example 'consent'\n * @example 'select_account'\n * @example 'consent select_account'\n * @see [Google OAuth2 Prompt Parameter](https://developers.google.com/identity/protocols/oauth2/openid-connect#prompt)\n * @since 7.12.0\n */\n prompt?: 'none' | 'consent' | 'select_account' | 'consent select_account' | 'select_account consent';\n}\n\nexport interface GoogleLoginResponseOnline {\n accessToken: AccessToken | null;\n idToken: string | null;\n profile: {\n email: string | null;\n familyName: string | null;\n givenName: string | null;\n id: string | null;\n name: string | null;\n imageUrl: string | null;\n };\n responseType: 'online';\n}\n\nexport interface GoogleLoginResponseOffline {\n serverAuthCode: string;\n responseType: 'offline';\n}\n\nexport type GoogleLoginResponse = GoogleLoginResponseOnline | GoogleLoginResponseOffline;\n\nexport interface AppleProviderOptions {\n /**\n * Scopes\n * @description An array of scopes to request during login\n * @example [\"name\", \"email\"]\n * default: [\"name\", \"email\"]\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * State\n * @description state\n */\n state?: string;\n /**\n * Use Broadcast Channel for authentication flow\n * @description When enabled, uses Broadcast Channel API for communication instead of URL redirects.\n * Only applicable on platforms that support Broadcast Channel (Android).\n * @default false\n */\n useBroadcastChannel?: boolean;\n}\n\nexport interface AppleProviderResponse {\n /**\n * Access token from Apple\n * @description 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)\n * Use `idToken` for user authentication, `accessToken` for API calls when properly exchanged.\n */\n accessToken: AccessToken | null;\n\n /**\n * Identity token (JWT) from Apple\n * @description 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\n * This is the primary token for user authentication and should be verified on your backend.\n */\n idToken: string | null;\n\n /**\n * User profile information\n * @description 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)\n */\n profile: {\n user: string;\n email: string | null;\n givenName: string | null;\n familyName: string | null;\n };\n\n /**\n * Authorization code for proper token exchange (when useProperTokenExchange is enabled)\n * @description Only present when `useProperTokenExchange` is `true`. This code should be exchanged\n * for proper access tokens on your backend using Apple's token endpoint. Use this for secure\n * server-side token validation and to obtain refresh tokens.\n * @see https://developer.apple.com/documentation/sign_in_with_apple/tokenresponse\n */\n authorizationCode?: string;\n}\n\nexport type LoginOptions =\n | {\n provider: 'facebook';\n options: FacebookLoginOptions;\n }\n | {\n provider: 'google';\n options: GoogleLoginOptions;\n }\n | {\n provider: 'apple';\n options: AppleProviderOptions;\n }\n | {\n provider: 'twitter';\n options: TwitterLoginOptions;\n }\n | {\n provider: 'oauth2';\n options: OAuth2LoginOptions;\n };\n\nexport type LoginResult =\n | {\n provider: 'facebook';\n result: FacebookLoginResponse;\n }\n | {\n provider: 'google';\n result: GoogleLoginResponse;\n }\n | {\n provider: 'apple';\n result: AppleProviderResponse;\n }\n | {\n provider: 'twitter';\n result: TwitterLoginResponse;\n }\n | {\n provider: 'oauth2';\n result: OAuth2LoginResponse;\n };\n\nexport interface AccessToken {\n applicationId?: string;\n declinedPermissions?: string[];\n expires?: string;\n isExpired?: boolean;\n lastRefresh?: string;\n permissions?: string[];\n token: string;\n tokenType?: string;\n refreshToken?: string;\n userId?: string;\n}\n\nexport interface FacebookLoginResponse {\n accessToken: AccessToken | null;\n idToken: string | null;\n profile: {\n userID: string;\n email: string | null;\n friendIDs: string[];\n birthday: string | null;\n ageRange: { min?: number; max?: number } | null;\n gender: string | null;\n location: { id: string; name: string } | null;\n hometown: { id: string; name: string } | null;\n profileURL: string | null;\n name: string | null;\n imageURL: string | null;\n };\n}\n\nexport interface TwitterProfile {\n id: string;\n username: string;\n name: string | null;\n profileImageUrl: string | null;\n verified: boolean;\n email?: string | null;\n}\n\nexport interface TwitterLoginResponse {\n accessToken: AccessToken | null;\n refreshToken?: string | null;\n scope: string[];\n tokenType: 'bearer';\n expiresIn?: number | null;\n profile: TwitterProfile;\n}\n\nexport interface AuthorizationCode {\n /**\n * Jwt\n * @description A JSON web token\n */\n jwt?: string;\n /**\n * Access Token\n * @description An access token\n */\n accessToken?: string;\n}\n\nexport interface AuthorizationCodeOptions {\n /**\n * Provider\n * @description Provider for the authorization code\n */\n provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2';\n /**\n * Provider ID for OAuth2 providers (required when provider is 'oauth2')\n * @description The ID used when configuring the OAuth2 provider in initialize()\n */\n providerId?: string;\n}\n\nexport interface isLoggedInOptions {\n /**\n * Provider\n * @description Provider for the isLoggedIn\n */\n provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2';\n /**\n * Provider ID for OAuth2 providers (required when provider is 'oauth2')\n * @description The ID used when configuring the OAuth2 provider in initialize()\n */\n providerId?: string;\n}\n\n// Define the provider-specific call types\nexport type ProviderSpecificCall = 'facebook#getProfile' | 'facebook#requestTracking';\n\n// Define the options and response types for each specific call\nexport interface FacebookGetProfileOptions {\n /**\n * Fields to retrieve from Facebook profile\n * @example [\"id\", \"name\", \"email\", \"picture\"]\n */\n fields?: string[];\n}\n\nexport interface FacebookGetProfileResponse {\n /**\n * Facebook profile data\n */\n profile: {\n id: string | null;\n name: string | null;\n email: string | null;\n first_name: string | null;\n last_name: string | null;\n picture?: {\n data: {\n height: number | null;\n is_silhouette: boolean | null;\n url: string | null;\n width: number | null;\n };\n } | null;\n [key: string]: any; // For additional fields that might be requested\n };\n}\n\nexport interface OpenSecureWindowOptions {\n /**\n * The endpoint to open\n */\n authEndpoint: string;\n /**\n * The redirect URI to use for the openSecureWindow call.\n * This will be checked to make sure it matches the redirect URI after the window finishes the redirection.\n */\n redirectUri: string;\n /**\n * The name of the broadcast channel to listen to, relevant only for web\n */\n broadcastChannelName?: string;\n}\n\nexport interface OpenSecureWindowResponse {\n /**\n * The result of the openSecureWindow call\n */\n redirectedUri: string;\n}\n\nexport type FacebookRequestTrackingOptions = Record<string, never>;\n\nexport interface FacebookRequestTrackingResponse {\n /**\n * App tracking authorization status\n */\n status: 'authorized' | 'denied' | 'notDetermined' | 'restricted';\n}\n\n// Map call strings to their options and response types\nexport type ProviderSpecificCallOptionsMap = {\n 'facebook#getProfile': FacebookGetProfileOptions;\n 'facebook#requestTracking': FacebookRequestTrackingOptions;\n};\n\nexport type ProviderSpecificCallResponseMap = {\n 'facebook#getProfile': FacebookGetProfileResponse;\n 'facebook#requestTracking': FacebookRequestTrackingResponse;\n};\n\n// Add a helper type to map providers to their response types\nexport type ProviderResponseMap = {\n facebook: FacebookLoginResponse;\n google: GoogleLoginResponse;\n apple: AppleProviderResponse;\n twitter: TwitterLoginResponse;\n oauth2: OAuth2LoginResponse;\n};\n\nexport interface SocialLoginPlugin {\n /**\n * Initialize the plugin\n * @description initialize the plugin with the required options\n */\n initialize(options: InitializeOptions): Promise<void>;\n /**\n * Login with the selected provider\n * @description login with the selected provider\n */\n login<T extends LoginOptions['provider']>(\n options: Extract<LoginOptions, { provider: T }>,\n ): Promise<{ provider: T; result: ProviderResponseMap[T] }>;\n /**\n * Logout\n * @description Logout the user from the specified provider\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * It will reject with error: \"logout is not implemented when using offline mode\"\n *\n * @throws Error if Google provider is in offline mode\n */\n logout(options: {\n provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2';\n providerId?: string;\n }): Promise<void>;\n /**\n * IsLoggedIn\n * @description Check if the user is currently logged in with the specified provider\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * It will reject with error: \"isLoggedIn is not implemented when using offline mode\"\n *\n * @throws Error if Google provider is in offline mode\n */\n isLoggedIn(options: isLoggedInOptions): Promise<{ isLoggedIn: boolean }>;\n\n /**\n * Get the current authorization code\n * @description Get the authorization code for server-side authentication\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * It will reject with error: \"getAuthorizationCode is not implemented when using offline mode\"\n *\n * In offline mode, the authorization code (serverAuthCode) is already returned by the `login()` method.\n *\n * @throws Error if Google provider is in offline mode\n */\n getAuthorizationCode(options: AuthorizationCodeOptions): Promise<AuthorizationCode>;\n /**\n * Refresh the access token\n * @description refresh the access token\n *\n * **Google Offline Mode Limitation:**\n * This method is NOT supported when Google is initialized with `mode: 'offline'`.\n * Offline mode only returns `serverAuthCode` for backend token exchange, so token refresh must happen on your backend.\n * The plugin logs and rejects with a message explaining that you should send `serverAuthCode` to your backend,\n * refresh the Google tokens there, or switch to `mode: 'online'` for client-side refresh.\n *\n * **Google Web Limitation:**\n * On Web, Google `refresh()` is not implemented, even when using `mode: 'online'`.\n * Call `login()` again on Web to obtain a fresh token instead.\n *\n * @throws Error if Google provider is in offline mode, or on Web where Google `refresh()` is not implemented\n */\n refresh(options: LoginOptions): Promise<void>;\n\n /**\n * OAuth2 refresh-token helper (feature parity with Capawesome OAuth).\n *\n * Scope:\n * - Only applies to the built-in `oauth2` provider (not Google/Apple/Facebook/Twitter).\n * - Requires a token endpoint (either `accessTokenEndpoint`/`tokenEndpoint` or `issuerUrl` discovery).\n *\n * Security note:\n * - This does not validate JWT signatures. It only exchanges/refreshes tokens.\n *\n * If `refreshToken` is omitted, the plugin will attempt to use the stored refresh token (if available).\n */\n refreshToken(options: {\n provider: 'oauth2';\n providerId: string;\n refreshToken?: string;\n additionalParameters?: Record<string, string>;\n }): Promise<OAuth2LoginResponse>;\n\n /**\n * Web-only: handle the OAuth redirect callback and return the parsed result.\n *\n * Notes:\n * - This is only meaningful on Web. iOS/Android implementations will reject.\n * - Intended for redirect-based flows (e.g. `oauth2` with `flow: 'redirect'`) where the page navigates away.\n */\n handleRedirectCallback(): Promise<LoginResult | null>;\n\n /**\n * Decode a JWT (typically an OIDC ID token) into its claims.\n *\n * Notes:\n * - Accepts both `idToken` and `token` to match common naming (Capawesome uses `token`).\n * - This does not validate the signature or issuer/audience. It only base64url-decodes the payload.\n */\n decodeIdToken(options: { idToken?: string; token?: string }): Promise<{ claims: Record<string, any> }>;\n\n /**\n * Convert an access token expiration timestamp (milliseconds since epoch) to an ISO date string.\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n getAccessTokenExpirationDate(options: {\n /**\n * Access token expiration date in milliseconds since epoch.\n * Typically: `Date.now() + expiresInSeconds * 1000`.\n */\n accessTokenExpirationDate: number;\n }): Promise<{ date: string }>;\n\n /**\n * Check if an access token is available (non-empty).\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n isAccessTokenAvailable(options: { accessToken: string | null }): Promise<{ isAvailable: boolean }>;\n\n /**\n * Check if an access token is expired.\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n isAccessTokenExpired(options: { accessTokenExpirationDate: number }): Promise<{ isExpired: boolean }>;\n\n /**\n * Check if a refresh token is available (non-empty).\n *\n * This is a pure helper (feature parity with Capawesome OAuth) and does not depend on provider state.\n */\n isRefreshTokenAvailable(options: { refreshToken: string | null }): Promise<{ isAvailable: boolean }>;\n\n /**\n * Execute provider-specific calls\n * @description Execute a provider-specific functionality\n */\n providerSpecificCall<T extends ProviderSpecificCall>(options: {\n call: T;\n options: ProviderSpecificCallOptionsMap[T];\n }): Promise<ProviderSpecificCallResponseMap[T]>;\n\n /**\n * Get the native Capacitor plugin version\n *\n * @returns {Promise<{ id: string }>} an Promise with version for this device\n * @throws An error if the something went wrong\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n /**\n * Opens a secured window for OAuth2 authentication.\n * For web, you should have the code in the redirected page to use a broadcast channel to send the redirected url to the app\n * Something like:\n * ```html\n * <html>\n * <head></head>\n * <body>\n * <script>\n * const searchParams = new URLSearchParams(location.search)\n * if (searchParams.has(\"code\")) {\n * new BroadcastChannel(\"my-channel-name\").postMessage(location.href);\n * window.close();\n * }\n * </script>\n * </body>\n * </html>\n * ```\n * For mobile, you should have a redirect uri that opens the app, something like: `myapp://oauth_callback/`\n * And make sure to register it in the app's info.plist:\n * ```xml\n * <key>CFBundleURLTypes</key>\n * <array>\n * <dict>\n * <key>CFBundleURLSchemes</key>\n * <array>\n * <string>myapp</string>\n * </array>\n * </dict>\n * </array>\n * ```\n * And in the AndroidManifest.xml file:\n * ```xml\n * <activity>\n * <intent-filter>\n * <action android:name=\"android.intent.action.VIEW\" />\n * <category android:name=\"android.intent.category.DEFAULT\" />\n * <category android:name=\"android.intent.category.BROWSABLE\" />\n * <data android:host=\"oauth_callback\" android:scheme=\"myapp\" />\n * </intent-filter>\n * </activity>\n * ```\n * @param options - the options for the openSecureWindow call\n */\n openSecureWindow(options: OpenSecureWindowOptions): Promise<OpenSecureWindowResponse>;\n}\n"]}
|
|
@@ -323,13 +323,21 @@ export class GoogleSocialLogin extends BaseSocialLogin {
|
|
|
323
323
|
reject(new Error('Failed to open popup'));
|
|
324
324
|
return;
|
|
325
325
|
}
|
|
326
|
-
const cleanup = () => {
|
|
326
|
+
const cleanup = (shouldClose = false) => {
|
|
327
327
|
window.removeEventListener('message', handleMessage);
|
|
328
328
|
clearInterval(popupClosedInterval);
|
|
329
329
|
clearTimeout(timeoutHandle);
|
|
330
330
|
if (broadcastChannel) {
|
|
331
331
|
broadcastChannel.close();
|
|
332
332
|
}
|
|
333
|
+
if (shouldClose) {
|
|
334
|
+
try {
|
|
335
|
+
popup.close();
|
|
336
|
+
}
|
|
337
|
+
catch (_a) {
|
|
338
|
+
// Ignore cross-origin errors when closing the popup
|
|
339
|
+
}
|
|
340
|
+
}
|
|
333
341
|
};
|
|
334
342
|
const processOAuthResponse = (data) => {
|
|
335
343
|
if (this.loginType === 'online') {
|
|
@@ -380,11 +388,11 @@ export class GoogleSocialLogin extends BaseSocialLogin {
|
|
|
380
388
|
if (event.origin !== window.location.origin || ((_b = (_a = event.data) === null || _a === void 0 ? void 0 : _a.source) === null || _b === void 0 ? void 0 : _b.startsWith('angular')))
|
|
381
389
|
return;
|
|
382
390
|
if (((_c = event.data) === null || _c === void 0 ? void 0 : _c.type) === 'oauth-response') {
|
|
383
|
-
cleanup();
|
|
391
|
+
cleanup(true);
|
|
384
392
|
processOAuthResponse(event.data);
|
|
385
393
|
}
|
|
386
394
|
else if (((_d = event.data) === null || _d === void 0 ? void 0 : _d.type) === 'oauth-error') {
|
|
387
|
-
cleanup();
|
|
395
|
+
cleanup(true);
|
|
388
396
|
const errorMessage = event.data.error || 'User cancelled the OAuth flow';
|
|
389
397
|
reject(new Error(errorMessage));
|
|
390
398
|
}
|
|
@@ -398,11 +406,11 @@ export class GoogleSocialLogin extends BaseSocialLogin {
|
|
|
398
406
|
if ((_a = data === null || data === void 0 ? void 0 : data.source) === null || _a === void 0 ? void 0 : _a.toString().startsWith('angular'))
|
|
399
407
|
return;
|
|
400
408
|
if ((data === null || data === void 0 ? void 0 : data.type) === 'oauth-response') {
|
|
401
|
-
cleanup();
|
|
409
|
+
cleanup(true);
|
|
402
410
|
processOAuthResponse(data);
|
|
403
411
|
}
|
|
404
412
|
else if ((data === null || data === void 0 ? void 0 : data.type) === 'oauth-error') {
|
|
405
|
-
cleanup();
|
|
413
|
+
cleanup(true);
|
|
406
414
|
const errorMessage = data.error || 'User cancelled the OAuth flow';
|
|
407
415
|
reject(new Error(errorMessage));
|
|
408
416
|
}
|
|
@@ -411,13 +419,7 @@ export class GoogleSocialLogin extends BaseSocialLogin {
|
|
|
411
419
|
window.addEventListener('message', handleMessage);
|
|
412
420
|
// Timeout after 5 minutes
|
|
413
421
|
timeoutHandle = setTimeout(() => {
|
|
414
|
-
cleanup();
|
|
415
|
-
try {
|
|
416
|
-
popup.close();
|
|
417
|
-
}
|
|
418
|
-
catch (_a) {
|
|
419
|
-
// Ignore cross-origin errors when closing
|
|
420
|
-
}
|
|
422
|
+
cleanup(true);
|
|
421
423
|
reject(new Error('OAuth timeout'));
|
|
422
424
|
}, 300000);
|
|
423
425
|
popupClosedInterval = setInterval(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-provider.js","sourceRoot":"","sources":["../../src/google-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseSocialLogin } from './base';\nimport type { GoogleLoginOptions, LoginResult, ProviderResponseMap, AuthorizationCode } from './definitions';\n\nconst GOOGLE_OFFLINE_REFRESH_MESSAGE =\n \"Google refresh() is not available when using offline mode. Offline mode only returns serverAuthCode for backend token exchange. Send serverAuthCode to your backend and refresh tokens there, or switch google.mode to 'online' for client-side refresh.\";\n\nexport class GoogleSocialLogin extends BaseSocialLogin {\n private clientId: string | null = null;\n private hostedDomain?: string;\n private loginType: 'online' | 'offline' = 'online';\n private redirectUrl?: string;\n private GOOGLE_TOKEN_REQUEST_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo';\n private readonly GOOGLE_STATE_KEY = 'capgo_social_login_google_state';\n\n async initialize(\n clientId: string | null,\n mode?: 'online' | 'offline',\n hostedDomain?: string | null,\n redirectUrl?: string,\n ): Promise<void> {\n this.clientId = clientId;\n if (mode) {\n this.loginType = mode;\n }\n this.hostedDomain = hostedDomain as string | undefined;\n this.redirectUrl = redirectUrl;\n }\n\n async login<T extends 'google'>(\n options: GoogleLoginOptions,\n ): Promise<{ provider: T; result: ProviderResponseMap[T] }> {\n if (!this.clientId) {\n throw new Error('Google Client ID not set. Call initialize() first.');\n }\n\n let scopes = options.scopes || [];\n\n if (scopes.length > 0) {\n // If scopes are provided, directly use the traditional OAuth flow\n if (!scopes.includes('https://www.googleapis.com/auth/userinfo.email')) {\n scopes.push('https://www.googleapis.com/auth/userinfo.email');\n }\n if (!scopes.includes('https://www.googleapis.com/auth/userinfo.profile')) {\n scopes.push('https://www.googleapis.com/auth/userinfo.profile');\n }\n if (!scopes.includes('openid')) {\n scopes.push('openid');\n }\n } else {\n scopes = [\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'openid',\n ];\n }\n\n const nonce = options.nonce || Math.random().toString(36).substring(2);\n\n // If scopes are provided, directly use the traditional OAuth flow\n return this.traditionalOAuth({\n scopes,\n nonce,\n hostedDomain: this.hostedDomain,\n prompt: options.prompt,\n });\n }\n\n async logout(): Promise<void> {\n if (this.loginType === 'offline') {\n return Promise.reject(\"Offline login doesn't store tokens. logout is not available\");\n }\n // eslint-disable-next-line\n const state = this.getGoogleState();\n if (!state) return;\n await this.rawLogoutGoogle(state.accessToken);\n }\n\n async isLoggedIn(): Promise<{ isLoggedIn: boolean }> {\n if (this.loginType === 'offline') {\n return Promise.reject(\"Offline login doesn't store tokens. isLoggedIn is not available\");\n }\n // eslint-disable-next-line\n const state = this.getGoogleState();\n if (!state) return { isLoggedIn: false };\n\n try {\n const isValidAccessToken = await this.accessTokenIsValid(state.accessToken);\n const isValidIdToken = this.idTokenValid(state.idToken);\n if (isValidAccessToken && isValidIdToken) {\n return { isLoggedIn: true };\n } else {\n try {\n await this.rawLogoutGoogle(state.accessToken, false);\n } catch (e) {\n console.error('Access token is not valid, but cannot logout', e);\n }\n return { isLoggedIn: false };\n }\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n async getAuthorizationCode(): Promise<AuthorizationCode> {\n if (this.loginType === 'offline') {\n return Promise.reject(\"Offline login doesn't store tokens. getAuthorizationCode is not available\");\n }\n // eslint-disable-next-line\n const state = this.getGoogleState();\n if (!state) throw new Error('No Google authorization code available');\n\n try {\n const isValidAccessToken = await this.accessTokenIsValid(state.accessToken);\n const isValidIdToken = this.idTokenValid(state.idToken);\n if (isValidAccessToken && isValidIdToken) {\n return { accessToken: state.accessToken, jwt: state.idToken };\n } else {\n try {\n await this.rawLogoutGoogle(state.accessToken, false);\n } catch (e) {\n console.error('Access token is not valid, but cannot logout', e);\n }\n throw new Error('No Google authorization code available');\n }\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n async refresh(): Promise<void> {\n if (this.loginType === 'offline') {\n console.warn(`[SocialLogin] ${GOOGLE_OFFLINE_REFRESH_MESSAGE}`);\n return Promise.reject(new Error(GOOGLE_OFFLINE_REFRESH_MESSAGE));\n }\n return Promise.reject(\n new Error('Google refresh is not implemented on web. Use login() again to obtain a new token.'),\n );\n }\n\n handleOAuthRedirect(url: URL): LoginResult | { error: string } | null {\n const paramsRaw = url.searchParams;\n\n // Check for errors in search params first (for offline mode)\n const errorInParams = paramsRaw.get('error');\n if (errorInParams) {\n localStorage.removeItem(BaseSocialLogin.OAUTH_STATE_KEY);\n const errorDescription = paramsRaw.get('error_description') || errorInParams;\n return { error: errorDescription };\n }\n\n const code = paramsRaw.get('code');\n\n if (code && paramsRaw.has('scope')) {\n return {\n provider: 'google',\n result: {\n serverAuthCode: code,\n responseType: 'offline',\n },\n };\n }\n\n const hash = url.hash.substring(1);\n console.log('handleOAuthRedirect', url.hash);\n\n if (!hash) return null;\n\n const params = new URLSearchParams(hash);\n\n // Check for error cases in hash (e.g., user cancelled)\n const error = params.get('error');\n if (error) {\n localStorage.removeItem(BaseSocialLogin.OAUTH_STATE_KEY);\n const errorDescription = params.get('error_description') || error;\n return { error: errorDescription };\n }\n\n console.log('handleOAuthRedirect ok');\n\n const accessToken = params.get('access_token');\n const idToken = params.get('id_token');\n\n if (accessToken && idToken) {\n localStorage.removeItem(BaseSocialLogin.OAUTH_STATE_KEY);\n const profile = this.parseJwt(idToken);\n return {\n provider: 'google',\n result: {\n accessToken: {\n token: accessToken,\n },\n idToken,\n profile: {\n email: profile.email || null,\n familyName: profile.family_name || null,\n givenName: profile.given_name || null,\n id: profile.sub || null,\n name: profile.name || null,\n imageUrl: profile.picture || null,\n },\n responseType: 'online',\n },\n };\n }\n return null;\n }\n\n private async accessTokenIsValid(accessToken: string): Promise<boolean> {\n const url = `${this.GOOGLE_TOKEN_REQUEST_URL}?access_token=${encodeURIComponent(accessToken)}`;\n\n try {\n // Make the GET request using fetch\n const response = await fetch(url);\n\n // Check if the response is successful\n if (!response.ok) {\n console.log(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response not successful. Status code: ${response.status}. Assuming that the token is not valid`,\n );\n return false;\n }\n\n // Get the response body as text\n const responseBody = await response.text();\n\n if (!responseBody) {\n console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`);\n throw new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`);\n }\n\n // Parse the response body as JSON\n let jsonObject: any;\n try {\n jsonObject = JSON.parse(responseBody);\n } catch (e) {\n console.error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${e}`,\n );\n throw new Error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${e}`,\n );\n }\n\n // Extract the 'expires_in' field\n const expiresInStr = jsonObject['expires_in'];\n\n if (expiresInStr === undefined || expiresInStr === null) {\n console.error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`,\n );\n throw new Error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`,\n );\n }\n\n // Parse 'expires_in' as an integer\n let expiresInInt: number;\n try {\n expiresInInt = parseInt(expiresInStr, 10);\n if (isNaN(expiresInInt)) {\n throw new Error(`'expires_in' is not a valid integer`);\n }\n } catch (e) {\n console.error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${expiresInStr} is not a valid integer. Error: ${e}`,\n );\n throw new Error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${expiresInStr} is not a valid integer. Error: ${e}`,\n );\n }\n\n // Determine if the access token is valid based on 'expires_in'\n return expiresInInt > 5;\n } catch (error) {\n console.error(error);\n throw error;\n }\n }\n\n private idTokenValid(idToken: string): boolean {\n try {\n const parsed = this.parseJwt(idToken);\n const currentTime = Math.ceil(Date.now() / 1000) + 5; // Convert current time to seconds since epoch\n return parsed.exp && currentTime < parsed.exp;\n } catch (e) {\n return false;\n }\n }\n\n private async rawLogoutGoogle(accessToken: string, tokenValid: boolean | null = null) {\n if (tokenValid === null) {\n tokenValid = await this.accessTokenIsValid(accessToken);\n }\n\n if (tokenValid === true) {\n try {\n await fetch(`https://accounts.google.com/o/oauth2/revoke?token=${encodeURIComponent(accessToken)}`);\n this.clearStateGoogle();\n } catch (e) {\n // ignore\n }\n return;\n } else {\n this.clearStateGoogle();\n return;\n }\n }\n\n private persistStateGoogle(accessToken: string, idToken: string) {\n try {\n window.localStorage.setItem(this.GOOGLE_STATE_KEY, JSON.stringify({ accessToken, idToken }));\n } catch (e) {\n console.error('Cannot persist state google', e);\n }\n }\n\n private clearStateGoogle() {\n try {\n window.localStorage.removeItem(this.GOOGLE_STATE_KEY);\n } catch (e) {\n console.error('Cannot clear state google', e);\n }\n }\n\n private getGoogleState(): { accessToken: string; idToken: string } | null {\n try {\n const state = window.localStorage.getItem(this.GOOGLE_STATE_KEY);\n if (!state) return null;\n const { accessToken, idToken } = JSON.parse(state);\n return { accessToken, idToken };\n } catch (e) {\n console.error('Cannot get state google', e);\n return null;\n }\n }\n\n private async traditionalOAuth<T extends 'google'>({\n scopes,\n hostedDomain,\n nonce,\n prompt,\n }: GoogleLoginOptions & { hostedDomain?: string }): Promise<{ provider: T; result: ProviderResponseMap[T] }> {\n const uniqueScopes = [...new Set([...(scopes || []), 'openid'])];\n\n const params = new URLSearchParams({\n client_id: this.clientId ?? '',\n redirect_uri: this.redirectUrl || window.location.origin + window.location.pathname,\n response_type: this.loginType === 'offline' ? 'code' : 'token id_token',\n scope: uniqueScopes.join(' '),\n ...(nonce && { nonce }),\n include_granted_scopes: 'true',\n state: 'popup',\n });\n if (hostedDomain !== undefined) {\n params.append('hd', hostedDomain);\n }\n if (prompt !== undefined) {\n params.append('prompt', prompt);\n }\n\n const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;\n const width = 500;\n const height = 600;\n const left = window.screenX + (window.outerWidth - width) / 2;\n const top = window.screenY + (window.outerHeight - height) / 2;\n localStorage.setItem(\n BaseSocialLogin.OAUTH_STATE_KEY,\n JSON.stringify({ provider: 'google', loginType: this.loginType, nonce }),\n );\n const popup = window.open(url, 'Google Sign In', `width=${width},height=${height},left=${left},top=${top},popup=1`);\n\n let popupClosedInterval: number;\n let timeoutHandle: number;\n\n // Use BroadcastChannel for cross-origin communication (works when postMessage doesn't)\n const channelName = `google_oauth_${nonce || Date.now()}`;\n let broadcastChannel: BroadcastChannel | null = null;\n\n try {\n broadcastChannel = new BroadcastChannel(channelName);\n } catch {\n // BroadcastChannel not supported, fall back to postMessage only\n }\n\n // This may never return...\n return new Promise((resolve, reject) => {\n if (!popup) {\n reject(new Error('Failed to open popup'));\n return;\n }\n\n const cleanup = () => {\n window.removeEventListener('message', handleMessage);\n clearInterval(popupClosedInterval);\n clearTimeout(timeoutHandle);\n if (broadcastChannel) {\n broadcastChannel.close();\n }\n };\n\n const processOAuthResponse = (data: Record<string, unknown>) => {\n if (this.loginType === 'online') {\n const { accessToken, idToken } = data as { accessToken?: { token: string }; idToken?: string };\n if (accessToken && idToken) {\n const profile = this.parseJwt(idToken);\n this.persistStateGoogle(accessToken.token, idToken);\n resolve({\n provider: 'google' as T,\n result: {\n accessToken: {\n token: accessToken.token,\n },\n idToken,\n profile: {\n email: profile.email || null,\n familyName: profile.family_name || null,\n givenName: profile.given_name || null,\n id: profile.sub || null,\n name: profile.name || null,\n imageUrl: profile.picture || null,\n },\n responseType: 'online',\n },\n });\n } else {\n reject(new Error('Invalid OAuth response: missing accessToken or idToken'));\n }\n } else {\n const { serverAuthCode } = data as { serverAuthCode: string };\n if (!serverAuthCode) {\n reject(new Error('Invalid OAuth response: missing serverAuthCode'));\n return;\n }\n resolve({\n provider: 'google' as T,\n result: {\n responseType: 'offline',\n serverAuthCode,\n },\n });\n }\n };\n\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== window.location.origin || event.data?.source?.startsWith('angular')) return;\n\n if (event.data?.type === 'oauth-response') {\n cleanup();\n processOAuthResponse(event.data);\n } else if (event.data?.type === 'oauth-error') {\n cleanup();\n const errorMessage = event.data.error || 'User cancelled the OAuth flow';\n reject(new Error(errorMessage));\n }\n // Don't reject for non-OAuth messages, just ignore them\n };\n\n // Listen for BroadcastChannel messages\n if (broadcastChannel) {\n broadcastChannel.onmessage = (event: MessageEvent) => {\n const data = event.data;\n if (data?.source?.toString().startsWith('angular')) return;\n\n if (data?.type === 'oauth-response') {\n cleanup();\n processOAuthResponse(data);\n } else if (data?.type === 'oauth-error') {\n cleanup();\n const errorMessage = (data.error as string) || 'User cancelled the OAuth flow';\n reject(new Error(errorMessage));\n }\n };\n }\n\n window.addEventListener('message', handleMessage);\n\n // Timeout after 5 minutes\n timeoutHandle = setTimeout(() => {\n cleanup();\n try {\n popup.close();\n } catch {\n // Ignore cross-origin errors when closing\n }\n reject(new Error('OAuth timeout'));\n }, 300000);\n\n popupClosedInterval = setInterval(() => {\n try {\n // Check if popup is closed - this may throw cross-origin errors for some providers\n if (popup.closed) {\n cleanup();\n reject(new Error('Popup closed'));\n }\n } catch {\n // Cross-origin error when checking popup.closed - this happens when the popup\n // navigates to a third-party OAuth provider with strict security settings.\n // We can't detect if the window was closed, so we rely on timeout and message handlers.\n clearInterval(popupClosedInterval);\n }\n }, 1000);\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"google-provider.js","sourceRoot":"","sources":["../../src/google-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAGzC,MAAM,8BAA8B,GAClC,0PAA0P,CAAC;AAE7P,MAAM,OAAO,iBAAkB,SAAQ,eAAe;IAAtD;;QACU,aAAQ,GAAkB,IAAI,CAAC;QAE/B,cAAS,GAAyB,QAAQ,CAAC;QAE3C,6BAAwB,GAAG,gDAAgD,CAAC;QACnE,qBAAgB,GAAG,iCAAiC,CAAC;IA6exE,CAAC;IA3eC,KAAK,CAAC,UAAU,CACd,QAAuB,EACvB,IAA2B,EAC3B,YAA4B,EAC5B,WAAoB;QAEpB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,YAAkC,CAAC;QACvD,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,KAAK,CACT,OAA2B;QAE3B,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;QAED,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;QAElC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,kEAAkE;YAClE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gDAAgD,CAAC,EAAE,CAAC;gBACvE,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAChE,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,kDAAkD,CAAC,EAAE,CAAC;gBACzE,MAAM,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;YAClE,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC/B,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,GAAG;gBACP,gDAAgD;gBAChD,kDAAkD;gBAClD,QAAQ;aACT,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAEvE,kEAAkE;QAClE,OAAO,IAAI,CAAC,gBAAgB,CAAC;YAC3B,MAAM;YACN,KAAK;YACL,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,OAAO,CAAC,MAAM,CAAC,6DAA6D,CAAC,CAAC;QACvF,CAAC;QACD,2BAA2B;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,OAAO,CAAC,MAAM,CAAC,iEAAiE,CAAC,CAAC;QAC3F,CAAC;QACD,2BAA2B;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QAEzC,IAAI,CAAC;YACH,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC5E,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACxD,IAAI,kBAAkB,IAAI,cAAc,EAAE,CAAC;gBACzC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;gBACvD,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,CAAC,CAAC,CAAC;gBACnE,CAAC;gBACD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,OAAO,CAAC,MAAM,CAAC,2EAA2E,CAAC,CAAC;QACrG,CAAC;QACD,2BAA2B;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAEtE,IAAI,CAAC;YACH,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC5E,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACxD,IAAI,kBAAkB,IAAI,cAAc,EAAE,CAAC;gBACzC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAChE,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;gBACvD,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,CAAC,CAAC,CAAC;gBACnE,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,iBAAiB,8BAA8B,EAAE,CAAC,CAAC;YAChE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAChG,CAAC;IACJ,CAAC;IAED,mBAAmB,CAAC,GAAQ;QAC1B,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC;QAEnC,6DAA6D;QAC7D,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,aAAa,EAAE,CAAC;YAClB,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC;YACzD,MAAM,gBAAgB,GAAG,SAAS,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,aAAa,CAAC;YAC7E,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QACrC,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEnC,IAAI,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE;oBACN,cAAc,EAAE,IAAI;oBACpB,YAAY,EAAE,SAAS;iBACxB;aACF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAE7C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;QAEzC,uDAAuD;QACvD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC;YACzD,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,KAAK,CAAC;YAClE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QACrC,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QAEtC,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAEvC,IAAI,WAAW,IAAI,OAAO,EAAE,CAAC;YAC3B,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACvC,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE;oBACN,WAAW,EAAE;wBACX,KAAK,EAAE,WAAW;qBACnB;oBACD,OAAO;oBACP,OAAO,EAAE;wBACP,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;wBAC5B,UAAU,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;wBACvC,SAAS,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;wBACrC,EAAE,EAAE,OAAO,CAAC,GAAG,IAAI,IAAI;wBACvB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;wBAC1B,QAAQ,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;qBAClC;oBACD,YAAY,EAAE,QAAQ;iBACvB;aACF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,WAAmB;QAClD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,wBAAwB,iBAAiB,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;QAE/F,IAAI,CAAC;YACH,mCAAmC;YACnC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAElC,sCAAsC;YACtC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,CAAC,GAAG,CACT,yBAAyB,IAAI,CAAC,wBAAwB,2CAA2C,QAAQ,CAAC,MAAM,wCAAwC,CACzJ,CAAC;gBACF,OAAO,KAAK,CAAC;YACf,CAAC;YAED,gCAAgC;YAChC,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAE3C,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,OAAO,CAAC,KAAK,CAAC,yBAAyB,IAAI,CAAC,wBAAwB,yBAAyB,CAAC,CAAC;gBAC/F,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,CAAC,wBAAwB,yBAAyB,CAAC,CAAC;YACnG,CAAC;YAED,kCAAkC;YAClC,IAAI,UAAe,CAAC;YACpB,IAAI,CAAC;gBACH,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACxC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACX,yBAAyB,IAAI,CAAC,wBAAwB,6CAA6C,CAAC,EAAE,CACvG,CAAC;gBACF,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,CAAC,wBAAwB,6CAA6C,CAAC,EAAE,CACvG,CAAC;YACJ,CAAC;YAED,iCAAiC;YACjC,MAAM,YAAY,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;YAE9C,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;gBACxD,OAAO,CAAC,KAAK,CACX,yBAAyB,IAAI,CAAC,wBAAwB,gDAAgD,CACvG,CAAC;gBACF,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,CAAC,wBAAwB,gDAAgD,CACvG,CAAC;YACJ,CAAC;YAED,mCAAmC;YACnC,IAAI,YAAoB,CAAC;YACzB,IAAI,CAAC;gBACH,YAAY,GAAG,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;gBAC1C,IAAI,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;oBACxB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACX,yBAAyB,IAAI,CAAC,wBAAwB,mBAAmB,YAAY,mCAAmC,CAAC,EAAE,CAC5H,CAAC;gBACF,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,CAAC,wBAAwB,mBAAmB,YAAY,mCAAmC,CAAC,EAAE,CAC5H,CAAC;YACJ,CAAC;YAED,+DAA+D;YAC/D,OAAO,YAAY,GAAG,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,OAAe;QAClC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,8CAA8C;YACpG,OAAO,MAAM,CAAC,GAAG,IAAI,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC;QAChD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,WAAmB,EAAE,aAA6B,IAAI;QAClF,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,UAAU,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,qDAAqD,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;gBACpG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,SAAS;YACX,CAAC;YACD,OAAO;QACT,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,WAAmB,EAAE,OAAe;QAC7D,IAAI,CAAC;YACH,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QAC/F,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAEO,gBAAgB;QACtB,IAAI,CAAC;YACH,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACjE,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YACxB,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACnD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,CAAC,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAqB,EACjD,MAAM,EACN,YAAY,EACZ,KAAK,EACL,MAAM,GACyC;;QAC/C,MAAM,YAAY,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QAEjE,MAAM,MAAM,GAAG,IAAI,eAAe,+BAChC,SAAS,EAAE,MAAA,IAAI,CAAC,QAAQ,mCAAI,EAAE,EAC9B,YAAY,EAAE,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,EACnF,aAAa,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,EACvE,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAC1B,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,CAAC,KACvB,sBAAsB,EAAE,MAAM,EAC9B,KAAK,EAAE,OAAO,IACd,CAAC;QACH,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClC,CAAC;QAED,MAAM,GAAG,GAAG,gDAAgD,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QAChF,MAAM,KAAK,GAAG,GAAG,CAAC;QAClB,MAAM,MAAM,GAAG,GAAG,CAAC;QACnB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/D,YAAY,CAAC,OAAO,CAClB,eAAe,CAAC,eAAe,EAC/B,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,CACzE,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,EAAE,SAAS,KAAK,WAAW,MAAM,SAAS,IAAI,QAAQ,GAAG,UAAU,CAAC,CAAC;QAEpH,IAAI,mBAA2B,CAAC;QAChC,IAAI,aAAqB,CAAC;QAE1B,uFAAuF;QACvF,MAAM,WAAW,GAAG,gBAAgB,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC1D,IAAI,gBAAgB,GAA4B,IAAI,CAAC;QAErD,IAAI,CAAC;YACH,gBAAgB,GAAG,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACvD,CAAC;QAAC,WAAM,CAAC;YACP,gEAAgE;QAClE,CAAC;QAED,2BAA2B;QAC3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;gBAC1C,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,CAAC,WAAW,GAAG,KAAK,EAAE,EAAE;gBACtC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;gBACrD,aAAa,CAAC,mBAAmB,CAAC,CAAC;gBACnC,YAAY,CAAC,aAAa,CAAC,CAAC;gBAC5B,IAAI,gBAAgB,EAAE,CAAC;oBACrB,gBAAgB,CAAC,KAAK,EAAE,CAAC;gBAC3B,CAAC;gBACD,IAAI,WAAW,EAAE,CAAC;oBAChB,IAAI,CAAC;wBACH,KAAK,CAAC,KAAK,EAAE,CAAC;oBAChB,CAAC;oBAAC,WAAM,CAAC;wBACP,oDAAoD;oBACtD,CAAC;gBACH,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,oBAAoB,GAAG,CAAC,IAA6B,EAAE,EAAE;gBAC7D,IAAI,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;oBAChC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,IAA6D,CAAC;oBAC/F,IAAI,WAAW,IAAI,OAAO,EAAE,CAAC;wBAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;wBACvC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;wBACpD,OAAO,CAAC;4BACN,QAAQ,EAAE,QAAa;4BACvB,MAAM,EAAE;gCACN,WAAW,EAAE;oCACX,KAAK,EAAE,WAAW,CAAC,KAAK;iCACzB;gCACD,OAAO;gCACP,OAAO,EAAE;oCACP,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;oCAC5B,UAAU,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;oCACvC,SAAS,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;oCACrC,EAAE,EAAE,OAAO,CAAC,GAAG,IAAI,IAAI;oCACvB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;oCAC1B,QAAQ,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;iCAClC;gCACD,YAAY,EAAE,QAAQ;6BACvB;yBACF,CAAC,CAAC;oBACL,CAAC;yBAAM,CAAC;wBACN,MAAM,CAAC,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC,CAAC;oBAC9E,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,MAAM,EAAE,cAAc,EAAE,GAAG,IAAkC,CAAC;oBAC9D,IAAI,CAAC,cAAc,EAAE,CAAC;wBACpB,MAAM,CAAC,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC,CAAC;wBACpE,OAAO;oBACT,CAAC;oBACD,OAAO,CAAC;wBACN,QAAQ,EAAE,QAAa;wBACvB,MAAM,EAAE;4BACN,YAAY,EAAE,SAAS;4BACvB,cAAc;yBACf;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,aAAa,GAAG,CAAC,KAAmB,EAAE,EAAE;;gBAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAI,MAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,0CAAE,UAAU,CAAC,SAAS,CAAC,CAAA;oBAAE,OAAO;gBAEjG,IAAI,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,IAAI,MAAK,gBAAgB,EAAE,CAAC;oBAC1C,OAAO,CAAC,IAAI,CAAC,CAAC;oBACd,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnC,CAAC;qBAAM,IAAI,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,IAAI,MAAK,aAAa,EAAE,CAAC;oBAC9C,OAAO,CAAC,IAAI,CAAC,CAAC;oBACd,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,+BAA+B,CAAC;oBACzE,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;gBAClC,CAAC;gBACD,wDAAwD;YAC1D,CAAC,CAAC;YAEF,uCAAuC;YACvC,IAAI,gBAAgB,EAAE,CAAC;gBACrB,gBAAgB,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;;oBACnD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;oBACxB,IAAI,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,0CAAE,QAAQ,GAAG,UAAU,CAAC,SAAS,CAAC;wBAAE,OAAO;oBAE3D,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,MAAK,gBAAgB,EAAE,CAAC;wBACpC,OAAO,CAAC,IAAI,CAAC,CAAC;wBACd,oBAAoB,CAAC,IAAI,CAAC,CAAC;oBAC7B,CAAC;yBAAM,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,MAAK,aAAa,EAAE,CAAC;wBACxC,OAAO,CAAC,IAAI,CAAC,CAAC;wBACd,MAAM,YAAY,GAAI,IAAI,CAAC,KAAgB,IAAI,+BAA+B,CAAC;wBAC/E,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;oBAClC,CAAC;gBACH,CAAC,CAAC;YACJ,CAAC;YAED,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;YAElD,0BAA0B;YAC1B,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;gBACd,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;YACrC,CAAC,EAAE,MAAM,CAAC,CAAC;YAEX,mBAAmB,GAAG,WAAW,CAAC,GAAG,EAAE;gBACrC,IAAI,CAAC;oBACH,mFAAmF;oBACnF,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;wBACjB,OAAO,EAAE,CAAC;wBACV,MAAM,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC;gBAAC,WAAM,CAAC;oBACP,8EAA8E;oBAC9E,2EAA2E;oBAC3E,wFAAwF;oBACxF,aAAa,CAAC,mBAAmB,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC,EAAE,IAAI,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { BaseSocialLogin } from './base';\nimport type { GoogleLoginOptions, LoginResult, ProviderResponseMap, AuthorizationCode } from './definitions';\n\nconst GOOGLE_OFFLINE_REFRESH_MESSAGE =\n \"Google refresh() is not available when using offline mode. Offline mode only returns serverAuthCode for backend token exchange. Send serverAuthCode to your backend and refresh tokens there, or switch google.mode to 'online' for client-side refresh.\";\n\nexport class GoogleSocialLogin extends BaseSocialLogin {\n private clientId: string | null = null;\n private hostedDomain?: string;\n private loginType: 'online' | 'offline' = 'online';\n private redirectUrl?: string;\n private GOOGLE_TOKEN_REQUEST_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo';\n private readonly GOOGLE_STATE_KEY = 'capgo_social_login_google_state';\n\n async initialize(\n clientId: string | null,\n mode?: 'online' | 'offline',\n hostedDomain?: string | null,\n redirectUrl?: string,\n ): Promise<void> {\n this.clientId = clientId;\n if (mode) {\n this.loginType = mode;\n }\n this.hostedDomain = hostedDomain as string | undefined;\n this.redirectUrl = redirectUrl;\n }\n\n async login<T extends 'google'>(\n options: GoogleLoginOptions,\n ): Promise<{ provider: T; result: ProviderResponseMap[T] }> {\n if (!this.clientId) {\n throw new Error('Google Client ID not set. Call initialize() first.');\n }\n\n let scopes = options.scopes || [];\n\n if (scopes.length > 0) {\n // If scopes are provided, directly use the traditional OAuth flow\n if (!scopes.includes('https://www.googleapis.com/auth/userinfo.email')) {\n scopes.push('https://www.googleapis.com/auth/userinfo.email');\n }\n if (!scopes.includes('https://www.googleapis.com/auth/userinfo.profile')) {\n scopes.push('https://www.googleapis.com/auth/userinfo.profile');\n }\n if (!scopes.includes('openid')) {\n scopes.push('openid');\n }\n } else {\n scopes = [\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'openid',\n ];\n }\n\n const nonce = options.nonce || Math.random().toString(36).substring(2);\n\n // If scopes are provided, directly use the traditional OAuth flow\n return this.traditionalOAuth({\n scopes,\n nonce,\n hostedDomain: this.hostedDomain,\n prompt: options.prompt,\n });\n }\n\n async logout(): Promise<void> {\n if (this.loginType === 'offline') {\n return Promise.reject(\"Offline login doesn't store tokens. logout is not available\");\n }\n // eslint-disable-next-line\n const state = this.getGoogleState();\n if (!state) return;\n await this.rawLogoutGoogle(state.accessToken);\n }\n\n async isLoggedIn(): Promise<{ isLoggedIn: boolean }> {\n if (this.loginType === 'offline') {\n return Promise.reject(\"Offline login doesn't store tokens. isLoggedIn is not available\");\n }\n // eslint-disable-next-line\n const state = this.getGoogleState();\n if (!state) return { isLoggedIn: false };\n\n try {\n const isValidAccessToken = await this.accessTokenIsValid(state.accessToken);\n const isValidIdToken = this.idTokenValid(state.idToken);\n if (isValidAccessToken && isValidIdToken) {\n return { isLoggedIn: true };\n } else {\n try {\n await this.rawLogoutGoogle(state.accessToken, false);\n } catch (e) {\n console.error('Access token is not valid, but cannot logout', e);\n }\n return { isLoggedIn: false };\n }\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n async getAuthorizationCode(): Promise<AuthorizationCode> {\n if (this.loginType === 'offline') {\n return Promise.reject(\"Offline login doesn't store tokens. getAuthorizationCode is not available\");\n }\n // eslint-disable-next-line\n const state = this.getGoogleState();\n if (!state) throw new Error('No Google authorization code available');\n\n try {\n const isValidAccessToken = await this.accessTokenIsValid(state.accessToken);\n const isValidIdToken = this.idTokenValid(state.idToken);\n if (isValidAccessToken && isValidIdToken) {\n return { accessToken: state.accessToken, jwt: state.idToken };\n } else {\n try {\n await this.rawLogoutGoogle(state.accessToken, false);\n } catch (e) {\n console.error('Access token is not valid, but cannot logout', e);\n }\n throw new Error('No Google authorization code available');\n }\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n async refresh(): Promise<void> {\n if (this.loginType === 'offline') {\n console.warn(`[SocialLogin] ${GOOGLE_OFFLINE_REFRESH_MESSAGE}`);\n return Promise.reject(new Error(GOOGLE_OFFLINE_REFRESH_MESSAGE));\n }\n return Promise.reject(\n new Error('Google refresh is not implemented on web. Use login() again to obtain a new token.'),\n );\n }\n\n handleOAuthRedirect(url: URL): LoginResult | { error: string } | null {\n const paramsRaw = url.searchParams;\n\n // Check for errors in search params first (for offline mode)\n const errorInParams = paramsRaw.get('error');\n if (errorInParams) {\n localStorage.removeItem(BaseSocialLogin.OAUTH_STATE_KEY);\n const errorDescription = paramsRaw.get('error_description') || errorInParams;\n return { error: errorDescription };\n }\n\n const code = paramsRaw.get('code');\n\n if (code && paramsRaw.has('scope')) {\n return {\n provider: 'google',\n result: {\n serverAuthCode: code,\n responseType: 'offline',\n },\n };\n }\n\n const hash = url.hash.substring(1);\n console.log('handleOAuthRedirect', url.hash);\n\n if (!hash) return null;\n\n const params = new URLSearchParams(hash);\n\n // Check for error cases in hash (e.g., user cancelled)\n const error = params.get('error');\n if (error) {\n localStorage.removeItem(BaseSocialLogin.OAUTH_STATE_KEY);\n const errorDescription = params.get('error_description') || error;\n return { error: errorDescription };\n }\n\n console.log('handleOAuthRedirect ok');\n\n const accessToken = params.get('access_token');\n const idToken = params.get('id_token');\n\n if (accessToken && idToken) {\n localStorage.removeItem(BaseSocialLogin.OAUTH_STATE_KEY);\n const profile = this.parseJwt(idToken);\n return {\n provider: 'google',\n result: {\n accessToken: {\n token: accessToken,\n },\n idToken,\n profile: {\n email: profile.email || null,\n familyName: profile.family_name || null,\n givenName: profile.given_name || null,\n id: profile.sub || null,\n name: profile.name || null,\n imageUrl: profile.picture || null,\n },\n responseType: 'online',\n },\n };\n }\n return null;\n }\n\n private async accessTokenIsValid(accessToken: string): Promise<boolean> {\n const url = `${this.GOOGLE_TOKEN_REQUEST_URL}?access_token=${encodeURIComponent(accessToken)}`;\n\n try {\n // Make the GET request using fetch\n const response = await fetch(url);\n\n // Check if the response is successful\n if (!response.ok) {\n console.log(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response not successful. Status code: ${response.status}. Assuming that the token is not valid`,\n );\n return false;\n }\n\n // Get the response body as text\n const responseBody = await response.text();\n\n if (!responseBody) {\n console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`);\n throw new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`);\n }\n\n // Parse the response body as JSON\n let jsonObject: any;\n try {\n jsonObject = JSON.parse(responseBody);\n } catch (e) {\n console.error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${e}`,\n );\n throw new Error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${e}`,\n );\n }\n\n // Extract the 'expires_in' field\n const expiresInStr = jsonObject['expires_in'];\n\n if (expiresInStr === undefined || expiresInStr === null) {\n console.error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`,\n );\n throw new Error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`,\n );\n }\n\n // Parse 'expires_in' as an integer\n let expiresInInt: number;\n try {\n expiresInInt = parseInt(expiresInStr, 10);\n if (isNaN(expiresInInt)) {\n throw new Error(`'expires_in' is not a valid integer`);\n }\n } catch (e) {\n console.error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${expiresInStr} is not a valid integer. Error: ${e}`,\n );\n throw new Error(\n `Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${expiresInStr} is not a valid integer. Error: ${e}`,\n );\n }\n\n // Determine if the access token is valid based on 'expires_in'\n return expiresInInt > 5;\n } catch (error) {\n console.error(error);\n throw error;\n }\n }\n\n private idTokenValid(idToken: string): boolean {\n try {\n const parsed = this.parseJwt(idToken);\n const currentTime = Math.ceil(Date.now() / 1000) + 5; // Convert current time to seconds since epoch\n return parsed.exp && currentTime < parsed.exp;\n } catch (e) {\n return false;\n }\n }\n\n private async rawLogoutGoogle(accessToken: string, tokenValid: boolean | null = null) {\n if (tokenValid === null) {\n tokenValid = await this.accessTokenIsValid(accessToken);\n }\n\n if (tokenValid === true) {\n try {\n await fetch(`https://accounts.google.com/o/oauth2/revoke?token=${encodeURIComponent(accessToken)}`);\n this.clearStateGoogle();\n } catch (e) {\n // ignore\n }\n return;\n } else {\n this.clearStateGoogle();\n return;\n }\n }\n\n private persistStateGoogle(accessToken: string, idToken: string) {\n try {\n window.localStorage.setItem(this.GOOGLE_STATE_KEY, JSON.stringify({ accessToken, idToken }));\n } catch (e) {\n console.error('Cannot persist state google', e);\n }\n }\n\n private clearStateGoogle() {\n try {\n window.localStorage.removeItem(this.GOOGLE_STATE_KEY);\n } catch (e) {\n console.error('Cannot clear state google', e);\n }\n }\n\n private getGoogleState(): { accessToken: string; idToken: string } | null {\n try {\n const state = window.localStorage.getItem(this.GOOGLE_STATE_KEY);\n if (!state) return null;\n const { accessToken, idToken } = JSON.parse(state);\n return { accessToken, idToken };\n } catch (e) {\n console.error('Cannot get state google', e);\n return null;\n }\n }\n\n private async traditionalOAuth<T extends 'google'>({\n scopes,\n hostedDomain,\n nonce,\n prompt,\n }: GoogleLoginOptions & { hostedDomain?: string }): Promise<{ provider: T; result: ProviderResponseMap[T] }> {\n const uniqueScopes = [...new Set([...(scopes || []), 'openid'])];\n\n const params = new URLSearchParams({\n client_id: this.clientId ?? '',\n redirect_uri: this.redirectUrl || window.location.origin + window.location.pathname,\n response_type: this.loginType === 'offline' ? 'code' : 'token id_token',\n scope: uniqueScopes.join(' '),\n ...(nonce && { nonce }),\n include_granted_scopes: 'true',\n state: 'popup',\n });\n if (hostedDomain !== undefined) {\n params.append('hd', hostedDomain);\n }\n if (prompt !== undefined) {\n params.append('prompt', prompt);\n }\n\n const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;\n const width = 500;\n const height = 600;\n const left = window.screenX + (window.outerWidth - width) / 2;\n const top = window.screenY + (window.outerHeight - height) / 2;\n localStorage.setItem(\n BaseSocialLogin.OAUTH_STATE_KEY,\n JSON.stringify({ provider: 'google', loginType: this.loginType, nonce }),\n );\n const popup = window.open(url, 'Google Sign In', `width=${width},height=${height},left=${left},top=${top},popup=1`);\n\n let popupClosedInterval: number;\n let timeoutHandle: number;\n\n // Use BroadcastChannel for cross-origin communication (works when postMessage doesn't)\n const channelName = `google_oauth_${nonce || Date.now()}`;\n let broadcastChannel: BroadcastChannel | null = null;\n\n try {\n broadcastChannel = new BroadcastChannel(channelName);\n } catch {\n // BroadcastChannel not supported, fall back to postMessage only\n }\n\n // This may never return...\n return new Promise((resolve, reject) => {\n if (!popup) {\n reject(new Error('Failed to open popup'));\n return;\n }\n\n const cleanup = (shouldClose = false) => {\n window.removeEventListener('message', handleMessage);\n clearInterval(popupClosedInterval);\n clearTimeout(timeoutHandle);\n if (broadcastChannel) {\n broadcastChannel.close();\n }\n if (shouldClose) {\n try {\n popup.close();\n } catch {\n // Ignore cross-origin errors when closing the popup\n }\n }\n };\n\n const processOAuthResponse = (data: Record<string, unknown>) => {\n if (this.loginType === 'online') {\n const { accessToken, idToken } = data as { accessToken?: { token: string }; idToken?: string };\n if (accessToken && idToken) {\n const profile = this.parseJwt(idToken);\n this.persistStateGoogle(accessToken.token, idToken);\n resolve({\n provider: 'google' as T,\n result: {\n accessToken: {\n token: accessToken.token,\n },\n idToken,\n profile: {\n email: profile.email || null,\n familyName: profile.family_name || null,\n givenName: profile.given_name || null,\n id: profile.sub || null,\n name: profile.name || null,\n imageUrl: profile.picture || null,\n },\n responseType: 'online',\n },\n });\n } else {\n reject(new Error('Invalid OAuth response: missing accessToken or idToken'));\n }\n } else {\n const { serverAuthCode } = data as { serverAuthCode: string };\n if (!serverAuthCode) {\n reject(new Error('Invalid OAuth response: missing serverAuthCode'));\n return;\n }\n resolve({\n provider: 'google' as T,\n result: {\n responseType: 'offline',\n serverAuthCode,\n },\n });\n }\n };\n\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== window.location.origin || event.data?.source?.startsWith('angular')) return;\n\n if (event.data?.type === 'oauth-response') {\n cleanup(true);\n processOAuthResponse(event.data);\n } else if (event.data?.type === 'oauth-error') {\n cleanup(true);\n const errorMessage = event.data.error || 'User cancelled the OAuth flow';\n reject(new Error(errorMessage));\n }\n // Don't reject for non-OAuth messages, just ignore them\n };\n\n // Listen for BroadcastChannel messages\n if (broadcastChannel) {\n broadcastChannel.onmessage = (event: MessageEvent) => {\n const data = event.data;\n if (data?.source?.toString().startsWith('angular')) return;\n\n if (data?.type === 'oauth-response') {\n cleanup(true);\n processOAuthResponse(data);\n } else if (data?.type === 'oauth-error') {\n cleanup(true);\n const errorMessage = (data.error as string) || 'User cancelled the OAuth flow';\n reject(new Error(errorMessage));\n }\n };\n }\n\n window.addEventListener('message', handleMessage);\n\n // Timeout after 5 minutes\n timeoutHandle = setTimeout(() => {\n cleanup(true);\n reject(new Error('OAuth timeout'));\n }, 300000);\n\n popupClosedInterval = setInterval(() => {\n try {\n // Check if popup is closed - this may throw cross-origin errors for some providers\n if (popup.closed) {\n cleanup();\n reject(new Error('Popup closed'));\n }\n } catch {\n // Cross-origin error when checking popup.closed - this happens when the popup\n // navigates to a third-party OAuth provider with strict security settings.\n // We can't detect if the window was closed, so we rely on timeout and message handlers.\n clearInterval(popupClosedInterval);\n }\n }, 1000);\n });\n }\n}\n"]}
|
|
@@ -38,6 +38,7 @@ export class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
38
38
|
}
|
|
39
39
|
return {
|
|
40
40
|
appId,
|
|
41
|
+
clientSecret: config.clientSecret,
|
|
41
42
|
issuerUrl: config.issuerUrl,
|
|
42
43
|
authorizationBaseUrl,
|
|
43
44
|
accessTokenEndpoint,
|
|
@@ -500,6 +501,9 @@ export class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
500
501
|
if (config.pkceEnabled) {
|
|
501
502
|
params.set('code_verifier', pending.codeVerifier);
|
|
502
503
|
}
|
|
504
|
+
if (config.clientSecret) {
|
|
505
|
+
params.set('client_secret', config.clientSecret);
|
|
506
|
+
}
|
|
503
507
|
if (config.additionalTokenParameters) {
|
|
504
508
|
for (const [k, v] of Object.entries(config.additionalTokenParameters)) {
|
|
505
509
|
params.set(k, v);
|
|
@@ -531,6 +535,9 @@ export class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
531
535
|
refresh_token: refreshToken,
|
|
532
536
|
client_id: config.appId,
|
|
533
537
|
});
|
|
538
|
+
if (config.clientSecret) {
|
|
539
|
+
params.set('client_secret', config.clientSecret);
|
|
540
|
+
}
|
|
534
541
|
if (config.additionalTokenParameters) {
|
|
535
542
|
for (const [k, v] of Object.entries(config.additionalTokenParameters)) {
|
|
536
543
|
params.set(k, v);
|