UNPKG

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