@web3auth/no-modal
Version:
Multi chain wallet aggregator for web3Auth
259 lines (249 loc) • 10.8 kB
JavaScript
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 };