@turnkey/react-wallet-kit
Version:
The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.
122 lines (118 loc) • 5.43 kB
JavaScript
;
var sdkTypes = require('@turnkey/sdk-types');
var react = require('react');
var core = require('@turnkey/core');
const SESSION_WARNING_THRESHOLD_MS = 60 * 1000; // 1 minute in milliseconds
const useDebouncedCallback = (fn, wait = 100) => {
const timer = react.useRef(null);
const fnRef = react.useRef(fn);
fnRef.current = fn;
return react.useCallback((...args) => {
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => {
fnRef.current(...args);
timer.current = null;
}, wait);
}, [wait]);
};
const isValidSession = session => {
return session?.expiry !== undefined && session.expiry * 1000 > Date.now();
};
async function withTurnkeyErrorHandling(fn, sessionExpireFn, callbacks, fallbackMessage = "An unknown error occurred", fallbackCode = sdkTypes.TurnkeyErrorCodes.UNKNOWN) {
try {
return await fn();
} catch (error) {
let tkError;
if (error instanceof sdkTypes.TurnkeyError) {
tkError = error;
if (tkError.code === sdkTypes.TurnkeyErrorCodes.SESSION_EXPIRED) {
if (sessionExpireFn) {
await sessionExpireFn();
} else {
// this should never happen. If it does, it means an SDK function is making
// a session-based API call without providing a `sessionExpireFn()` for handling
// SESSION_EXPIRED errors in `withTurnkeyErrorHandling()`
throw new sdkTypes.TurnkeyError("SESSION_EXPIRED received without sessionExpireFn handler", sdkTypes.TurnkeyErrorCodes.INTERNAL_ERROR, error);
}
}
// skip onError for WalletConnect expired errors
if (tkError.code !== sdkTypes.TurnkeyErrorCodes.WALLET_CONNECT_EXPIRED) {
callbacks?.onError?.(tkError);
}
throw tkError;
}
// we wrap non-Turnkey errors
tkError = new sdkTypes.TurnkeyError(fallbackMessage, fallbackCode, error);
callbacks?.onError?.(tkError);
throw tkError;
}
}
// Custom hook to get the current screen size
function useScreenSize() {
const [width, setWidth] = react.useState(typeof window !== "undefined" ? window.innerWidth : 1024);
const [height, setHeight] = react.useState(typeof window !== "undefined" ? window.innerHeight : 768);
react.useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return {
width,
height,
// 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 == core.WalletInterfaceType.WalletConnect;
}
// Finds and returns the WalletConnect provider for the specified chain
function findWalletConnectProvider(walletProviders, chain) {
return walletProviders.find(p => p.interfaceType === core.WalletInterfaceType.WalletConnect && p.chainInfo.namespace === chain);
}
function useWalletProviderState(initialState = []) {
const [walletProviders, setWalletProviders] = react.useState(initialState);
const prevProvidersRef = react.useRef(initialState);
function isSameWalletProvider(a, b) {
if (a.length !== b.length) return false;
const key = provider => {
const name = provider.info.name;
const namespace = provider.chainInfo.namespace;
const interfaceType = provider.interfaceType;
const connectedAddresses = [...provider.connectedAddresses].map(x => x.toLowerCase()).sort().join(",");
const uri = provider.uri || "";
return `${namespace}|${interfaceType}|${name}|${connectedAddresses}|${uri}`;
};
const A = a.map(key).sort();
const B = b.map(key).sort();
for (let i = 0; i < A.length; i++) if (A[i] !== B[i]) return false;
return true;
}
const updateWalletProviders = react.useCallback(newProviders => {
if (!isSameWalletProvider(prevProvidersRef.current, newProviders)) {
prevProvidersRef.current = newProviders;
setWalletProviders(newProviders);
}
// we do nothing if the wallet providers are the same
}, []);
return [walletProviders, updateWalletProviders];
}
function mergeWalletsWithoutDuplicates(existingWallets, newWallets) {
const existingWalletIds = new Set(existingWallets.map(w => w.walletId));
const uniqueNewWallets = newWallets.filter(w => !existingWalletIds.has(w.walletId));
return [...existingWallets, ...uniqueNewWallets];
}
exports.SESSION_WARNING_THRESHOLD_MS = SESSION_WARNING_THRESHOLD_MS;
exports.findWalletConnectProvider = findWalletConnectProvider;
exports.isValidSession = isValidSession;
exports.isWalletConnect = isWalletConnect;
exports.mergeWalletsWithoutDuplicates = mergeWalletsWithoutDuplicates;
exports.useDebouncedCallback = useDebouncedCallback;
exports.useScreenSize = useScreenSize;
exports.useWalletProviderState = useWalletProviderState;
exports.withTurnkeyErrorHandling = withTurnkeyErrorHandling;
//# sourceMappingURL=utils.js.map