@web3auth/no-modal
Version:
Multi chain wallet aggregator for web3Auth
360 lines (356 loc) • 18.3 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { SafeEventEmitter } from '@web3auth/auth';
import { LOGIN_PROVIDER } from '@web3auth/auth-adapter';
import { ADAPTER_STATUS, WalletInitializationError, log, CHAIN_NAMESPACES, storageAvailable, getChainConfig, fetchProjectConfig, WALLET_ADAPTERS, cloneDeep, ADAPTER_EVENTS, ADAPTER_NAMESPACES, WalletLoginError, PLUGIN_NAMESPACES, PLUGIN_STATUS } from '@web3auth/base';
import { CommonJRPCProvider } from '@web3auth/base-provider';
import deepmerge from 'deepmerge';
const ADAPTER_CACHE_KEY = "Web3Auth-cachedAdapter";
class Web3AuthNoModal extends SafeEventEmitter {
constructor(options) {
var _options$chainConfig, _options$chainConfig2, _options$chainConfig3, _options$chainConfig4;
super();
_defineProperty(this, "coreOptions", void 0);
_defineProperty(this, "connectedAdapterName", null);
_defineProperty(this, "status", ADAPTER_STATUS.NOT_READY);
_defineProperty(this, "cachedAdapter", null);
_defineProperty(this, "walletAdapters", {});
_defineProperty(this, "commonJRPCProvider", null);
_defineProperty(this, "plugins", {});
_defineProperty(this, "storage", "localStorage");
if (!options.clientId) throw WalletInitializationError.invalidParams("Please provide a valid clientId in constructor");
if (options.enableLogging) log.enableAll();else log.setLevel("error");
if (!options.privateKeyProvider && !options.chainConfig) {
throw WalletInitializationError.invalidParams("Please provide chainConfig or privateKeyProvider");
}
options.chainConfig = options.chainConfig || options.privateKeyProvider.currentChainConfig;
if (!((_options$chainConfig = options.chainConfig) !== null && _options$chainConfig !== void 0 && _options$chainConfig.chainNamespace) || !Object.values(CHAIN_NAMESPACES).includes((_options$chainConfig2 = options.chainConfig) === null || _options$chainConfig2 === void 0 ? void 0 : _options$chainConfig2.chainNamespace)) throw WalletInitializationError.invalidParams("Please provide a valid chainNamespace in chainConfig");
if (options.storageKey === "session") this.storage = "sessionStorage";
this.cachedAdapter = storageAvailable(this.storage) ? window[this.storage].getItem(ADAPTER_CACHE_KEY) : null;
this.coreOptions = _objectSpread(_objectSpread({}, options), {}, {
chainConfig: _objectSpread(_objectSpread({}, getChainConfig((_options$chainConfig3 = options.chainConfig) === null || _options$chainConfig3 === void 0 ? void 0 : _options$chainConfig3.chainNamespace, (_options$chainConfig4 = options.chainConfig) === null || _options$chainConfig4 === void 0 ? void 0 : _options$chainConfig4.chainId, options.clientId) || {}), options.chainConfig)
});
this.subscribeToAdapterEvents = this.subscribeToAdapterEvents.bind(this);
}
get connected() {
return Boolean(this.connectedAdapterName);
}
get provider() {
if (this.status !== ADAPTER_STATUS.NOT_READY && this.commonJRPCProvider) {
return this.commonJRPCProvider;
}
return null;
}
set provider(_) {
throw new Error("Not implemented");
}
async init() {
this.commonJRPCProvider = await CommonJRPCProvider.getProviderInstance({
chainConfig: this.coreOptions.chainConfig
});
let projectConfig;
try {
var _this$coreOptions$acc;
projectConfig = await fetchProjectConfig(this.coreOptions.clientId, this.coreOptions.web3AuthNetwork, (_this$coreOptions$acc = this.coreOptions.accountAbstractionProvider) === null || _this$coreOptions$acc === void 0 ? void 0 : _this$coreOptions$acc.config.smartAccountInit.name);
} catch (e) {
log.error("Failed to fetch project configurations", e);
throw WalletInitializationError.notReady("failed to fetch project configurations", e);
}
const initPromises = Object.keys(this.walletAdapters).map(async adapterName => {
this.subscribeToAdapterEvents(this.walletAdapters[adapterName]);
// if adapter doesn't have any chain config yet then set it based on provided namespace and chainId.
// if no chainNamespace or chainId is being provided, it will connect with mainnet.
if (!this.walletAdapters[adapterName].chainConfigProxy) {
const providedChainConfig = this.coreOptions.chainConfig;
if (!providedChainConfig.chainNamespace) throw WalletInitializationError.invalidParams("Please provide chainNamespace in chainConfig");
this.walletAdapters[adapterName].setAdapterSettings({
chainConfig: providedChainConfig,
sessionTime: this.coreOptions.sessionTime,
clientId: this.coreOptions.clientId,
web3AuthNetwork: this.coreOptions.web3AuthNetwork,
useCoreKitKey: this.coreOptions.useCoreKitKey
});
} else {
this.walletAdapters[adapterName].setAdapterSettings({
sessionTime: this.coreOptions.sessionTime,
clientId: this.coreOptions.clientId,
web3AuthNetwork: this.coreOptions.web3AuthNetwork,
useCoreKitKey: this.coreOptions.useCoreKitKey
});
}
if (adapterName === WALLET_ADAPTERS.AUTH) {
const authAdapter = this.walletAdapters[adapterName];
const {
whitelabel
} = projectConfig;
this.coreOptions.uiConfig = deepmerge(cloneDeep(whitelabel || {}), this.coreOptions.uiConfig || {});
if (!this.coreOptions.uiConfig.mode) this.coreOptions.uiConfig.mode = "light";
const {
sms_otp_enabled: smsOtpEnabled,
whitelist,
key_export_enabled: keyExportEnabled
} = projectConfig;
if (smsOtpEnabled !== undefined) {
authAdapter.setAdapterSettings({
loginConfig: {
[LOGIN_PROVIDER.SMS_PASSWORDLESS]: {
showOnModal: smsOtpEnabled,
showOnDesktop: smsOtpEnabled,
showOnMobile: smsOtpEnabled,
showOnSocialBackupFactor: smsOtpEnabled
}
}
});
}
if (whitelist) {
authAdapter.setAdapterSettings({
originData: whitelist.signed_urls
});
}
if (typeof keyExportEnabled === "boolean") {
this.coreOptions.privateKeyProvider.setKeyExportFlag(keyExportEnabled);
// dont know if this is required or not.
this.commonJRPCProvider.setKeyExportFlag(keyExportEnabled);
}
if (this.coreOptions.privateKeyProvider) {
if (authAdapter.currentChainNamespace !== this.coreOptions.privateKeyProvider.currentChainConfig.chainNamespace) {
throw WalletInitializationError.incompatibleChainNameSpace("private key provider is not compatible with provided chainNamespace for auth adapter");
}
authAdapter.setAdapterSettings({
privateKeyProvider: this.coreOptions.privateKeyProvider
});
}
authAdapter.setAdapterSettings({
whiteLabel: this.coreOptions.uiConfig
});
if (!authAdapter.privateKeyProvider) {
throw WalletInitializationError.invalidParams("privateKeyProvider is required for auth adapter");
}
} else if (adapterName === WALLET_ADAPTERS.WALLET_CONNECT_V2) {
const walletConnectAdapter = this.walletAdapters[adapterName];
const {
wallet_connect_enabled: walletConnectEnabled,
wallet_connect_project_id: walletConnectProjectId
} = projectConfig;
if (walletConnectEnabled === false) {
throw WalletInitializationError.invalidParams("Please enable wallet connect v2 addon on dashboard");
}
if (!walletConnectProjectId) throw WalletInitializationError.invalidParams("Invalid wallet connect project id. Please configure it on the dashboard");
walletConnectAdapter.setAdapterSettings({
adapterSettings: {
walletConnectInitOptions: {
projectId: walletConnectProjectId
}
}
});
}
return this.walletAdapters[adapterName].init({
autoConnect: this.cachedAdapter === adapterName
}).catch(e => log.error(e, adapterName));
});
await Promise.all(initPromises);
if (this.status === ADAPTER_STATUS.NOT_READY) {
this.status = ADAPTER_STATUS.READY;
this.emit(ADAPTER_EVENTS.READY);
}
}
getAdapter(adapterName) {
return this.walletAdapters[adapterName] || null;
}
configureAdapter(adapter) {
this.checkInitRequirements();
const providedChainConfig = this.coreOptions.chainConfig;
if (!providedChainConfig.chainNamespace) throw WalletInitializationError.invalidParams("Please provide chainNamespace in chainConfig");
const adapterAlreadyExists = this.walletAdapters[adapter.name];
if (adapterAlreadyExists) throw WalletInitializationError.duplicateAdapterError(`Wallet adapter for ${adapter.name} already exists`);
if (adapter.adapterNamespace !== ADAPTER_NAMESPACES.MULTICHAIN && adapter.adapterNamespace !== providedChainConfig.chainNamespace) throw WalletInitializationError.incompatibleChainNameSpace(`This wallet adapter belongs to ${adapter.adapterNamespace} which is incompatible with currently used namespace: ${providedChainConfig.chainNamespace}`);
if (adapter.adapterNamespace === ADAPTER_NAMESPACES.MULTICHAIN && adapter.currentChainNamespace && providedChainConfig.chainNamespace !== adapter.currentChainNamespace) {
// chainConfig checks are already validated in constructor so using typecast is safe here.
adapter.setAdapterSettings({
chainConfig: providedChainConfig
});
}
this.walletAdapters[adapter.name] = adapter;
return this;
}
clearCache() {
if (!storageAvailable(this.storage)) return;
window[this.storage].removeItem(ADAPTER_CACHE_KEY);
this.cachedAdapter = null;
}
async addChain(chainConfig) {
if (this.status === ADAPTER_STATUS.CONNECTED && this.connectedAdapterName) return this.walletAdapters[this.connectedAdapterName].addChain(chainConfig);
if (this.commonJRPCProvider) {
return this.commonJRPCProvider.addChain(chainConfig);
}
throw WalletInitializationError.notReady(`No wallet is ready`);
}
async switchChain(params) {
if (this.status === ADAPTER_STATUS.CONNECTED && this.connectedAdapterName) return this.walletAdapters[this.connectedAdapterName].switchChain(params);
if (this.commonJRPCProvider) {
return this.commonJRPCProvider.switchChain(params);
}
throw WalletInitializationError.notReady(`No wallet is ready`);
}
/**
* Connect to a specific wallet adapter
* @param walletName - Key of the walletAdapter to use.
*/
async connectTo(walletName, loginParams) {
if (!this.walletAdapters[walletName] || !this.commonJRPCProvider) throw WalletInitializationError.notFound(`Please add wallet adapter for ${walletName} wallet, before connecting`);
return new Promise((resolve, reject) => {
this.once(ADAPTER_EVENTS.CONNECTED, _ => {
resolve(this.provider);
});
this.once(ADAPTER_EVENTS.ERRORED, err => {
reject(err);
});
this.walletAdapters[walletName].connect(loginParams);
});
}
async logout(options = {
cleanup: false
}) {
if (this.status !== ADAPTER_STATUS.CONNECTED || !this.connectedAdapterName) throw WalletLoginError.notConnectedError(`No wallet is connected`);
await this.walletAdapters[this.connectedAdapterName].disconnect(options);
}
async getUserInfo() {
log.debug("Getting user info", this.status, this.connectedAdapterName);
if (this.status !== ADAPTER_STATUS.CONNECTED || !this.connectedAdapterName) throw WalletLoginError.notConnectedError(`No wallet is connected`);
return this.walletAdapters[this.connectedAdapterName].getUserInfo();
}
async enableMFA(loginParams) {
if (this.status !== ADAPTER_STATUS.CONNECTED || !this.connectedAdapterName) throw WalletLoginError.notConnectedError(`No wallet is connected`);
if (this.connectedAdapterName !== WALLET_ADAPTERS.AUTH) throw WalletLoginError.unsupportedOperation(`EnableMFA is not supported for this adapter.`);
return this.walletAdapters[this.connectedAdapterName].enableMFA(loginParams);
}
async manageMFA(loginParams) {
if (this.status !== ADAPTER_STATUS.CONNECTED || !this.connectedAdapterName) throw WalletLoginError.notConnectedError(`No wallet is connected`);
if (this.connectedAdapterName !== WALLET_ADAPTERS.AUTH) throw WalletLoginError.unsupportedOperation(`ManageMFA is not supported for this adapter.`);
return this.walletAdapters[this.connectedAdapterName].manageMFA(loginParams);
}
async authenticateUser() {
if (this.status !== ADAPTER_STATUS.CONNECTED || !this.connectedAdapterName) throw WalletLoginError.notConnectedError(`No wallet is connected`);
return this.walletAdapters[this.connectedAdapterName].authenticateUser();
}
addPlugin(plugin) {
if (this.plugins[plugin.name]) throw WalletInitializationError.duplicateAdapterError(`Plugin ${plugin.name} already exist`);
if (plugin.pluginNamespace !== PLUGIN_NAMESPACES.MULTICHAIN && plugin.pluginNamespace !== this.coreOptions.chainConfig.chainNamespace) throw WalletInitializationError.incompatibleChainNameSpace(`This plugin belongs to ${plugin.pluginNamespace} namespace which is incompatible with currently used namespace: ${this.coreOptions.chainConfig.chainNamespace}`);
this.plugins[plugin.name] = plugin;
if (this.status === ADAPTER_STATUS.CONNECTED && this.connectedAdapterName) {
// web3auth is already connected. can initialize plugins
this.connectToPlugins({
adapter: this.connectedAdapterName
});
}
return this;
}
getPlugin(name) {
return this.plugins[name] || null;
}
subscribeToAdapterEvents(walletAdapter) {
walletAdapter.on(ADAPTER_EVENTS.CONNECTED, async data => {
if (!this.commonJRPCProvider) throw WalletInitializationError.notFound(`CommonJrpcProvider not found`);
const {
provider
} = data;
let finalProvider = provider.provider || provider;
// setup aa provider after adapter is connected and private key provider is setup
if (this.coreOptions.accountAbstractionProvider && (data.adapter === WALLET_ADAPTERS.AUTH || data.adapter !== WALLET_ADAPTERS.AUTH && this.coreOptions.useAAWithExternalWallet)) {
await this.coreOptions.accountAbstractionProvider.setupProvider(provider); // Don't change this to finalProvider
finalProvider = this.coreOptions.accountAbstractionProvider;
}
this.commonJRPCProvider.updateProviderEngineProxy(finalProvider);
this.connectedAdapterName = data.adapter;
this.status = ADAPTER_STATUS.CONNECTED;
this.cacheWallet(data.adapter);
log.debug("connected", this.status, this.connectedAdapterName);
this.connectToPlugins(data);
this.emit(ADAPTER_EVENTS.CONNECTED, _objectSpread({}, data));
});
walletAdapter.on(ADAPTER_EVENTS.DISCONNECTED, async () => {
// get back to ready state for rehydrating.
this.status = ADAPTER_STATUS.READY;
if (storageAvailable(this.storage)) {
const cachedAdapter = window[this.storage].getItem(ADAPTER_CACHE_KEY);
if (this.connectedAdapterName === cachedAdapter) {
this.clearCache();
}
}
log.debug("disconnected", this.status, this.connectedAdapterName);
await Promise.all(Object.values(this.plugins).map(plugin => {
return plugin.disconnect().catch(error => {
// swallow error if adapter doesn't supports this plugin.
if (error.code === 5211) {
return;
}
// throw error;
log.error(error);
});
}));
this.connectedAdapterName = null;
this.emit(ADAPTER_EVENTS.DISCONNECTED);
});
walletAdapter.on(ADAPTER_EVENTS.CONNECTING, data => {
this.status = ADAPTER_STATUS.CONNECTING;
this.emit(ADAPTER_EVENTS.CONNECTING, data);
log.debug("connecting", this.status, this.connectedAdapterName);
});
walletAdapter.on(ADAPTER_EVENTS.ERRORED, data => {
this.status = ADAPTER_STATUS.ERRORED;
this.clearCache();
this.emit(ADAPTER_EVENTS.ERRORED, data);
log.debug("errored", this.status, this.connectedAdapterName);
});
walletAdapter.on(ADAPTER_EVENTS.ADAPTER_DATA_UPDATED, data => {
log.debug("adapter data updated", data);
this.emit(ADAPTER_EVENTS.ADAPTER_DATA_UPDATED, data);
});
walletAdapter.on(ADAPTER_EVENTS.CACHE_CLEAR, data => {
log.debug("adapter cache clear", data);
if (storageAvailable(this.storage)) {
this.clearCache();
}
});
}
checkInitRequirements() {
if (this.status === ADAPTER_STATUS.CONNECTING) throw WalletInitializationError.notReady("Already pending connection");
if (this.status === ADAPTER_STATUS.CONNECTED) throw WalletInitializationError.notReady("Already connected");
if (this.status === ADAPTER_STATUS.READY) throw WalletInitializationError.notReady("Adapter is already initialized");
}
cacheWallet(walletName) {
if (!storageAvailable(this.storage)) return;
window[this.storage].setItem(ADAPTER_CACHE_KEY, walletName);
this.cachedAdapter = walletName;
}
connectToPlugins(data) {
Object.values(this.plugins).map(async plugin => {
try {
if (!plugin.SUPPORTED_ADAPTERS.includes("all") && !plugin.SUPPORTED_ADAPTERS.includes(data.adapter)) {
return;
}
if (plugin.status === PLUGIN_STATUS.CONNECTED) return;
const {
authInstance
} = this.walletAdapters[this.connectedAdapterName];
const {
options,
sessionId,
sessionNamespace
} = authInstance || {};
await plugin.initWithWeb3Auth(this, options === null || options === void 0 ? void 0 : options.whiteLabel);
await plugin.connect({
sessionId,
sessionNamespace
});
} catch (error) {
// swallow error if connector adapter doesn't supports this plugin.
if (error.code === 5211) {
return;
}
log.error(error);
}
});
}
}
export { Web3AuthNoModal };