UNPKG

@turnkey/react-wallet-kit

Version:

The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.

238 lines (235 loc) 8.78 kB
import { createForOfIteratorHelper as _createForOfIteratorHelper, slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.mjs'; import { TurnkeyError, TurnkeyErrorCodes, OAuthProviders } from '@turnkey/sdk-types'; import { OAUTH_PROVIDER_CONFIGS, popupWidth, popupHeight } from './config.mjs'; import { consumeOAuthState, storeOAuthState } from './storage.mjs'; /** * Builds the OAuth state parameter string */ function buildOAuthState(params) { var provider = params.provider, flow = params.flow, publicKey = params.publicKey, nonce = params.nonce, additionalState = params.additionalState; var state = "provider=".concat(provider, "&flow=").concat(flow, "&publicKey=").concat(encodeURIComponent(publicKey)); if (nonce) { state += "&nonce=".concat(nonce); } if (additionalState) { var extra = Object.entries(additionalState).map(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), k = _ref2[0], v = _ref2[1]; return "".concat(encodeURIComponent(k), "=").concat(encodeURIComponent(v)); }).join("&"); if (extra) { state += "&".concat(extra); } } return state; } /** * Opens a centered OAuth popup window */ function openOAuthPopup() { var width = popupWidth; var height = popupHeight; var left = window.screenX + (window.innerWidth - width) / 2; var top = window.screenY + (window.innerHeight - height) / 2; return window.open("about:blank", "_blank", "width=".concat(width, ",height=").concat(height, ",top=").concat(top, ",left=").concat(left, ",scrollbars=yes,resizable=yes")); } /** * Redirects to OAuth provider URL */ function redirectToOAuthProvider(url) { window.location.href = url; var timeLimit = 5 * 60 * 1000; // 5 minutes return new Promise(function (_, reject) { var timeout = setTimeout(function () { reject(new Error("Authentication timed out.")); }, timeLimit); window.addEventListener("beforeunload", function () { return clearTimeout(timeout); }); }); } /** * Cleans up OAuth parameters from URL (removes hash and search) */ function cleanupOAuthUrl() { window.history.replaceState(null, document.title, window.location.pathname); } /** * Cleans up OAuth hash from URL but preserves search parameters */ function cleanupOAuthUrlPreserveSearch() { window.history.replaceState(null, document.title, window.location.pathname + window.location.search); } /** * Parses the OAuth state parameter string into an object */ function parseStateParam(stateParam) { if (!stateParam) return {}; var result = {}; var pairs = stateParam.split("&"); var _iterator = _createForOfIteratorHelper(pairs), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var pair = _step.value; var _pair$split = pair.split("="), _pair$split2 = _slicedToArray(_pair$split, 2), key = _pair$split2[0], value = _pair$split2[1]; if (key && value !== undefined) { result[key] = decodeURIComponent(value); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return result; } /** * Unified OAuth response parser for both popup and redirect flows. * Handles both: * - PKCE flows (Facebook, Discord, X): code in search parameters * - Non-PKCE flows (Google, Apple): id_token in hash parameters * - Apple's non-standard hash format where state parameters are directly embedded * * @param url - The full URL to parse (from popup or redirect) * @param expectedProvider - Optional provider if already known (for popup flows) * @returns Parsed OAuth response data including tokens, codes, and state parameters, or null if invalid */ function parseOAuthResponse(url, expectedProvider) { var idToken = null; var authCode = null; var stateString = null; var parsedUrl = new URL(url); var search = parsedUrl.search.substring(1); // Remove leading '?' var hash = parsedUrl.hash.substring(1); // Remove leading '#' // Handle PKCE flows (code in search parameters) if (search && search.includes("code=")) { var searchParams = new URLSearchParams(search); authCode = searchParams.get("code"); stateString = searchParams.get("state"); } // Handle non-PKCE flows (id_token in hash) else if (hash) { // Handle Apple's non-standard format: state=provider=apple&flow=redirect&...&code=...&id_token=... if (hash.startsWith("state=provider=apple")) { // Extract id_token from the end var idTokenMatch = hash.match(/id_token=([^&]+)$/); idToken = idTokenMatch ? idTokenMatch[1] : null; // Extract state content between "state=" and "&code=" var stateEndIndex = hash.indexOf("&code="); if (stateEndIndex !== -1) { stateString = hash.substring(6, stateEndIndex); // Remove "state=" prefix } } else { // Standard OAuth format - parse as URLSearchParams var hashParams = new URLSearchParams(hash); idToken = hashParams.get("id_token"); stateString = hashParams.get("state"); } } if (!stateString) { throw new TurnkeyError("Missing OAuth state in redirect response", TurnkeyErrorCodes.INVALID_OAUTH_STATE); } // Validate state parameter consumeOAuthState(stateString); // Parse state parameters var _parseStateParam = parseStateParam(stateString), provider = _parseStateParam.provider, flow = _parseStateParam.flow, publicKey = _parseStateParam.publicKey, openModal = _parseStateParam.openModal, sessionKey = _parseStateParam.sessionKey, oauthIntent = _parseStateParam.oauthIntent, nonce = _parseStateParam.nonce; // If we have an expected provider (popup flow), validate it matches if (expectedProvider) { var config = OAUTH_PROVIDER_CONFIGS[expectedProvider]; if (config.usesPKCE) { // PKCE providers must have authCode and matching provider in state if (!authCode || !stateString || provider !== expectedProvider) { return null; } } else { // Non-PKCE providers must have idToken if (!idToken) { return null; } } } return { idToken: idToken, authCode: authCode, provider: provider !== null && provider !== void 0 ? provider : null, flow: flow !== null && flow !== void 0 ? flow : null, publicKey: publicKey !== null && publicKey !== void 0 ? publicKey : null, openModal: openModal !== null && openModal !== void 0 ? openModal : null, sessionKey: sessionKey !== null && sessionKey !== void 0 ? sessionKey : undefined, oauthIntent: oauthIntent !== null && oauthIntent !== void 0 ? oauthIntent : null, nonce: nonce !== null && nonce !== void 0 ? nonce : null }; } /** * Builds the complete OAuth authorization URL for a provider */ function buildOAuthUrl(params) { var provider = params.provider, clientId = params.clientId, redirectUri = params.redirectUri, publicKey = params.publicKey, nonce = params.nonce, flow = params.flow, codeChallenge = params.codeChallenge, additionalState = params.additionalState; var config = OAUTH_PROVIDER_CONFIGS[provider]; var authUrl = new URL(config.authUrl); authUrl.searchParams.set("client_id", clientId); authUrl.searchParams.set("redirect_uri", redirectUri); authUrl.searchParams.set("response_type", config.responseType); if (config.scopes) { authUrl.searchParams.set("scope", config.scopes); } if (config.responseMode) { authUrl.searchParams.set("response_mode", config.responseMode); } // PKCE parameters if (config.usesPKCE && codeChallenge) { authUrl.searchParams.set("code_challenge", codeChallenge); authUrl.searchParams.set("code_challenge_method", "S256"); } // Nonce handling - some providers want it in URL params, others in state if (config.nonceInParams) { authUrl.searchParams.set("nonce", nonce); } // Google-specific: prompt for account selection if (provider === OAuthProviders.GOOGLE) { authUrl.searchParams.set("prompt", "select_account"); } // Build state parameter var stateParams = { provider: provider, flow: flow, publicKey: publicKey }; // Include nonce in state for providers that need it there (not in URL params) if (!config.nonceInParams) { stateParams.nonce = nonce; } if (additionalState) { stateParams.additionalState = additionalState; } // Generate state string and store for validation var state = buildOAuthState(stateParams); authUrl.searchParams.set("state", state); storeOAuthState(state); return authUrl.toString(); } export { buildOAuthState, buildOAuthUrl, cleanupOAuthUrl, cleanupOAuthUrlPreserveSearch, openOAuthPopup, parseOAuthResponse, parseStateParam, redirectToOAuthProvider }; //# sourceMappingURL=url.mjs.map