@reown/appkit-controllers
Version:
#### 🔗 [Website](https://reown.com/appkit)
348 lines • 15 kB
JavaScript
import { proxy } from 'valtio/vanilla';
import { subscribeKey as subKey } from 'valtio/vanilla/utils';
import { ConstantsUtil } from '@reown/appkit-common';
import { AssetUtil } from '../utils/AssetUtil.js';
import { CoreHelperUtil } from '../utils/CoreHelperUtil.js';
import { FetchUtil } from '../utils/FetchUtil.js';
import { CUSTOM_DEEPLINK_WALLETS } from '../utils/MobileWallet.js';
import { StorageUtil } from '../utils/StorageUtil.js';
import { AssetController } from './AssetController.js';
import { ChainController } from './ChainController.js';
import { ConnectorController } from './ConnectorController.js';
import { EventsController } from './EventsController.js';
import { OptionsController } from './OptionsController.js';
// -- Helpers ------------------------------------------- //
const baseUrl = CoreHelperUtil.getApiUrl();
export const api = new FetchUtil({
baseUrl,
clientId: null
});
const entries = 40;
const recommendedEntries = 4;
const imageCountToFetch = 20;
// -- State --------------------------------------------- //
const state = proxy({
promises: {},
page: 1,
count: 0,
featured: [],
allFeatured: [],
recommended: [],
allRecommended: [],
wallets: [],
filteredWallets: [],
search: [],
isAnalyticsEnabled: false,
excludedWallets: [],
isFetchingRecommendedWallets: false
});
// -- Controller ---------------------------------------- //
export const ApiController = {
state,
subscribeKey(key, callback) {
return subKey(state, key, callback);
},
_getSdkProperties() {
const { projectId, sdkType, sdkVersion } = OptionsController.state;
return {
projectId,
st: sdkType || 'appkit',
sv: sdkVersion || 'html-wagmi-4.2.2'
};
},
_filterOutExtensions(wallets) {
if (OptionsController.state.isUniversalProvider) {
return wallets.filter(w => Boolean(w.mobile_link || w.desktop_link || w.webapp_link));
}
return wallets;
},
async _fetchWalletImage(imageId) {
const imageUrl = `${api.baseUrl}/getWalletImage/${imageId}`;
const blob = await api.getBlob({ path: imageUrl, params: ApiController._getSdkProperties() });
AssetController.setWalletImage(imageId, URL.createObjectURL(blob));
},
async _fetchNetworkImage(imageId) {
const imageUrl = `${api.baseUrl}/public/getAssetImage/${imageId}`;
const blob = await api.getBlob({ path: imageUrl, params: ApiController._getSdkProperties() });
AssetController.setNetworkImage(imageId, URL.createObjectURL(blob));
},
async _fetchConnectorImage(imageId) {
const imageUrl = `${api.baseUrl}/public/getAssetImage/${imageId}`;
const blob = await api.getBlob({ path: imageUrl, params: ApiController._getSdkProperties() });
AssetController.setConnectorImage(imageId, URL.createObjectURL(blob));
},
async _fetchCurrencyImage(countryCode) {
const imageUrl = `${api.baseUrl}/public/getCurrencyImage/${countryCode}`;
const blob = await api.getBlob({ path: imageUrl, params: ApiController._getSdkProperties() });
AssetController.setCurrencyImage(countryCode, URL.createObjectURL(blob));
},
async _fetchTokenImage(symbol) {
const imageUrl = `${api.baseUrl}/public/getTokenImage/${symbol}`;
const blob = await api.getBlob({ path: imageUrl, params: ApiController._getSdkProperties() });
AssetController.setTokenImage(symbol, URL.createObjectURL(blob));
},
_filterWalletsByPlatform(wallets) {
const filteredWallets = CoreHelperUtil.isMobile()
? wallets?.filter(w => {
if (w.mobile_link) {
return true;
}
if (w.id === CUSTOM_DEEPLINK_WALLETS.COINBASE.id) {
return true;
}
const isSolana = ChainController.state.activeChain === 'solana';
return (isSolana &&
(w.id === CUSTOM_DEEPLINK_WALLETS.SOLFLARE.id ||
w.id === CUSTOM_DEEPLINK_WALLETS.PHANTOM.id));
})
: wallets;
return filteredWallets;
},
async fetchProjectConfig() {
const response = await api.get({
path: '/appkit/v1/config',
params: ApiController._getSdkProperties()
});
return response.features;
},
async fetchAllowedOrigins() {
try {
const { allowedOrigins } = await api.get({
path: '/projects/v1/origins',
params: ApiController._getSdkProperties()
});
return allowedOrigins;
}
catch (error) {
if (error instanceof Error && error.cause instanceof Response) {
const status = error.cause.status;
if (status === ConstantsUtil.HTTP_STATUS_CODES.TOO_MANY_REQUESTS) {
throw new Error('RATE_LIMITED', { cause: error });
}
if (status >= ConstantsUtil.HTTP_STATUS_CODES.SERVER_ERROR && status < 600) {
throw new Error('SERVER_ERROR', { cause: error });
}
return [];
}
return [];
}
},
async fetchNetworkImages() {
const requestedCaipNetworks = ChainController.getAllRequestedCaipNetworks();
const ids = requestedCaipNetworks
?.map(({ assets }) => assets?.imageId)
.filter(Boolean)
.filter(imageId => !AssetUtil.getNetworkImageById(imageId));
if (ids) {
await Promise.allSettled(ids.map(id => ApiController._fetchNetworkImage(id)));
}
},
async fetchConnectorImages() {
const { connectors } = ConnectorController.state;
const ids = connectors.map(({ imageId }) => imageId).filter(Boolean);
await Promise.allSettled(ids.map(id => ApiController._fetchConnectorImage(id)));
},
async fetchCurrencyImages(currencies = []) {
await Promise.allSettled(currencies.map(currency => ApiController._fetchCurrencyImage(currency)));
},
async fetchTokenImages(tokens = []) {
await Promise.allSettled(tokens.map(token => ApiController._fetchTokenImage(token)));
},
async fetchWallets(params) {
const exclude = params.exclude ?? [];
const sdkProperties = ApiController._getSdkProperties();
if (sdkProperties.sv.startsWith('html-core-')) {
exclude.push(...Object.values(CUSTOM_DEEPLINK_WALLETS).map(w => w.id));
}
const wallets = await api.get({
path: '/getWallets',
params: {
...ApiController._getSdkProperties(),
...params,
page: String(params.page),
entries: String(params.entries),
include: params.include?.join(','),
exclude: exclude.join(',')
}
});
const filteredWallets = ApiController._filterWalletsByPlatform(wallets?.data);
return {
data: filteredWallets || [],
// Keep original count for display on main page
count: wallets?.count
};
},
async fetchFeaturedWallets() {
const { featuredWalletIds } = OptionsController.state;
if (featuredWalletIds?.length) {
const params = {
...ApiController._getSdkProperties(),
page: 1,
entries: featuredWalletIds?.length ?? recommendedEntries,
include: featuredWalletIds
};
const { data } = await ApiController.fetchWallets(params);
const sortedData = [...data].sort((a, b) => featuredWalletIds.indexOf(a.id) - featuredWalletIds.indexOf(b.id));
const images = sortedData.map(d => d.image_id).filter(Boolean);
await Promise.allSettled(images.map(id => ApiController._fetchWalletImage(id)));
state.featured = sortedData;
state.allFeatured = sortedData;
}
},
async fetchRecommendedWallets() {
try {
state.isFetchingRecommendedWallets = true;
const { includeWalletIds, excludeWalletIds, featuredWalletIds } = OptionsController.state;
const exclude = [...(excludeWalletIds ?? []), ...(featuredWalletIds ?? [])].filter(Boolean);
const chains = ChainController.getRequestedCaipNetworkIds().join(',');
const params = {
page: 1,
entries: recommendedEntries,
include: includeWalletIds,
exclude,
chains
};
const { data, count } = await ApiController.fetchWallets(params);
const recent = StorageUtil.getRecentWallets();
const recommendedImages = data.map(d => d.image_id).filter(Boolean);
const recentImages = recent.map(r => r.image_id).filter(Boolean);
await Promise.allSettled([...recommendedImages, ...recentImages].map(id => ApiController._fetchWalletImage(id)));
state.recommended = data;
state.allRecommended = data;
state.count = count ?? 0;
}
catch {
// Catch silently
}
finally {
state.isFetchingRecommendedWallets = false;
}
},
async fetchWalletsByPage({ page }) {
const { includeWalletIds, excludeWalletIds, featuredWalletIds } = OptionsController.state;
const chains = ChainController.getRequestedCaipNetworkIds().join(',');
const exclude = [
...state.recommended.map(({ id }) => id),
...(excludeWalletIds ?? []),
...(featuredWalletIds ?? [])
].filter(Boolean);
const params = {
page,
entries,
include: includeWalletIds,
exclude,
chains
};
const { data, count } = await ApiController.fetchWallets(params);
const images = data
.slice(0, imageCountToFetch)
.map(w => w.image_id)
.filter(Boolean);
await Promise.allSettled(images.map(id => ApiController._fetchWalletImage(id)));
state.wallets = CoreHelperUtil.uniqueBy([...state.wallets, ...ApiController._filterOutExtensions(data)], 'id').filter(w => w.chains?.some(chain => chains.includes(chain)));
state.count = count > state.count ? count : state.count;
state.page = page;
},
async initializeExcludedWallets({ ids }) {
const params = {
page: 1,
entries: ids.length,
include: ids
};
const { data } = await ApiController.fetchWallets(params);
if (data) {
data.forEach(wallet => {
state.excludedWallets.push({ rdns: wallet.rdns, name: wallet.name });
});
}
},
async searchWallet({ search, badge }) {
const { includeWalletIds, excludeWalletIds } = OptionsController.state;
const chains = ChainController.getRequestedCaipNetworkIds().join(',');
state.search = [];
const params = {
page: 1,
entries: 100,
search: search?.trim(),
badge_type: badge,
include: includeWalletIds,
exclude: excludeWalletIds,
chains
};
const { data } = await ApiController.fetchWallets(params);
EventsController.sendEvent({
type: 'track',
event: 'SEARCH_WALLET',
properties: { badge: badge ?? '', search: search ?? '' }
});
const images = data.map(w => w.image_id).filter(Boolean);
await Promise.allSettled([
...images.map(id => ApiController._fetchWalletImage(id)),
CoreHelperUtil.wait(300)
]);
state.search = ApiController._filterOutExtensions(data);
},
initPromise(key, fetchFn) {
const existingPromise = state.promises[key];
if (existingPromise) {
return existingPromise;
}
return (state.promises[key] = fetchFn());
},
prefetch({ fetchConnectorImages = true, fetchFeaturedWallets = true, fetchRecommendedWallets = true, fetchNetworkImages = true } = {}) {
const promises = [
fetchConnectorImages &&
ApiController.initPromise('connectorImages', ApiController.fetchConnectorImages),
fetchFeaturedWallets &&
ApiController.initPromise('featuredWallets', ApiController.fetchFeaturedWallets),
fetchRecommendedWallets &&
ApiController.initPromise('recommendedWallets', ApiController.fetchRecommendedWallets),
fetchNetworkImages &&
ApiController.initPromise('networkImages', ApiController.fetchNetworkImages)
].filter(Boolean);
return Promise.allSettled(promises);
},
prefetchAnalyticsConfig() {
if (OptionsController.state.features?.analytics) {
ApiController.fetchAnalyticsConfig();
}
},
async fetchAnalyticsConfig() {
try {
const { isAnalyticsEnabled } = await api.get({
path: '/getAnalyticsConfig',
params: ApiController._getSdkProperties()
});
OptionsController.setFeatures({ analytics: isAnalyticsEnabled });
}
catch (error) {
OptionsController.setFeatures({ analytics: false });
}
},
filterByNamespaces(namespaces) {
if (!namespaces?.length) {
state.featured = state.allFeatured;
state.recommended = state.allRecommended;
return;
}
const caipNetworkIds = ChainController.getRequestedCaipNetworkIds().join(',');
state.featured = state.allFeatured.filter(wallet => wallet.chains?.some(chain => caipNetworkIds.includes(chain)));
state.recommended = state.allRecommended.filter(wallet => wallet.chains?.some(chain => caipNetworkIds.includes(chain)));
state.filteredWallets = state.wallets.filter(wallet => wallet.chains?.some(chain => caipNetworkIds.includes(chain)));
},
clearFilterByNamespaces() {
state.filteredWallets = [];
},
setFilterByNamespace(namespace) {
if (!namespace) {
state.featured = state.allFeatured;
state.recommended = state.allRecommended;
return;
}
const caipNetworkIds = ChainController.getRequestedCaipNetworkIds().join(',');
state.featured = state.allFeatured.filter(wallet => wallet.chains?.some(chain => caipNetworkIds.includes(chain)));
state.recommended = state.allRecommended.filter(wallet => wallet.chains?.some(chain => caipNetworkIds.includes(chain)));
state.filteredWallets = state.wallets.filter(wallet => wallet.chains?.some(chain => caipNetworkIds.includes(chain)));
}
};
//# sourceMappingURL=ApiController.js.map