@reown/appkit-controllers
Version:
#### 🔗 [Website](https://reown.com/appkit)
390 lines • 14.2 kB
JavaScript
import { ConstantsUtil as CommonConstants } from '@reown/appkit-common';
import { ConstantsUtil } from './ConstantsUtil.js';
import { StorageUtil } from './StorageUtil.js';
export const CoreHelperUtil = {
isMobile() {
if (this.isClient()) {
return Boolean((window?.matchMedia &&
typeof window.matchMedia === 'function' &&
window.matchMedia('(pointer:coarse)')?.matches) ||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini/u.test(navigator.userAgent));
}
return false;
},
checkCaipNetwork(network, networkName = '') {
return network?.caipNetworkId.toLocaleLowerCase().includes(networkName.toLowerCase());
},
isAndroid() {
if (!this.isMobile()) {
return false;
}
const ua = window?.navigator.userAgent.toLowerCase();
return CoreHelperUtil.isMobile() && ua.includes('android');
},
isIos() {
if (!this.isMobile()) {
return false;
}
const ua = window?.navigator.userAgent.toLowerCase();
return ua.includes('iphone') || ua.includes('ipad');
},
isSafari() {
if (!this.isClient()) {
return false;
}
const ua = window?.navigator.userAgent.toLowerCase();
return ua.includes('safari');
},
isClient() {
return typeof window !== 'undefined';
},
isPairingExpired(expiry) {
return expiry ? expiry - Date.now() <= ConstantsUtil.TEN_SEC_MS : true;
},
isAllowedRetry(lastRetry, differenceMs = ConstantsUtil.ONE_SEC_MS) {
return Date.now() - lastRetry >= differenceMs;
},
copyToClopboard(text) {
navigator.clipboard.writeText(text);
},
isIframe() {
try {
return window?.self !== window?.top;
}
catch (e) {
return false;
}
},
isSafeApp() {
if (CoreHelperUtil.isClient() && window.self !== window.top) {
try {
const ancestor = window?.location?.ancestorOrigins?.[0];
const safeAppUrl = 'https://app.safe.global';
if (ancestor) {
const ancestorUrl = new URL(ancestor);
const safeUrl = new URL(safeAppUrl);
return ancestorUrl.hostname === safeUrl.hostname;
}
}
catch {
return false;
}
}
return false;
},
getPairingExpiry() {
return Date.now() + ConstantsUtil.FOUR_MINUTES_MS;
},
getNetworkId(caipAddress) {
return caipAddress?.split(':')[1];
},
getPlainAddress(caipAddress) {
return caipAddress?.split(':')[2];
},
async wait(milliseconds) {
return new Promise(resolve => {
setTimeout(resolve, milliseconds);
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debounce(func, timeout = 500) {
let timer = undefined;
return (...args) => {
function next() {
func(...args);
}
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(next, timeout);
};
},
isHttpUrl(url) {
return url.startsWith('http://') || url.startsWith('https://');
},
formatNativeUrl(appUrl, wcUri, universalLink = null) {
if (CoreHelperUtil.isHttpUrl(appUrl)) {
return this.formatUniversalUrl(appUrl, wcUri);
}
let safeAppUrl = appUrl;
let safeUniversalLink = universalLink;
if (!safeAppUrl.includes('://')) {
safeAppUrl = appUrl.replaceAll('/', '').replaceAll(':', '');
safeAppUrl = `${safeAppUrl}://`;
}
if (!safeAppUrl.endsWith('/')) {
safeAppUrl = `${safeAppUrl}/`;
}
if (safeUniversalLink && !safeUniversalLink?.endsWith('/')) {
safeUniversalLink = `${safeUniversalLink}/`;
}
// Android deeplinks in tg context require the uri to be encoded twice
if (this.isTelegram() && this.isAndroid()) {
// eslint-disable-next-line no-param-reassign
wcUri = encodeURIComponent(wcUri);
}
const encodedWcUrl = encodeURIComponent(wcUri);
return {
redirect: `${safeAppUrl}wc?uri=${encodedWcUrl}`,
redirectUniversalLink: safeUniversalLink
? `${safeUniversalLink}wc?uri=${encodedWcUrl}`
: undefined,
href: safeAppUrl
};
},
formatUniversalUrl(appUrl, wcUri) {
if (!CoreHelperUtil.isHttpUrl(appUrl)) {
return this.formatNativeUrl(appUrl, wcUri);
}
let safeAppUrl = appUrl;
if (!safeAppUrl.endsWith('/')) {
safeAppUrl = `${safeAppUrl}/`;
}
const encodedWcUrl = encodeURIComponent(wcUri);
return {
redirect: `${safeAppUrl}wc?uri=${encodedWcUrl}`,
href: safeAppUrl
};
},
getOpenTargetForPlatform(target) {
if (target === 'popupWindow') {
return target;
}
// Only '_blank' deeplinks work in Telegram context
if (this.isTelegram()) {
// But for social login, we need to load the page in the same context
if (StorageUtil.getTelegramSocialProvider()) {
return '_top';
}
return '_blank';
}
return target;
},
openHref(href, target, features) {
window?.open(href, this.getOpenTargetForPlatform(target), features || 'noreferrer noopener');
},
returnOpenHref(href, target, features) {
return window?.open(href, this.getOpenTargetForPlatform(target), features || 'noreferrer noopener');
},
isTelegram() {
return (typeof window !== 'undefined' &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(Boolean(window.TelegramWebviewProxy) ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Boolean(window.Telegram) ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Boolean(window.TelegramWebviewProxyProto)));
},
isPWA() {
if (typeof window === 'undefined') {
return false;
}
const isStandaloneDisplayMode = window?.matchMedia && typeof window.matchMedia === 'function'
? window.matchMedia('(display-mode: standalone)')?.matches
: false;
const isIOSStandalone = window?.navigator?.standalone;
return Boolean(isStandaloneDisplayMode || isIOSStandalone);
},
async preloadImage(src) {
const imagePromise = new Promise((resolve, reject) => {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.crossOrigin = 'anonymous';
image.src = src;
});
return Promise.race([imagePromise, CoreHelperUtil.wait(2000)]);
},
formatBalance(balance, symbol) {
let formattedBalance = '0.000';
if (typeof balance === 'string') {
const number = Number(balance);
if (number) {
const formattedValue = Math.floor(number * 1000) / 1000;
if (formattedValue) {
formattedBalance = formattedValue.toString();
}
}
}
return `${formattedBalance}${symbol ? ` ${symbol}` : ''}`;
},
formatBalance2(balance, symbol) {
let formattedBalance = undefined;
if (balance === '0') {
formattedBalance = '0';
}
else if (typeof balance === 'string') {
const number = Number(balance);
if (number) {
formattedBalance = number.toString().match(/^-?\d+(?:\.\d{0,3})?/u)?.[0];
}
}
return {
value: formattedBalance ?? '0',
rest: formattedBalance === '0' ? '000' : '',
symbol
};
},
getApiUrl() {
return CommonConstants.W3M_API_URL;
},
getBlockchainApiUrl() {
return CommonConstants.BLOCKCHAIN_API_RPC_URL;
},
getAnalyticsUrl() {
return CommonConstants.PULSE_API_URL;
},
getUUID() {
if (crypto?.randomUUID) {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/gu, c => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parseError(error) {
if (typeof error === 'string') {
return error;
}
else if (typeof error?.issues?.[0]?.message === 'string') {
return error.issues[0].message;
}
else if (error instanceof Error) {
return error.message;
}
return 'Unknown error';
},
sortRequestedNetworks(approvedIds, requestedNetworks = []) {
const approvedIndexMap = {};
if (requestedNetworks && approvedIds) {
approvedIds.forEach((id, index) => {
approvedIndexMap[id] = index;
});
requestedNetworks.sort((a, b) => {
const indexA = approvedIndexMap[a.id];
const indexB = approvedIndexMap[b.id];
if (indexA !== undefined && indexB !== undefined) {
return indexA - indexB;
}
else if (indexA !== undefined) {
return -1;
}
else if (indexB !== undefined) {
return 1;
}
return 0;
});
}
return requestedNetworks;
},
calculateBalance(array) {
let sum = 0;
for (const item of array) {
sum += item.value ?? 0;
}
return sum;
},
formatTokenBalance(number) {
const roundedNumber = number.toFixed(2);
const [dollars, pennies] = roundedNumber.split('.');
return { dollars, pennies };
},
isAddress(address, chain = 'eip155') {
switch (chain) {
case 'eip155':
if (!/^(?:0x)?[0-9a-f]{40}$/iu.test(address)) {
return false;
}
else if (/^(?:0x)?[0-9a-f]{40}$/iu.test(address) ||
/^(?:0x)?[0-9A-F]{40}$/iu.test(address)) {
return true;
}
return false;
case 'solana':
return /[1-9A-HJ-NP-Za-km-z]{32,44}$/iu.test(address);
default:
return false;
}
},
uniqueBy(arr, key) {
const set = new Set();
return arr.filter(item => {
const keyValue = item[key];
if (set.has(keyValue)) {
return false;
}
set.add(keyValue);
return true;
});
},
generateSdkVersion(adapters, platform, version) {
const hasNoAdapters = adapters.length === 0;
const adapterNames = (hasNoAdapters
? ConstantsUtil.ADAPTER_TYPES.UNIVERSAL
: adapters.map(adapter => adapter.adapterType).join(','));
return `${platform}-${adapterNames}-${version}`;
},
// eslint-disable-next-line max-params
createAccount(namespace, address, type, publicKey, path) {
return {
namespace,
address,
type,
publicKey,
path
};
},
isCaipAddress(address) {
if (typeof address !== 'string') {
return false;
}
const sections = address.split(':');
const namespace = sections[0];
return (sections.filter(Boolean).length === 3 &&
namespace in CommonConstants.CHAIN_NAME_MAP);
},
isMac() {
const ua = window?.navigator.userAgent.toLowerCase();
return ua.includes('macintosh') && !ua.includes('safari');
},
formatTelegramSocialLoginUrl(url) {
const valueToInject = `--${encodeURIComponent(window?.location.href)}`;
const paramToInject = 'state=';
const parsedUrl = new URL(url);
if (parsedUrl.host === 'auth.magic.link') {
const providerParam = 'provider_authorization_url=';
const providerUrl = url.substring(url.indexOf(providerParam) + providerParam.length);
const resultUrl = this.injectIntoUrl(decodeURIComponent(providerUrl), paramToInject, valueToInject);
return url.replace(providerUrl, encodeURIComponent(resultUrl));
}
return this.injectIntoUrl(url, paramToInject, valueToInject);
},
injectIntoUrl(url, key, appendString) {
// Find the position of "key" e.g. "state=" in the URL
const keyIndex = url.indexOf(key);
if (keyIndex === -1) {
throw new Error(`${key} parameter not found in the URL: ${url}`);
}
// Find the position of the next "&" after "key"
const keyEndIndex = url.indexOf('&', keyIndex);
const keyLength = key.length;
// If there is no "&" after key, it means "key" is the last parameter
// eslint-disable-next-line no-negated-condition
const keyParamEnd = keyEndIndex !== -1 ? keyEndIndex : url.length;
// Extract the part of the URL before the key value
const beforeKeyValue = url.substring(0, keyIndex + keyLength);
// Extract the current key value
const currentKeyValue = url.substring(keyIndex + keyLength, keyParamEnd);
// Extract the part of the URL after the key value
const afterKeyValue = url.substring(keyEndIndex);
// Append the new string to the key value
const newKeyValue = currentKeyValue + appendString;
// Reconstruct the URL with the appended key value
const newUrl = beforeKeyValue + newKeyValue + afterKeyValue;
return newUrl;
}
};
//# sourceMappingURL=CoreHelperUtil.js.map