@turnkey/react-wallet-kit
Version:
The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.
342 lines (339 loc) • 14.5 kB
JavaScript
import { slicedToArray as _slicedToArray, toConsumableArray as _toConsumableArray, asyncToGenerator as _asyncToGenerator, regeneratorRuntime as _regeneratorRuntime } from './_virtual/_rollupPluginBabelHelpers.mjs';
import { TurnkeyError, TurnkeyErrorCodes } from '@turnkey/sdk-types';
import { useState, useEffect, useRef, useCallback } from 'react';
import { WalletInterfaceType } from '@turnkey/core';
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
var APPLE_AUTH_URL = "https://account.apple.com/auth/authorize";
var FACEBOOK_AUTH_URL = "https://www.facebook.com/v11.0/dialog/oauth";
var FACEBOOK_GRAPH_URL = "https://graph.facebook.com/v11.0/oauth/access_token";
var popupWidth = 500;
var popupHeight = 600;
var SESSION_WARNING_THRESHOLD_MS = 60 * 1000; // 1 minute in milliseconds
var useDebouncedCallback = function useDebouncedCallback(fn) {
var wait = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100;
var timer = useRef(null);
var fnRef = useRef(fn);
fnRef.current = fn;
return useCallback(function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(function () {
fnRef.current.apply(fnRef, args);
timer.current = null;
}, wait);
}, [wait]);
};
var isValidSession = function isValidSession(session) {
return (session === null || session === void 0 ? void 0 : session.expiry) !== undefined && session.expiry * 1000 > Date.now();
};
function withTurnkeyErrorHandling(_x, _x2) {
return _withTurnkeyErrorHandling.apply(this, arguments);
}
// Helper function to parse Apple OAuth redirects
function _withTurnkeyErrorHandling() {
_withTurnkeyErrorHandling = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(fn, callbacks) {
var fallbackMessage,
fallbackCode,
_callbacks$onError2,
_callbacks$onError,
tkError,
_args = arguments;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
fallbackMessage = _args.length > 2 && _args[2] !== undefined ? _args[2] : "An unknown error occurred";
fallbackCode = _args.length > 3 && _args[3] !== undefined ? _args[3] : TurnkeyErrorCodes.UNKNOWN;
_context.prev = 2;
_context.next = 5;
return fn();
case 5:
return _context.abrupt("return", _context.sent);
case 8:
_context.prev = 8;
_context.t0 = _context["catch"](2);
if (!(_context.t0 instanceof TurnkeyError)) {
_context.next = 13;
break;
}
callbacks === null || callbacks === void 0 ? void 0 : (_callbacks$onError = callbacks.onError) === null || _callbacks$onError === void 0 ? void 0 : _callbacks$onError.call(callbacks, _context.t0);
throw _context.t0;
case 13:
tkError = new TurnkeyError(fallbackMessage, fallbackCode, _context.t0);
callbacks === null || callbacks === void 0 ? void 0 : (_callbacks$onError2 = callbacks.onError) === null || _callbacks$onError2 === void 0 ? void 0 : _callbacks$onError2.call(callbacks, tkError);
throw tkError;
case 16:
case "end":
return _context.stop();
}
}, _callee, null, [[2, 8]]);
}));
return _withTurnkeyErrorHandling.apply(this, arguments);
}
function parseAppleOAuthRedirect(hash) {
// Apple's format has unencoded parameters in the state portion
var idTokenMatch = hash.match(/id_token=([^&]+)$/);
var idToken = idTokenMatch ? idTokenMatch[1] : null;
// Extract state parameters - state is at the beginning
// It typically looks like: state=provider=apple&flow=redirect&publicKey=123...&openModal=true&code=...&id_token=...
var stateEndIndex = hash.indexOf("&code=");
if (stateEndIndex === -1) return {
idToken: idToken,
provider: null,
flow: null,
publicKey: null,
openModal: null
};
var stateContent = hash.substring(6, stateEndIndex); // Remove "state=" prefix
var stateParams = new URLSearchParams(stateContent);
return {
idToken: idToken,
provider: stateParams.get("provider"),
flow: stateParams.get("flow"),
publicKey: stateParams.get("publicKey"),
openModal: stateParams.get("openModal")
};
}
// Helper function to parse Google OAuth redirects
function parseGoogleOAuthRedirect(hash) {
var hashParams = new URLSearchParams(hash);
var idToken = hashParams.get("id_token");
var state = hashParams.get("state");
var provider = null;
var flow = null;
var publicKey = null;
var openModal = null;
if (state) {
var stateParams = new URLSearchParams(state);
provider = stateParams.get("provider");
flow = stateParams.get("flow");
publicKey = stateParams.get("publicKey");
openModal = stateParams.get("openModal");
}
return {
idToken: idToken,
provider: provider,
flow: flow,
publicKey: publicKey,
openModal: openModal
};
}
// Main function to determine provider and parse accordingly
function parseOAuthRedirect(hash) {
// Check if this is an Apple redirect
if (hash.startsWith("state=provider=apple")) {
return parseAppleOAuthRedirect(hash);
} else {
return parseGoogleOAuthRedirect(hash);
}
}
// Function to generate PKCE challenge pair for Facebook OAuth
function generateChallengePair() {
return _generateChallengePair.apply(this, arguments);
}
// Function to exchange Facebook authorization code for token
function _generateChallengePair() {
_generateChallengePair = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2() {
var randomBytes, verifier, encoder, data, digest, base64Challenge;
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
randomBytes = new Uint8Array(32);
window.crypto.getRandomValues(randomBytes);
verifier = btoa(String.fromCharCode.apply(String, _toConsumableArray(randomBytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
encoder = new TextEncoder();
data = encoder.encode(verifier);
_context2.next = 7;
return window.crypto.subtle.digest("SHA-256", data);
case 7:
digest = _context2.sent;
base64Challenge = btoa(String.fromCharCode.apply(String, _toConsumableArray(new Uint8Array(digest)))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
return _context2.abrupt("return", {
verifier: verifier,
codeChallenge: base64Challenge
});
case 10:
case "end":
return _context2.stop();
}
}, _callee2);
}));
return _generateChallengePair.apply(this, arguments);
}
function exchangeCodeForToken(_x3, _x4, _x5, _x6) {
return _exchangeCodeForToken.apply(this, arguments);
}
function _exchangeCodeForToken() {
_exchangeCodeForToken = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3(clientId, redirectUri, code, codeVerifier) {
var params, response, errorData;
return _regeneratorRuntime().wrap(function _callee3$(_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
code: code,
grant_type: "authorization_code"
});
_context3.next = 3;
return fetch(FACEBOOK_GRAPH_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params.toString()
});
case 3:
response = _context3.sent;
if (response.ok) {
_context3.next = 9;
break;
}
_context3.next = 7;
return response.json();
case 7:
errorData = _context3.sent;
throw new Error("Facebook token exchange failed: ".concat(JSON.stringify(errorData)));
case 9:
_context3.next = 11;
return response.json();
case 11:
return _context3.abrupt("return", _context3.sent);
case 12:
case "end":
return _context3.stop();
}
}, _callee3);
}));
return _exchangeCodeForToken.apply(this, arguments);
}
function handleFacebookPKCEFlow(_x7) {
return _handleFacebookPKCEFlow.apply(this, arguments);
}
// Custom hook to get the current screen size
function _handleFacebookPKCEFlow() {
_handleFacebookPKCEFlow = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee4(_ref) {
var code, publicKey, openModal, clientId, redirectURI, callbacks, completeOauth, onPushPage, verifier, tokenData;
return _regeneratorRuntime().wrap(function _callee4$(_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
code = _ref.code, publicKey = _ref.publicKey, openModal = _ref.openModal, clientId = _ref.clientId, redirectURI = _ref.redirectURI, callbacks = _ref.callbacks, completeOauth = _ref.completeOauth, onPushPage = _ref.onPushPage;
// Retrieve the verifier stored during OAuth initiation
verifier = sessionStorage.getItem("facebook_verifier");
if (verifier) {
_context4.next = 4;
break;
}
throw new TurnkeyError("Missing PKCE verifier for Facebook authentication", TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR);
case 4:
_context4.prev = 4;
_context4.next = 7;
return exchangeCodeForToken(clientId, redirectURI, code, verifier);
case 7:
tokenData = _context4.sent;
// Clean up the verifier as it's no longer needed
sessionStorage.removeItem("facebook_verifier");
// Handle different UI flows based on openModal parameter
if (!(openModal === "true")) {
_context4.next = 14;
break;
}
_context4.next = 12;
return onPushPage(tokenData.id_token);
case 12:
_context4.next = 20;
break;
case 14:
if (!(callbacks !== null && callbacks !== void 0 && callbacks.onOauthRedirect)) {
_context4.next = 18;
break;
}
callbacks.onOauthRedirect({
idToken: tokenData.id_token,
publicKey: publicKey
});
_context4.next = 20;
break;
case 18:
_context4.next = 20;
return completeOauth({
oidcToken: tokenData.id_token,
publicKey: publicKey
});
case 20:
// Clean up the URL after processing
window.history.replaceState(null, document.title, window.location.pathname);
return _context4.abrupt("return");
case 24:
_context4.prev = 24;
_context4.t0 = _context4["catch"](4);
console.error("Error exchanging Facebook code for token:", _context4.t0);
throw new TurnkeyError("Failed to complete Facebook authentication", TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR, _context4.t0);
case 28:
case "end":
return _context4.stop();
}
}, _callee4, null, [[4, 24]]);
}));
return _handleFacebookPKCEFlow.apply(this, arguments);
}
function useScreenSize() {
var _useState = useState(typeof window !== "undefined" ? window.innerWidth : 1024),
_useState2 = _slicedToArray(_useState, 2),
width = _useState2[0],
setWidth = _useState2[1];
useEffect(function () {
var handleResize = function handleResize() {
return setWidth(window.innerWidth);
};
window.addEventListener("resize", handleResize);
return function () {
return window.removeEventListener("resize", handleResize);
};
}, []);
return {
width: width,
// I have no idea why but, Tailwind's responsive design breakpoints do not work. Throughout the modal components, you will see conditional styling using this `isMobile` variable.
// This is fine since we only need to style for 2 screen sizes: mobile and desktop. If anyone can figure out why Tailwind's responsive design breakpoints do not work, please fix it and restyle the components accordingly, changing the `isMobile` to the Tailwind stuff when applicable.
isMobile: width < 640
};
}
function isWalletConnect(wallet) {
return wallet.interfaceType == WalletInterfaceType.WalletConnect;
}
function useWalletProviderState() {
var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var _useState3 = useState(initialState),
_useState4 = _slicedToArray(_useState3, 2),
walletProviders = _useState4[0],
setWalletProviders = _useState4[1];
var prevProvidersRef = useRef(initialState);
function isSameWalletProvider(a, b) {
if (a.length !== b.length) return false;
var key = function key(provider) {
var name = provider.info.name;
var namespace = provider.chainInfo.namespace;
var interfaceType = provider.interfaceType;
var connectedAddresses = _toConsumableArray(provider.connectedAddresses).map(function (x) {
return x.toLowerCase();
}).sort().join(",");
return "".concat(namespace, "|").concat(interfaceType, "|").concat(name, "|").concat(connectedAddresses);
};
var A = a.map(key).sort();
var B = b.map(key).sort();
for (var i = 0; i < A.length; i++) if (A[i] !== B[i]) return false;
return true;
}
var updateWalletProviders = useCallback(function (newProviders) {
if (!isSameWalletProvider(prevProvidersRef.current, newProviders)) {
prevProvidersRef.current = newProviders;
setWalletProviders(newProviders);
}
// we do nothing if the wallet providers are the same
}, []);
return [walletProviders, updateWalletProviders];
}
export { APPLE_AUTH_URL, FACEBOOK_AUTH_URL, FACEBOOK_GRAPH_URL, GOOGLE_AUTH_URL, SESSION_WARNING_THRESHOLD_MS, exchangeCodeForToken, generateChallengePair, handleFacebookPKCEFlow, isValidSession, isWalletConnect, parseOAuthRedirect, popupHeight, popupWidth, useDebouncedCallback, useScreenSize, useWalletProviderState, withTurnkeyErrorHandling };
//# sourceMappingURL=utils.mjs.map