UNPKG

@web3auth/no-modal

Version:
259 lines (249 loc) 10.8 kB
import _objectSpread from '@babel/runtime/helpers/objectSpread2'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { MetaMaskSDK } from '@metamask/sdk'; import { CHAIN_NAMESPACES, getErrorAnalyticsProperties } from '@toruslabs/base-controllers'; import deepmerge from 'deepmerge'; import { getSiteIcon, getSiteName } from '../utils.js'; import { getCaipChainId } from '../../base/utils.js'; import { ANALYTICS_EVENTS } from '../../base/analytics.js'; import { BaseEvmConnector } from '../base-evm-connector/baseEvmConnector.js'; import { CONNECTOR_NAMESPACES } from '../../base/chain/IChainInterface.js'; import { CONNECTOR_CATEGORY, CONNECTOR_STATUS, CONNECTOR_EVENTS } from '../../base/connector/constants.js'; import { WALLET_CONNECTORS } from '../../base/wallet/index.js'; import { WalletLoginError, Web3AuthError } from '../../base/errors/index.js'; class MetaMaskConnector extends BaseEvmConnector { constructor(connectorOptions) { super(connectorOptions); _defineProperty(this, "connectorNamespace", CONNECTOR_NAMESPACES.EIP155); _defineProperty(this, "currentChainNamespace", CHAIN_NAMESPACES.EIP155); _defineProperty(this, "type", CONNECTOR_CATEGORY.EXTERNAL); _defineProperty(this, "name", WALLET_CONNECTORS.METAMASK); _defineProperty(this, "status", CONNECTOR_STATUS.NOT_READY); _defineProperty(this, "metamaskProvider", null); _defineProperty(this, "metamaskSDK", null); _defineProperty(this, "metamaskOptions", void 0); _defineProperty(this, "analytics", void 0); this.metamaskOptions = connectorOptions.connectorSettings; this.analytics = connectorOptions.analytics; } get provider() { if (this.status !== CONNECTOR_STATUS.NOT_READY && this.metamaskProvider) { return this.metamaskProvider; } return null; } set provider(_) { throw new Error("Not implemented"); } async init(options) { var _metamaskOptions$useD; await super.init(options); const chainConfig = this.coreOptions.chains.find(x => x.chainId === options.chainId); super.checkInitializationRequirements({ chainConfig }); // Detect app metadata const iconUrl = await getSiteIcon(window); // TODO: handle ssr const appMetadata = { name: getSiteName(window) || "web3auth", url: window.location.origin || "https://web3auth.io", iconUrl }; // initialize the MetaMask SDK const metamaskOptions = deepmerge(this.metamaskOptions || {}, { dappMetadata: appMetadata }); this.metamaskSDK = new MetaMaskSDK(_objectSpread(_objectSpread({}, metamaskOptions), {}, { _source: "web3auth", useDeeplink: (_metamaskOptions$useD = metamaskOptions.useDeeplink) !== null && _metamaskOptions$useD !== void 0 ? _metamaskOptions$useD : true })); // Work around: in case there is an existing SDK instance in memory (window.mmsdk exists), it won't initialize the new SDK instance again // and return the existing instance instead of undefined (this is an assumption, not sure if it's a bug or feature of the MetaMask SDK) const initResult = await this.metamaskSDK.init(); if (initResult) { this.metamaskSDK = initResult; } this.isInjected = this.metamaskSDK.isExtensionActive(); this.status = CONNECTOR_STATUS.READY; this.emit(CONNECTOR_EVENTS.READY, WALLET_CONNECTORS.METAMASK); try { if (options.autoConnect) { this.rehydrated = true; const provider = await this.connect({ chainId: options.chainId }); if (!provider) { this.rehydrated = false; throw WalletLoginError.connectionError("Failed to rehydrate."); } } } catch (error) { this.emit(CONNECTOR_EVENTS.REHYDRATION_ERROR, error); } } async connect({ chainId }) { super.checkConnectionRequirements(); if (!this.metamaskSDK) throw WalletLoginError.notConnectedError("Connector is not initialized"); const chainConfig = this.coreOptions.chains.find(x => x.chainId === chainId); if (!chainConfig) throw WalletLoginError.connectionError("Chain config is not available"); // Skip tracking for injected MetaMask since it's handled in connectTo // Skip tracking for rehydration since only new connections are tracked // Only track non-injected MetaMask when connection completes since it auto-initializes to generate QR code const shouldTrack = !this.isInjected && !this.rehydrated; const startTime = Date.now(); const eventData = { connector: this.name, connector_type: this.type, is_injected: this.isInjected, chain_id: getCaipChainId(chainConfig), chain_name: chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.displayName, chain_namespace: chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.chainNamespace }; try { if (this.status !== CONNECTOR_STATUS.CONNECTING) { var _this$metamaskOptions; this.status = CONNECTOR_STATUS.CONNECTING; this.emit(CONNECTOR_EVENTS.CONNECTING, { connector: WALLET_CONNECTORS.METAMASK }); if (!this.metamaskSDK.isExtensionActive() && (_this$metamaskOptions = this.metamaskOptions) !== null && _this$metamaskOptions !== void 0 && _this$metamaskOptions.headless) { // when metamask is not injected and headless is true, broadcast the uri to the login modal this.metamaskSDK.getProvider().on("display_uri", uri => { this.updateConnectorData({ uri }); }); } await this.metamaskSDK.connect(); } this.metamaskProvider = this.metamaskSDK.getProvider(); if (!this.metamaskProvider) throw WalletLoginError.notConnectedError("Failed to connect with provider"); // switch chain if not connected to the right chain const currentChainId = await this.metamaskProvider.request({ method: "eth_chainId" }); if (currentChainId !== chainConfig.chainId) { await this.switchChain(chainConfig, true); } // handle disconnect event const accountDisconnectHandler = accounts => { if (accounts.length === 0) this.disconnect(); }; this.metamaskProvider.on("accountsChanged", accountDisconnectHandler); this.metamaskProvider.once("disconnect", () => { this.disconnect(); }); this.status = CONNECTOR_STATUS.CONNECTED; // track connection events if (shouldTrack) { var _this$analytics, _this$analytics2; (_this$analytics = this.analytics) === null || _this$analytics === void 0 || _this$analytics.track(ANALYTICS_EVENTS.CONNECTION_STARTED, eventData); (_this$analytics2 = this.analytics) === null || _this$analytics2 === void 0 || _this$analytics2.track(ANALYTICS_EVENTS.CONNECTION_COMPLETED, _objectSpread(_objectSpread({}, eventData), {}, { duration: Date.now() - startTime })); } this.emit(CONNECTOR_EVENTS.CONNECTED, { connector: WALLET_CONNECTORS.METAMASK, reconnected: this.rehydrated, provider: this.metamaskProvider }); return this.metamaskProvider; } catch (error) { // ready again to be connected this.status = CONNECTOR_STATUS.READY; if (!this.rehydrated) this.emit(CONNECTOR_EVENTS.ERRORED, error); this.rehydrated = false; // track connection events if (shouldTrack) { var _this$analytics3, _this$analytics4; (_this$analytics3 = this.analytics) === null || _this$analytics3 === void 0 || _this$analytics3.track(ANALYTICS_EVENTS.CONNECTION_STARTED, eventData); (_this$analytics4 = this.analytics) === null || _this$analytics4 === void 0 || _this$analytics4.track(ANALYTICS_EVENTS.CONNECTION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, eventData), getErrorAnalyticsProperties(error)), {}, { duration: Date.now() - startTime })); } if (error instanceof Web3AuthError) throw error; throw WalletLoginError.connectionError("Failed to login with MetaMask wallet", error); } } async disconnect(options = { cleanup: false }) { if (!this.metamaskProvider) throw WalletLoginError.connectionError("MetaMask provider is not available"); await super.disconnectSession(); if (typeof this.metamaskProvider.removeAllListeners !== "undefined") this.metamaskProvider.removeAllListeners(); await this.metamaskSDK.terminate(); if (options.cleanup) { this.status = CONNECTOR_STATUS.NOT_READY; this.metamaskProvider = null; } else { // ready to be connected again this.status = CONNECTOR_STATUS.READY; } await super.disconnect(); } async getUserInfo() { if (this.status !== CONNECTOR_STATUS.CONNECTED) throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); return {}; } async switchChain(params, init = false) { super.checkSwitchChainRequirements(params, init); const requestSwitchChain = () => this.metamaskProvider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: params.chainId }] }); try { await requestSwitchChain(); } catch (error) { // If the error code is 4902, the network needs to be added if ((error === null || error === void 0 ? void 0 : error.code) === 4902) { const chainConfig = this.coreOptions.chains.find(x => x.chainId === params.chainId && [CHAIN_NAMESPACES.EIP155, CHAIN_NAMESPACES.SOLANA].includes(x.chainNamespace)); await this.addChain(chainConfig); await requestSwitchChain(); } else { throw error; } } } async enableMFA() { throw new Error("Method Not implemented"); } async manageMFA() { throw new Error("Method Not implemented"); } async addChain(chainConfig) { if (!this.metamaskProvider) throw WalletLoginError.connectionError("Injected provider is not available"); await this.metamaskProvider.request({ method: "wallet_addEthereumChain", params: [{ chainId: chainConfig.chainId, chainName: chainConfig.displayName, rpcUrls: [chainConfig.rpcTarget], blockExplorerUrls: [chainConfig.blockExplorerUrl], nativeCurrency: { name: chainConfig.tickerName, symbol: chainConfig.ticker, decimals: chainConfig.decimals || 18 }, iconUrls: [chainConfig.logo] }] }); } } const metaMaskConnector = params => { return ({ coreOptions, analytics }) => { return new MetaMaskConnector({ connectorSettings: params, coreOptions, analytics }); }; }; export { metaMaskConnector };