@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
JavaScript
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